pax_global_header00006660000000000000000000000064126674005060014520gustar00rootroot0000000000000052 comment=a0e0b67b5afbb02d9ea9e48d71ae80b3efb8c0ac rails-4.2.6/000077500000000000000000000000001266740050600126435ustar00rootroot00000000000000rails-4.2.6/.gitignore000066400000000000000000000007561266740050600146430ustar00rootroot00000000000000# Don't put *.swp, *.bak, etc here; those belong in a global ~/.gitignore. # Check out https://help.github.com/articles/ignoring-files for how to set that up. debug.log .Gemfile /.bundle /.ruby-version pkg /dist /doc/rdoc /*/doc /*/test/tmp /activerecord/sqlnet.log /activemodel/test/fixtures/fixture_database.sqlite3 /activesupport/test/fixtures/isolation_test /railties/test/500.html /railties/test/fixtures/tmp /railties/test/initializer/root/log /railties/doc /railties/tmp /guides/output rails-4.2.6/.travis.yml000066400000000000000000000023711266740050600147570ustar00rootroot00000000000000language: ruby sudo: false script: 'ci/travis.rb' before_install: - "rvm current | grep 'jruby' && export AR_JDBC=true || echo" - "rm ${BUNDLE_GEMFILE}.lock" before_script: - bundle update cache: bundler env: global: - JRUBY_OPTS='-J-Xmx1024M' matrix: - "GEM=railties" - "GEM=ap" - "GEM=am,amo,as,av,aj" - "GEM=ar:mysql" - "GEM=ar:mysql2" - "GEM=ar:sqlite3" - "GEM=ar:postgresql" - "GEM=aj:integration" rvm: - 1.9.3 - 2.0.0 - 2.1 - 2.2.4 - 2.3.0 - ruby-head matrix: allow_failures: - rvm: ruby-head - rvm: 1.9.3 env: "GEM=ar:mysql" - rvm: 2.0.0 env: "GEM=ar:mysql" - rvm: ruby-head env: "GEM=ar:mysql" - env: "GEM=aj:integration" fast_finish: true notifications: email: false irc: on_success: change on_failure: always channels: - "irc.freenode.org#rails-contrib" campfire: on_success: change on_failure: always rooms: - secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI=" bundler_args: --without test --jobs 3 --retry 3 services: - memcached - redis - rabbitmq addons: postgresql: "9.3" rails-4.2.6/.yardopts000066400000000000000000000001041266740050600145040ustar00rootroot00000000000000--exclude /templates/ --quiet act*/lib/**/*.rb railties/lib/**/*.rb rails-4.2.6/CONTRIBUTING.md000066400000000000000000000022551266740050600151000ustar00rootroot00000000000000Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the team](http://contributors.rubyonrails.org)! * If you want to submit a bug report please make sure to follow our [reporting guidelines](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#reporting-an-issue). * If you want to submit a patch, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide. * If you want to contribute to Rails documentation, please read the [Contributing to the Rails Documentation](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation) section of the aforementioned guide. *We only accept bug reports and pull requests in GitHub*. * If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk). * If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code. Thanks! :heart: :heart: :heart: Rails Team rails-4.2.6/Gemfile000066400000000000000000000066631266740050600141510ustar00rootroot00000000000000source 'https://rubygems.org' gemspec # We need a newish Rake since Active Job sets its test tasks' descriptions. gem 'rake', '>= 10.3' # This needs to be with require false as it is # loaded after loading the test library to # ensure correct loading order gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' gem 'jquery-rails', '~> 4.0' gem 'coffee-rails', '~> 4.1.0' gem 'turbolinks' gem 'sprockets', '~> 3.0.0.rc.1' gem 'execjs', '< 2.5' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) # being dependent on a binary library. gem 'bcrypt', '~> 3.1.10', require: false # This needs to be with require false to avoid # it being automatically loaded by sprockets gem 'uglifier', '>= 1.3.0', require: false group :doc do gem 'sdoc', '~> 0.4.0' gem 'redcarpet', '~> 3.1.2', platforms: :ruby gem 'w3c_validators' gem 'kindlerb', '0.1.1' gem 'mustache', '~> 0.99.8' end # AS gem 'dalli', '>= 2.2.1' # ActiveJob group :job do gem 'resque', require: false gem 'resque-scheduler', require: false gem 'sidekiq', require: false gem 'sucker_punch', '< 2.0', require: false gem 'delayed_job', require: false gem 'queue_classic', require: false, platforms: :ruby gem 'sneakers', '0.1.1.pre', require: false gem 'que', require: false gem 'backburner', require: false gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false gem 'qu-redis', require: false gem 'delayed_job_active_record', require: false gem 'sequel', require: false gem 'amq-protocol', '< 2.0.0', require: false end # Add your own local bundler stuff local_gemfile = File.dirname(__FILE__) + "/.Gemfile" instance_eval File.read local_gemfile if File.exist? local_gemfile group :test do # FIX: Our test suite isn't ready to run in random order yet gem 'minitest', '< 5.3.4' platforms :mri_19 do gem 'ruby-prof', '~> 0.11.2' end # platforms :mri_19, :mri_20 do # gem 'debugger' # end platforms :mri do gem 'stackprof' end gem 'benchmark-ips' end platforms :ruby do gem 'nokogiri', '>= 1.4.5' # Needed for compiling the ActionDispatch::Journey parser gem 'racc', '>=1.4.6', require: false # AR gem 'sqlite3', '~> 1.3.6' group :db do gem 'pg', '>= 0.15.0' gem 'mysql', '>= 2.9.0' gem 'mysql2', '>= 0.4.0' end end platforms :jruby do gem 'json' if ENV['AR_JDBC'] gem 'activerecord-jdbcsqlite3-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master' group :db do gem 'activerecord-jdbcmysql-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master' gem 'activerecord-jdbcpostgresql-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master' end else gem 'activerecord-jdbcsqlite3-adapter', '>= 1.3.0' group :db do gem 'activerecord-jdbcmysql-adapter', '>= 1.3.0' gem 'activerecord-jdbcpostgresql-adapter', '>= 1.3.0' end end end platforms :rbx do # The rubysl-yaml gem doesn't ship with Psych by default # as it needs libyaml that isn't always available. gem 'psych', '~> 2.0' end # gems that are necessary for ActiveRecord tests with Oracle database if ENV['ORACLE_ENHANCED'] platforms :ruby do gem 'ruby-oci8', '~> 2.1' end gem 'activerecord-oracle_enhanced-adapter', github: 'rsim/oracle-enhanced', branch: 'master' end # A gem necessary for ActiveRecord tests with IBM DB gem 'ibm_db' if ENV['IBM_DB'] rails-4.2.6/Gemfile.lock000066400000000000000000000145441266740050600150750ustar00rootroot00000000000000GIT remote: git://github.com/bkeepers/qu.git revision: d098e2657c92e89a6413bebd9c033930759c061f branch: master specs: qu (0.2.0) qu-rails (0.2.0) qu (= 0.2.0) railties (>= 3.2, < 5) qu-redis (0.2.0) qu (= 0.2.0) redis-namespace PATH remote: . specs: actionmailer (4.2.6) actionpack (= 4.2.6) actionview (= 4.2.6) activejob (= 4.2.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) actionpack (4.2.6) actionview (= 4.2.6) activesupport (= 4.2.6) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) actionview (4.2.6) activesupport (= 4.2.6) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) activejob (4.2.6) activesupport (= 4.2.6) globalid (>= 0.3.0) activemodel (4.2.6) activesupport (= 4.2.6) builder (~> 3.1) activerecord (4.2.6) activemodel (= 4.2.6) activesupport (= 4.2.6) arel (~> 6.0) activesupport (4.2.6) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) rails (4.2.6) actionmailer (= 4.2.6) actionpack (= 4.2.6) actionview (= 4.2.6) activejob (= 4.2.6) activemodel (= 4.2.6) activerecord (= 4.2.6) activesupport (= 4.2.6) bundler (>= 1.3.0, < 2.0) railties (= 4.2.6) sprockets-rails railties (4.2.6) actionpack (= 4.2.6) activesupport (= 4.2.6) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) GEM remote: https://rubygems.org/ specs: amq-protocol (1.9.2) arel (6.0.3) backburner (1.2.0) beaneater (~> 1.0) dante (> 0.1.5) bcrypt (3.1.10) bcrypt (3.1.10-x64-mingw32) bcrypt (3.1.10-x86-mingw32) beaneater (1.0.0) benchmark-ips (2.3.0) builder (3.2.2) bunny (1.1.9) amq-protocol (>= 1.9.2) celluloid (0.15.2) timers (~> 1.1.0) coffee-rails (4.1.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.1.x) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.10.0) concurrent-ruby (1.0.0) connection_pool (2.2.0) dalli (2.7.5) dante (0.2.0) delayed_job (4.1.1) activesupport (>= 3.0, < 5.0) delayed_job_active_record (4.1.0) activerecord (>= 3.0, < 5) delayed_job (>= 3.0, < 5) erubis (2.7.0) execjs (2.4.0) globalid (0.3.6) activesupport (>= 4.1.0) i18n (0.7.0) jquery-rails (4.0.5) rails-dom-testing (~> 1.0) railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (1.8.3) kindlerb (0.1.1) mustache nokogiri loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) metaclass (0.0.4) mime-types (2.99.1) mini_portile2 (2.0.0) minitest (5.3.3) mocha (0.14.0) metaclass (~> 0.0.1) mono_logger (1.1.0) multi_json (1.11.2) mustache (0.99.8) mysql (2.9.1) mysql2 (0.4.2) nokogiri (1.6.7.1) mini_portile2 (~> 2.0.0.rc2) nokogiri (1.6.7.1-x64-mingw32) mini_portile2 (~> 2.0.0.rc2) nokogiri (1.6.7.1-x86-mingw32) mini_portile2 (~> 2.0.0.rc2) pg (0.18.4) psych (2.0.17) que (0.11.2) queue_classic (3.1.0) pg (>= 0.17, < 0.19) racc (1.4.14) rack (1.6.4) rack-cache (1.5.1) rack (>= 0.4) rack-protection (1.5.3) rack rack-test (0.6.3) rack (>= 1.0) rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) rails-dom-testing (1.0.7) activesupport (>= 4.2.0.beta, < 5.0) nokogiri (~> 1.6.0) rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) rake (10.4.2) rdoc (4.2.1) json (~> 1.4) redcarpet (3.1.2) redis (3.2.2) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) resque (1.25.2) mono_logger (~> 1.0) multi_json (~> 1.0) redis-namespace (~> 1.3) sinatra (>= 0.9.2) vegas (~> 0.1.2) resque-scheduler (4.0.0) mono_logger (~> 1.0) redis (~> 3.0) resque (~> 1.25) rufus-scheduler (~> 3.0) ruby-prof (0.11.3) rufus-scheduler (3.1.10) sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) sequel (4.29.0) serverengine (1.5.11) sigdump (~> 0.2.2) sidekiq (4.0.1) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) json (~> 1.0) redis (~> 3.2, >= 3.2.1) sigdump (0.2.3) sinatra (1.4.6) rack (~> 1.4) rack-protection (~> 1.4) tilt (>= 1.3, < 3) sneakers (0.1.1.pre) bunny (~> 1.1.3) serverengine thor thread sprockets (3.0.3) rack (~> 1.0) sprockets-rails (3.0.4) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.11) stackprof (0.2.7) sucker_punch (1.0.5) celluloid (~> 0.15.2) thor (0.19.1) thread (0.2.2) thread_safe (0.3.5) tilt (2.0.1) timers (1.1.0) turbolinks (2.5.3) coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) vegas (0.1.11) rack (>= 1.0.0) w3c_validators (1.2) json nokogiri PLATFORMS ruby x64-mingw32 x86-mingw32 DEPENDENCIES activerecord-jdbcmysql-adapter (>= 1.3.0) activerecord-jdbcpostgresql-adapter (>= 1.3.0) activerecord-jdbcsqlite3-adapter (>= 1.3.0) amq-protocol (< 2.0.0) backburner bcrypt (~> 3.1.10) benchmark-ips coffee-rails (~> 4.1.0) dalli (>= 2.2.1) delayed_job delayed_job_active_record execjs (< 2.5) jquery-rails (~> 4.0) json kindlerb (= 0.1.1) minitest (< 5.3.4) mocha (~> 0.14) mustache (~> 0.99.8) mysql (>= 2.9.0) mysql2 (>= 0.4.0) nokogiri (>= 1.4.5) pg (>= 0.15.0) psych (~> 2.0) qu-rails! qu-redis que queue_classic racc (>= 1.4.6) rack-cache (~> 1.2) rails! rake (>= 10.3) redcarpet (~> 3.1.2) resque resque-scheduler ruby-prof (~> 0.11.2) sdoc (~> 0.4.0) sequel sidekiq sneakers (= 0.1.1.pre) sprockets (~> 3.0.0.rc.1) sqlite3 (~> 1.3.6) stackprof sucker_punch (< 2.0) turbolinks uglifier (>= 1.3.0) w3c_validators BUNDLED WITH 1.11.2 rails-4.2.6/RAILS_VERSION000066400000000000000000000000061266740050600146410ustar00rootroot000000000000004.2.6 rails-4.2.6/README.md000066400000000000000000000101521266740050600141210ustar00rootroot00000000000000## Welcome to Rails Rails is a web-application framework that includes everything needed to create database-backed web applications according to the [Model-View-Controller (MVC)](http://en.wikipedia.org/wiki/Model-view-controller) pattern. Understanding the MVC pattern is key to understanding Rails. MVC divides your application into three layers, each with a specific responsibility. The _Model layer_ represents your domain model (such as Account, Product, Person, Post, etc.) and encapsulates the business logic that is specific to your application. In Rails, database-backed model classes are derived from `ActiveRecord::Base`. Active Record allows you to present the data from database rows as objects and embellish these data objects with business logic methods. You can read more about Active Record in its [README](activerecord/README.rdoc). Although most Rails models are backed by a database, models can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as provided by the Active Model module. You can read more about Active Model in its [README](activemodel/README.rdoc). The _Controller layer_ is responsible for handling incoming HTTP requests and providing a suitable response. Usually this means returning HTML, but Rails controllers can also generate XML, JSON, PDFs, mobile-specific views, and more. Controllers load and manipulate models, and render view templates in order to generate the appropriate HTTP response. In Rails, incoming requests are routed by Action Dispatch to an appropriate controller, and controller classes are derived from `ActionController::Base`. Action Dispatch and Action Controller are bundled together in Action Pack. You can read more about Action Pack in its [README](actionpack/README.rdoc). The _View layer_ is composed of "templates" that are responsible for providing appropriate representations of your application's resources. Templates can come in a variety of formats, but most view templates are HTML with embedded Ruby code (ERB files). Views are typically rendered to generate a controller response, or to generate the body of an email. In Rails, View generation is handled by Action View. You can read more about Action View in its [README](actionview/README.rdoc). Active Record, Action Pack, and Action View can each be used independently outside Rails. In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library to generate and send emails; Active Job ([README](activejob/README.md)), a framework for declaring jobs and making them run on a variety of queueing backends; and Active Support ([README](activesupport/README.rdoc)), a collection of utility classes and standard library extensions that are useful for Rails, and may also be used independently outside Rails. ## Getting Started 1. Install Rails at the command prompt if you haven't yet: gem install rails 2. At the command prompt, create a new Rails application: rails new myapp where "myapp" is the application name. 3. Change directory to `myapp` and start the web server: cd myapp rails server Run with `--help` or `-h` for options. 4. Using a browser, go to `http://localhost:3000` and you'll see: "Welcome aboard: You're riding Ruby on Rails!" 5. Follow the guidelines to start developing your application. You may find the following resources handy: * [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html) * [Ruby on Rails Guides](http://guides.rubyonrails.org) * [The API Documentation](http://api.rubyonrails.org) * [Ruby on Rails Tutorial](http://www.railstutorial.org/book) ## Contributing We encourage you to contribute to Ruby on Rails! Please check out the [Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org) ## Code Status * [![Build Status](https://travis-ci.org/rails/rails.svg?branch=4-2-stable)](https://travis-ci.org/rails/rails) ## License Ruby on Rails is released under the [MIT License](http://www.opensource.org/licenses/MIT). rails-4.2.6/RELEASING_RAILS.rdoc000066400000000000000000000160311266740050600157600ustar00rootroot00000000000000= Releasing Rails In this document, we'll cover the steps necessary to release Rails. Each section contains steps to take during that time before the release. The times suggested in each header are just that: suggestions. However, they should really be considered as minimums. == 10 Days before release Today is mostly coordination tasks. Here are the things you must do today: === Is the CI green? If not, make it green. (See "Fixing the CI") Do not release with a Red CI. You can find the CI status here: http://travis-ci.org/rails/rails === Is Sam Ruby happy? If not, make him happy. Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile Web Development with Rails) all work. These are valuable integration tests for Rails. You can check the status of his tests here: http://intertwingly.net/projects/dashboard.html Do not release with Red AWDwR tests. === Are the supported plugins working? If not, make it work. Some Rails plugins are important and need to be supported until Rails 5. As these plugins are outside the Rails repository it is easy to break them without knowing after some refactoring or bug fix, so it is important to check if the following plugins are working with the versions that will be released: * https://github.com/rails/protected_attributes * https://github.com/rails/activerecord-deprecated_finders Do not release red plugins tests. === Do we have any Git dependencies? If so, contact those authors. Having Git dependencies indicates that we depend on unreleased code. Obviously Rails cannot be released when it depends on unreleased code. Contact the authors of those particular gems and work out a release date that suits them. === Contact the security team (either Koz or tenderlove) Let them know of your plans to release. There may be security issues to be addressed, and that can impact your release date. === Notify implementors. Ruby implementors have high stakes in making sure Rails works. Be kind and give them a heads up that Rails will be released soonish. This only need to be done for major and minor releases, bugfix releases aren't a big enough deal, and are supposed to be backwards compatible. Send an email just giving a heads up about the upcoming release to these lists: * team@jruby.org * community@rubini.us * rubyonrails-core@googlegroups.com Implementors will love you and help you. == 3 Days before release This is when you should release the release candidate. Here are your tasks for today: === Is the CI green? If not, make it green. === Is Sam Ruby happy? If not, make him happy. === Contact the security team. CVE emails must be sent on this day. === Create a release branch. From the stable branch, create a release branch. For example, if you're releasing Rails 3.0.10, do this: [aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10 Switched to a new branch '3-0-10' [aaron@higgins rails (3-0-10)]$ === Update each CHANGELOG. Many times commits are made without the CHANGELOG being updated. You should review the commits since the last release, and fill in any missing information for each CHANGELOG. You can review the commits for the 3.0.10 release like this: [aaron@higgins rails (3-0-10)]$ git log v3.0.9.. If you're doing a stable branch release, you should also ensure that all of the CHANGELOG entries in the stable branch are also synced to the master branch. === Update the RAILS_VERSION file to include the RC. === Build and test the gem. Run `rake install` to generate the gems and install them locally. Then try generating a new app and ensure that nothing explodes. This will stop you from looking silly when you push an RC to rubygems.org and then realise it is broken. === Release the gem. IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest to use Ruby 1.8 when releasing. Run `rake release`. This will populate the gemspecs with data from RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org. Here are the commands that `rake release` should use, so you can understand what to do in case anything goes wrong: $ rake all:build $ git commit -am'updating RAILS_VERSION' $ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1 $ git push $ git push --tags $ for i in $(ls pkg); do gem push $i; done === Send Rails release announcements Write a release announcement that includes the version, changes, and links to GitHub where people can find the specific commit list. Here are the mailing lists where you should announce: * rubyonrails-core@googlegroups.com * rubyonrails-talk@googlegroups.com * ruby-talk@ruby-lang.org Use Markdown format for your announcement. Remember to ask people to report issues with the release candidate to the rails-core mailing list. IMPORTANT: If any users experience regressions when using the release candidate, you *must* postpone the release. Bugfix releases *should not* break existing applications. === Post the announcement to the Rails blog. If you used Markdown format for your email, you can just paste it in to the blog. * http://weblog.rubyonrails.org === Post the announcement to the Rails Twitter account. == Time between release candidate and actual release Check the rails-core mailing list and the GitHub issue list for regressions in the RC. If any regressions are found, fix the regressions and repeat the release candidate process. We will not release the final until 72 hours after the last release candidate has been pushed. This means that if users find regressions, the scheduled release date must be postponed. When you fix the regressions, do not create a new branch. Fix them on the stable branch, then cherry pick the commit to your release branch. No other commits should be added to the release branch besides regression fixing commits. == Day of release Many of these steps are the same as for the release candidate, so if you need more explanation on a particular step, see the RC steps. Today, do this stuff in this order: * Apply security patches to the release branch * Update CHANGELOG with security fixes. * Update RAILS_VERSION to remove the rc * Build and test the gem * Release the gems * If releasing a new stable version: - Trigger stable docs generation (see below) - Update the version in the home page * Email security lists * Email general announcement lists === Emailing the Rails security announce list Email the security announce list once for each vulnerability fixed. You can do this, or ask the security team to do it. Email the security reports to: * rubyonrails-security@googlegroups.com * oss-security@lists.openwall.com Be sure to note the security fixes in your announcement along with CVE numbers and links to each patch. Some people may not be able to upgrade right away, so we need to give them the security fixes in patch form. * Blog announcements * Twitter announcements * Merge the release branch to the stable branch. * Drink beer (or other cocktail) == Misc === Fixing the CI There are two simple steps for fixing the CI: 1. Identify the problem 2. Fix it Repeat these steps until the CI is green. rails-4.2.6/Rakefile000066400000000000000000000037041266740050600143140ustar00rootroot00000000000000require 'sdoc' require 'net/http' $:.unshift File.expand_path('..', __FILE__) require "tasks/release" require 'railties/lib/rails/api/task' desc "Build gem files for all projects" task :build => "all:build" desc "Release all gems to rubygems and create a tag" task :release => "all:release" PROJECTS = %w(activesupport activemodel actionpack actionview actionmailer activerecord railties activejob) desc 'Run all tests by default' task :default => %w(test test:isolated) %w(test test:isolated package gem).each do |task_name| desc "Run #{task_name} task for all projects" task task_name do errors = [] PROJECTS.each do |project| system(%(cd #{project} && #{$0} #{task_name})) || errors << project end fail("Errors in #{errors.join(', ')}") unless errors.empty? end end desc "Smoke-test all projects" task :smoke do (PROJECTS - %w(activerecord)).each do |project| system %(cd #{project} && #{$0} test:isolated) end system %(cd activerecord && #{$0} sqlite3:isolated_test) end desc "Install gems for all projects." task :install => "all:install" desc "Generate documentation for the Rails framework" if ENV['EDGE'] Rails::API::EdgeTask.new('rdoc') else Rails::API::StableTask.new('rdoc') end desc 'Bump all versions to match RAILS_VERSION' task :update_versions => "all:update_versions" # We have a webhook configured in GitHub that gets invoked after pushes. # This hook triggers the following tasks: # # * updates the local checkout # * updates Rails Contributors # * generates and publishes edge docs # * if there's a new stable tag, generates and publishes stable docs # # Everything is automated and you do NOT need to run this task normally. desc 'Publishes docs, run this AFTER a new stable tag has been pushed' task :publish_docs do Net::HTTP.new('api.rubyonrails.org', 8080).start do |http| request = Net::HTTP::Post.new('/rails-master-hook') response = http.request(request) puts response.body end end rails-4.2.6/actionmailer/000077500000000000000000000000001266740050600153125ustar00rootroot00000000000000rails-4.2.6/actionmailer/CHANGELOG.md000066400000000000000000000056261266740050600171340ustar00rootroot00000000000000## Rails 4.2.6 (March 07, 2016) ## * No changes. ## Rails 4.2.5.2 (February 26, 2016) ## * No changes. ## Rails 4.2.5.1 (January 25, 2015) ## * No changes. ## Rails 4.2.5 (November 12, 2015) ## * No changes. ## Rails 4.2.4 (August 24, 2015) ## * No Changes * ## Rails 4.2.3 (June 25, 2015) ## * `assert_emails` in block form use the given number as expected value. This makes the error message much easier to understand. *Yuji Yaginuma* * Mailer preview now uses `url_for` to fix links to emails for apps running on a subdirectory. *Remo Mueller* * Mailer previews no longer crash when the `mail` method wasn't called (`NullMail`). Fixes #19849. *Yves Senn* * Make sure labels and values line up in mailer previews. *Yves Senn* ## Rails 4.2.2 (June 16, 2015) ## * No Changes * ## Rails 4.2.1 (March 19, 2015) ## * No Changes * ## Rails 4.2.0 (December 20, 2014) ## * `MailerGenerator` now generates layouts by default. The HTML mailer layout now includes `` and `` tags which improve the spam rating in some spam detection engines. Mailers now inherit from `ApplicationMailer` which sets the default layout. *Andy Jeffries* * `link_to` and `url_for` now generate URLs by default in templates. Passing `only_path: false` is no longer needed. Fixes #16497 and #16589. *Xavier Noria*, *Richard Schneeman* * Attachments can now be added while rendering the mail template. Fixes #16974. *Christian Felder* * Add `#deliver_later` and `#deliver_now` methods and deprecate `#deliver` in favor of `#deliver_now`. `#deliver_later` will enqueue a job to render and deliver the mail instead of delivering it immediately. The job is enqueued using the new Active Job framework in Rails and will use the queue that you have configured in Rails. *DHH*, *Abdelkader Boudih*, *Cristian Bica* * `ActionMailer::Previews` are now class methods instead of instance methods. *Cristian Bica* * Deprecate `*_path` helpers in email views. They generated broken links in email views and were not the intention of most developers. The `*_url` helper is recommended instead. *Richard Schneeman* * Raise an exception when attachments are added after `mail` is called. This is a safeguard to prevent invalid emails. Fixes #16163. *Yves Senn* * Add `config.action_mailer.show_previews` configuration option. This configuration option can be used to enable the mail preview in environments other than development (such as staging). Defaults to `true` in development and `false` elsewhere. *Leonard Garvey* * Allow preview interceptors to be registered through `config.action_mailer.preview_interceptors`. See #15739. *Yves Senn* Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md) for previous changes. rails-4.2.6/actionmailer/MIT-LICENSE000066400000000000000000000020621266740050600167460ustar00rootroot00000000000000Copyright (c) 2004-2014 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rails-4.2.6/actionmailer/README.rdoc000066400000000000000000000130501266740050600171170ustar00rootroot00000000000000= Action Mailer -- Easy email delivery and testing Action Mailer is a framework for designing email service layers. These layers are used to consolidate code for sending out forgotten passwords, welcome wishes on signup, invoices for billing, and any other use case that requires a written notification to either a person or another system. Action Mailer is in essence a wrapper around Action Controller and the Mail gem. It provides a way to make emails using templates in the same way that Action Controller renders views using templates. Additionally, an Action Mailer class can be used to process incoming email, such as allowing a blog to accept new posts from an email (which could even have been sent from a phone). == Sending emails The framework works by initializing any instance variables you want to be available in the email template, followed by a call to +mail+ to deliver the email. This can be as simple as: class Notifier < ActionMailer::Base default from: 'system@loudthinking.com' def welcome(recipient) @recipient = recipient mail(to: recipient, subject: "[Signed up] Welcome #{recipient}") end end The body of the email is created by using an Action View template (regular ERB) that has the instance variables that are declared in the mailer action. So the corresponding body template for the method above could look like this: Hello there, Mr. <%= @recipient %> Thank you for signing up! If the recipient was given as "david@loudthinking.com", the email generated would look like this: Date: Mon, 25 Jan 2010 22:48:09 +1100 From: system@loudthinking.com To: david@loudthinking.com Message-ID: <4b5d84f9dd6a5_7380800b81ac29578@void.loudthinking.com.mail> Subject: [Signed up] Welcome david@loudthinking.com Mime-Version: 1.0 Content-Type: text/plain; charset="US-ASCII"; Content-Transfer-Encoding: 7bit Hello there, Mr. david@loudthinking.com Thank you for signing up! In order to send mails, you simply call the method and then call +deliver_now+ on the return value. Calling the method returns a Mail Message object: message = Notifier.welcome("david@loudthinking.com") # => Returns a Mail::Message object message.deliver_now # => delivers the email Or you can just chain the methods together like: Notifier.welcome("david@loudthinking.com").deliver_now # Creates the email and sends it immediately == Setting defaults It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method +default+ which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers, email messages have, like +:from+ as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed. Note that every value you set with this method will get overwritten if you use the same key in your mailer method. Example: class AuthenticationMailer < ActionMailer::Base default from: "awesome@application.com", subject: Proc.new { "E-mail was generated at #{Time.now}" } ..... end == Receiving emails To receive emails, you need to implement a public instance method called +receive+ that takes an email object as its single parameter. The Action Mailer framework has a corresponding class method, which is also called +receive+, that accepts a raw, unprocessed email as a string, which it then turns into the email object and calls the receive instance method. Example: class Mailman < ActionMailer::Base def receive(email) page = Page.find_by(address: email.to.first) page.emails.create( subject: email.subject, body: email.body ) if email.has_attachments? email.attachments.each do |attachment| page.attachments.create({ file: attachment, description: email.subject }) end end end end This Mailman can be the target for Postfix or other MTAs. In Rails, you would use the runner in the trivial case like this: rails runner 'Mailman.receive(STDIN.read)' However, invoking Rails in the runner for each mail to be received is very resource intensive. A single instance of Rails should be run within a daemon, if it is going to process more than just a limited amount of email. == Configuration The Base class has the full list of configuration options. Here's an example: ActionMailer::Base.smtp_settings = { address: 'smtp.yourserver.com', # default: localhost port: '25', # default: 25 user_name: 'user', password: 'pass', authentication: :plain # :plain, :login or :cram_md5 } == Download and installation The latest version of Action Mailer can be installed with RubyGems: % [sudo] gem install actionmailer Source code can be downloaded as part of the Rails project on GitHub * https://github.com/rails/rails/tree/4-2-stable/actionmailer == License Action Mailer is released under the MIT license: * http://www.opensource.org/licenses/MIT == Support API documentation is at * http://api.rubyonrails.org Bug reports can be filed for the Ruby on Rails project here: * https://github.com/rails/rails/issues Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core rails-4.2.6/actionmailer/Rakefile000066400000000000000000000013341266740050600167600ustar00rootroot00000000000000require 'rake/testtask' require 'rubygems/package_task' desc "Default Task" task default: [ :test ] # Run the unit tests Rake::TestTask.new { |t| t.libs << "test" t.pattern = 'test/**/*_test.rb' t.warning = true t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) } namespace :test do task :isolated do Dir.glob("test/**/*_test.rb").all? do |file| sh(Gem.ruby, '-w', '-Ilib:test', file) end or raise "Failures" end end spec = eval(File.read('actionmailer.gemspec')) Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end desc "Release to rubygems" task release: :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define Rake::Task['gem:push'].invoke end rails-4.2.6/actionmailer/actionmailer.gemspec000066400000000000000000000020141266740050600213230ustar00rootroot00000000000000version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'actionmailer' s.version = version s.summary = 'Email composition, delivery, and receiving framework (part of Rails).' s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.' s.required_ruby_version = '>= 1.9.3' s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] s.require_path = 'lib' s.requirements << 'none' s.add_dependency 'actionpack', version s.add_dependency 'actionview', version s.add_dependency 'activejob', version s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4'] s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' end rails-4.2.6/actionmailer/lib/000077500000000000000000000000001266740050600160605ustar00rootroot00000000000000rails-4.2.6/actionmailer/lib/action_mailer.rb000066400000000000000000000037521266740050600212220ustar00rootroot00000000000000#-- # Copyright (c) 2004-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ require 'abstract_controller' require 'action_mailer/version' # Common Active Support usage in Action Mailer require 'active_support/rails' require 'active_support/core_ext/class' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/string/inflections' require 'active_support/lazy_load_hooks' module ActionMailer extend ::ActiveSupport::Autoload eager_autoload do autoload :Collector end autoload :Base autoload :DeliveryMethods autoload :InlinePreviewInterceptor autoload :MailHelper autoload :Preview autoload :Previews, 'action_mailer/preview' autoload :TestCase autoload :TestHelper autoload :MessageDelivery autoload :DeliveryJob end autoload :Mime, 'action_dispatch/http/mime_type' ActiveSupport.on_load(:action_view) do ActionView::Base.default_formats ||= Mime::SET.symbols ActionView::Template::Types.delegate_to Mime end rails-4.2.6/actionmailer/lib/action_mailer/000077500000000000000000000000001266740050600206665ustar00rootroot00000000000000rails-4.2.6/actionmailer/lib/action_mailer/base.rb000066400000000000000000001136701266740050600221350ustar00rootroot00000000000000require 'mail' require 'action_mailer/collector' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/anonymous' require 'action_mailer/log_subscriber' module ActionMailer # Action Mailer allows you to send email from your application using a mailer model and views. # # = Mailer Models # # To use Action Mailer, you need to create a mailer model. # # $ rails generate mailer Notifier # # The generated model inherits from ApplicationMailer which in turn # inherits from ActionMailer::Base. A mailer model defines methods # used to generate an email message. In these methods, you can setup variables to be used in # the mailer views, options on the mail itself such as the :from address, and attachments. # # class ApplicationMailer < ActionMailer::Base # default from: 'from@exmaple.com' # layout 'mailer' # end # # class Notifier < ApplicationMailer # default from: 'no-reply@example.com', # return_path: 'system@example.com' # # def welcome(recipient) # @account = recipient # mail(to: recipient.email_address_with_name, # bcc: ["bcc@example.com", "Order Watcher "]) # end # end # # Within the mailer method, you have access to the following methods: # # * attachments[]= - Allows you to add attachments to your email in an intuitive # manner; attachments['filename.png'] = File.read('path/to/filename.png') # # * attachments.inline[]= - Allows you to add an inline attachment to your email # in the same manner as attachments[]= # # * headers[]= - Allows you to specify any header field in your email such # as headers['X-No-Spam'] = 'True'. Note that declaring a header multiple times # will add many fields of the same name. Read #headers doc for more information. # # * headers(hash) - Allows you to specify multiple headers in your email such # as headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'}) # # * mail - Allows you to specify email to be sent. # # The hash passed to the mail method allows you to specify any header that a Mail::Message # will accept (any valid email header including optional fields). # # The mail method, if not passed a block, will inspect your views and send all the views with # the same name as the method, so the above action would send the +welcome.text.erb+ view # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email. # # If you want to explicitly render only certain templates, pass a block: # # mail(to: user.email) do |format| # format.text # format.html # end # # The block syntax is also useful in providing information specific to a part: # # mail(to: user.email) do |format| # format.text(content_transfer_encoding: "base64") # format.html # end # # Or even to render a special view: # # mail(to: user.email) do |format| # format.text # format.html { render "some_other_template" } # end # # = Mailer views # # Like Action Controller, each mailer class has a corresponding view directory in which each # method of the class looks for a template with its name. # # To define a template to be used with a mailing, create an .erb file with the same # name as the method in your mailer model. For example, in the mailer defined above, the template at # app/views/notifier/welcome.text.erb would be used to generate the email. # # Variables defined in the methods of your mailer model are accessible as instance variables in their # corresponding view. # # Emails by default are sent in plain text, so a sample view for our model example might look like this: # # Hi <%= @account.name %>, # Thanks for joining our service! Please check back often. # # You can even use Action View helpers in these views. For example: # # You got a new note! # <%= truncate(@note.body, length: 25) %> # # If you need to access the subject, from or the recipients in the view, you can do that through message object: # # You got a new note from <%= message.from %>! # <%= truncate(@note.body, length: 25) %> # # # = Generating URLs # # URLs can be generated in mailer views using url_for or named routes. Unlike controllers from # Action Pack, the mailer instance doesn't have any context about the incoming request, so you'll need # to provide all of the details needed to generate a URL. # # When using url_for you'll need to provide the :host, :controller, and :action: # # <%= url_for(host: "example.com", controller: "welcome", action: "greeting") %> # # When using named routes you only need to supply the :host: # # <%= users_url(host: "example.com") %> # # You should use the named_route_url style (which generates absolute URLs) and avoid using the # named_route_path style (which generates relative URLs), since clients reading the mail will # have no concept of a current URL from which to determine a relative path. # # It is also possible to set a default host that will be used in all mailers by setting the :host # option as a configuration option in config/application.rb: # # config.action_mailer.default_url_options = { host: "example.com" } # # When you decide to set a default :host for your mailers, then you need to make sure to use the # only_path: false option when using url_for. Since the url_for view helper # will generate relative URLs by default when a :host option isn't explicitly provided, passing # only_path: false will ensure that absolute URLs are generated. # # = Sending mail # # Once a mailer action and template are defined, you can deliver your message or create it and save it # for delivery later: # # Notifier.welcome(User.first).deliver_now # sends the email # mail = Notifier.welcome(User.first) # => an ActionMailer::MessageDelivery object # mail.deliver_now # sends the email # # The ActionMailer::MessageDelivery class is a wrapper around a Mail::Message object. If # you want direct access to the Mail::Message object you can call the message method on # the ActionMailer::MessageDelivery object. # # Notifier.welcome(User.first).message # => a Mail::Message object # # Action Mailer is nicely integrated with Active Job so you can send emails in the background (example: outside # of the request-response cycle, so the user doesn't have to wait on it): # # Notifier.welcome(User.first).deliver_later # enqueue the email sending to Active Job # # You never instantiate your mailer class. Rather, you just call the method you defined on the class itself. # # = Multipart Emails # # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use # multipart templates, where each template is named after the name of the action, followed by the content # type. Each such detected template will be added as a separate part to the message. # # For example, if the following templates exist: # * signup_notification.text.erb # * signup_notification.html.erb # * signup_notification.xml.builder # * signup_notification.yml.erb # # Each would be rendered and added as a separate part to the message, with the corresponding content # type. The content type for the entire message is automatically set to multipart/alternative, # which indicates that the email contains multiple different representations of the same email # body. The same instance variables defined in the action are passed to all email templates. # # Implicit template rendering is not performed if any attachments or parts have been added to the email. # This means that you'll have to manually add each part to the email and set the content type of the email # to multipart/alternative. # # = Attachments # # Sending attachment in emails is easy: # # class Notifier < ApplicationMailer # def welcome(recipient) # attachments['free_book.pdf'] = File.read('path/to/file.pdf') # mail(to: recipient, subject: "New account information") # end # end # # Which will (if it had both a welcome.text.erb and welcome.html.erb # template in the view directory), send a complete multipart/mixed email with two parts, # the first part being a multipart/alternative with the text and HTML email parts inside, # and the second being a application/pdf with a Base64 encoded copy of the file.pdf book # with the filename +free_book.pdf+. # # If you need to send attachments with no content, you need to create an empty view for it, # or add an empty body parameter like this: # # class Notifier < ApplicationMailer # def welcome(recipient) # attachments['free_book.pdf'] = File.read('path/to/file.pdf') # mail(to: recipient, subject: "New account information", body: "") # end # end # # = Inline Attachments # # You can also specify that a file should be displayed inline with other HTML. This is useful # if you want to display a corporate logo or a photo. # # class Notifier < ApplicationMailer # def welcome(recipient) # attachments.inline['photo.png'] = File.read('path/to/photo.png') # mail(to: recipient, subject: "Here is what we look like") # end # end # # And then to reference the image in the view, you create a welcome.html.erb file and # make a call to +image_tag+ passing in the attachment you want to display and then call # +url+ on the attachment to get the relative content id path for the image source: # #

Please Don't Cringe

# # <%= image_tag attachments['photo.png'].url -%> # # As we are using Action View's +image_tag+ method, you can pass in any other options you want: # #

Please Don't Cringe

# # <%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%> # # = Observing and Intercepting Mails # # Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to # register classes that are called during the mail delivery life cycle. # # An observer class must implement the :delivered_email(message) method which will be # called once for every email sent after the email has been sent. # # An interceptor class must implement the :delivering_email(message) method which will be # called before the email is sent, allowing you to make modifications to the email before it hits # the delivery agents. Your class should make any needed modifications directly to the passed # in Mail::Message instance. # # = Default Hash # # Action Mailer provides some intelligent defaults for your emails, these are usually specified in a # default method inside the class definition: # # class Notifier < ApplicationMailer # default sender: 'system@example.com' # end # # You can pass in any header value that a Mail::Message accepts. Out of the box, # ActionMailer::Base sets the following: # # * mime_version: "1.0" # * charset: "UTF-8", # * content_type: "text/plain", # * parts_order: [ "text/plain", "text/enriched", "text/html" ] # # parts_order and charset are not actually valid Mail::Message header fields, # but Action Mailer translates them appropriately and sets the correct values. # # As you can pass in any header, you need to either quote the header as a string, or pass it in as # an underscored symbol, so the following will work: # # class Notifier < ApplicationMailer # default 'Content-Transfer-Encoding' => '7bit', # content_description: 'This is a description' # end # # Finally, Action Mailer also supports passing Proc objects into the default hash, so you # can define methods that evaluate as the message is being generated: # # class Notifier < ApplicationMailer # default 'X-Special-Header' => Proc.new { my_method } # # private # # def my_method # 'some complex call' # end # end # # Note that the proc is evaluated right at the start of the mail message generation, so if you # set something in the defaults using a proc, and then set the same thing inside of your # mailer method, it will get over written by the mailer method. # # It is also possible to set these default options that will be used in all mailers through # the default_options= configuration in config/application.rb: # # config.action_mailer.default_options = { from: "no-reply@example.org" } # # = Callbacks # # You can specify callbacks using before_action and after_action for configuring your messages. # This may be useful, for example, when you want to add default inline attachments for all # messages sent out by a certain mailer class: # # class Notifier < ApplicationMailer # before_action :add_inline_attachment! # # def welcome # mail # end # # private # # def add_inline_attachment! # attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg') # end # end # # Callbacks in Action Mailer are implemented using # AbstractController::Callbacks, so you can define and configure # callbacks in the same manner that you would use callbacks in classes that # inherit from ActionController::Base. # # Note that unless you have a specific reason to do so, you should prefer using before_action # rather than after_action in your Action Mailer classes so that headers are parsed properly. # # = Previewing emails # # You can preview your email templates visually by adding a mailer preview file to the # ActionMailer::Base.preview_path. Since most emails do something interesting # with database data, you'll need to write some scenarios to load messages with fake data: # # class NotifierPreview < ActionMailer::Preview # def welcome # Notifier.welcome(User.first) # end # end # # Methods must return a Mail::Message object which can be generated by calling the mailer # method without the additional deliver_now / deliver_later. The location of the # mailer previews directory can be configured using the preview_path option which has a default # of test/mailers/previews: # # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews" # # An overview of all previews is accessible at http://localhost:3000/rails/mailers # on a running development server instance. # # Previews can also be intercepted in a similar manner as deliveries can be by registering # a preview interceptor that has a previewing_email method: # # class CssInlineStyler # def self.previewing_email(message) # # inline CSS styles # end # end # # config.action_mailer.preview_interceptors :css_inline_styler # # Note that interceptors need to be registered both with register_interceptor # and register_preview_interceptor if they should operate on both sending and # previewing emails. # # = Configuration options # # These options are specified on the class level, like # ActionMailer::Base.raise_delivery_errors = true # # * default_options - You can pass this in at a class level as well as within the class itself as # per the above section. # # * logger - the logger is used for generating information on the mailing run if available. # Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers. # # * smtp_settings - Allows detailed configuration for :smtp delivery method: # * :address - Allows you to use a remote mail server. Just change it from its default # "localhost" setting. # * :port - On the off chance that your mail server doesn't run on port 25, you can change it. # * :domain - If you need to specify a HELO domain, you can do it here. # * :user_name - If your mail server requires authentication, set the username in this setting. # * :password - If your mail server requires authentication, set the password in this setting. # * :authentication - If your mail server requires authentication, you need to specify the # authentication type here. # This is a symbol and one of :plain (will send the password Base64 encoded), :login (will # send the password Base64 encoded) or :cram_md5 (combines a Challenge/Response mechanism to exchange # information and a cryptographic Message Digest 5 algorithm to hash important information) # * :enable_starttls_auto - Detects if STARTTLS is enabled in your SMTP server and starts # to use it. Defaults to true. # * :openssl_verify_mode - When using TLS, you can set how OpenSSL checks the certificate. This is # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name # of an OpenSSL verify constant ('none', 'peer', 'client_once', # 'fail_if_no_peer_cert') or directly the constant (OpenSSL::SSL::VERIFY_NONE, # OpenSSL::SSL::VERIFY_PEER, ...). # # * sendmail_settings - Allows you to override options for the :sendmail delivery method. # * :location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail. # * :arguments - The command line arguments. Defaults to -i -t with -f sender@address # added automatically before the message is sent. # # * file_settings - Allows you to override options for the :file delivery method. # * :location - The directory into which emails will be written. Defaults to the application # tmp/mails. # # * raise_delivery_errors - Whether or not errors should be raised if the email fails to be delivered. # # * delivery_method - Defines a delivery method. Possible values are :smtp (default), # :sendmail, :test, and :file. Or you may provide a custom delivery method # object e.g. +MyOwnDeliveryMethodClass+. See the Mail gem documentation on the interface you need to # implement for a custom delivery agent. # # * perform_deliveries - Determines whether emails are actually sent from Action Mailer when you # call .deliver on an email message or on an Action Mailer method. This is on by default but can # be turned off to aid in functional testing. # # * deliveries - Keeps an array of all the emails sent out through the Action Mailer with # delivery_method :test. Most useful for unit and functional testing. class Base < AbstractController::Base include DeliveryMethods include Previews abstract! include AbstractController::Rendering include AbstractController::Logger include AbstractController::Helpers include AbstractController::Translation include AbstractController::AssetPaths include AbstractController::Callbacks include ActionView::Layouts PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout] def _protected_ivars # :nodoc: PROTECTED_IVARS end helper ActionMailer::MailHelper private_class_method :new #:nodoc: class_attribute :default_params self.default_params = { mime_version: "1.0", charset: "UTF-8", content_type: "text/plain", parts_order: [ "text/plain", "text/enriched", "text/html" ] }.freeze class << self # Register one or more Observers which will be notified when mail is delivered. def register_observers(*observers) observers.flatten.compact.each { |observer| register_observer(observer) } end # Register one or more Interceptors which will be called before mail is sent. def register_interceptors(*interceptors) interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) } end # Register an Observer which will be notified when mail is delivered. # Either a class, string or symbol can be passed in as the Observer. # If a string or symbol is passed in it will be camelized and constantized. def register_observer(observer) delivery_observer = case observer when String, Symbol observer.to_s.camelize.constantize else observer end Mail.register_observer(delivery_observer) end # Register an Interceptor which will be called before mail is sent. # Either a class, string or symbol can be passed in as the Interceptor. # If a string or symbol is passed in it will be camelized and constantized. def register_interceptor(interceptor) delivery_interceptor = case interceptor when String, Symbol interceptor.to_s.camelize.constantize else interceptor end Mail.register_interceptor(delivery_interceptor) end # Returns the name of current mailer. This method is also being used as a path for a view lookup. # If this is an anonymous mailer, this method will return +anonymous+ instead. def mailer_name @mailer_name ||= anonymous? ? "anonymous" : name.underscore end # Allows to set the name of current mailer. attr_writer :mailer_name alias :controller_path :mailer_name # Sets the defaults through app configuration: # # config.action_mailer.default(from: "no-reply@example.org") # # Aliased by ::default_options= def default(value = nil) self.default_params = default_params.merge(value).freeze if value default_params end # Allows to set defaults through app configuration: # # config.action_mailer.default_options = { from: "no-reply@example.org" } alias :default_options= :default # Receives a raw email, parses it into an email object, decodes it, # instantiates a new mailer, and passes the email object to the mailer # object's +receive+ method. # # If you want your mailer to be able to process incoming messages, you'll # need to implement a +receive+ method that accepts the raw email string # as a parameter: # # class MyMailer < ActionMailer::Base # def receive(mail) # # ... # end # end def receive(raw_mail) ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload| mail = Mail.new(raw_mail) set_payload_for_mail(payload, mail) new.receive(mail) end end # Wraps an email delivery inside of ActiveSupport::Notifications instrumentation. # # This method is actually called by the Mail::Message object itself # through a callback when you call :deliver on the Mail::Message, # calling +deliver_mail+ directly and passing a Mail::Message will do # nothing except tell the logger you sent the email. def deliver_mail(mail) #:nodoc: ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload| set_payload_for_mail(payload, mail) yield # Let Mail do the delivery actions end end def respond_to?(method, include_private = false) #:nodoc: super || action_methods.include?(method.to_s) end protected def set_payload_for_mail(payload, mail) #:nodoc: payload[:mailer] = name payload[:message_id] = mail.message_id payload[:subject] = mail.subject payload[:to] = mail.to payload[:from] = mail.from payload[:bcc] = mail.bcc if mail.bcc.present? payload[:cc] = mail.cc if mail.cc.present? payload[:date] = mail.date payload[:mail] = mail.encoded end def method_missing(method_name, *args) # :nodoc: if action_methods.include?(method_name.to_s) MessageDelivery.new(self, method_name, *args) else super end end end attr_internal :message # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer # will be initialized according to the named method. If not, the mailer will # remain uninitialized (useful when you only need to invoke the "receive" # method, for instance). def initialize(method_name=nil, *args) super() @_mail_was_called = false @_message = Mail.new process(method_name, *args) if method_name end def process(method_name, *args) #:nodoc: payload = { mailer: self.class.name, action: method_name } ActiveSupport::Notifications.instrument("process.action_mailer", payload) do lookup_context.skip_default_locale! super @_message = NullMail.new unless @_mail_was_called end end class NullMail #:nodoc: def body; '' end def header; {} end def respond_to?(string, include_all=false) true end def method_missing(*args) nil end end # Returns the name of the mailer object. def mailer_name self.class.mailer_name end # Allows you to pass random and unusual headers to the new Mail::Message # object which will add them to itself. # # headers['X-Special-Domain-Specific-Header'] = "SecretValue" # # You can also pass a hash into headers of header field names and values, # which will then be set on the Mail::Message object: # # headers 'X-Special-Domain-Specific-Header' => "SecretValue", # 'In-Reply-To' => incoming.message_id # # The resulting Mail::Message will have the following in its header: # # X-Special-Domain-Specific-Header: SecretValue # # Note about replacing already defined headers: # # * +subject+ # * +sender+ # * +from+ # * +to+ # * +cc+ # * +bcc+ # * +reply-to+ # * +orig-date+ # * +message-id+ # * +references+ # # Fields can only appear once in email headers while other fields such as # X-Anything can appear multiple times. # # If you want to replace any header which already exists, first set it to # +nil+ in order to reset the value otherwise another field will be added # for the same header. def headers(args = nil) if args @_message.headers(args) else @_message end end # Allows you to add attachments to an email, like so: # # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg') # # If you do this, then Mail will take the file name and work out the mime type # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and # base64 encode the contents of the attachment all for you. # # You can also specify overrides if you want by passing a hash instead of a string: # # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip', # content: File.read('/path/to/filename.jpg')} # # If you want to use a different encoding than Base64, you can pass an encoding in, # but then it is up to you to pass in the content pre-encoded, and don't expect # Mail to know how to decode this data: # # file_content = SpecialEncode(File.read('/path/to/filename.jpg')) # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip', # encoding: 'SpecialEncoding', # content: file_content } # # You can also search for specific attachments: # # # By Filename # mail.attachments['filename.jpg'] # => Mail::Part object or nil # # # or by index # mail.attachments[0] # => Mail::Part (first attachment) # def attachments if @_mail_was_called LateAttachmentsProxy.new(@_message.attachments) else @_message.attachments end end class LateAttachmentsProxy < SimpleDelegator def inline; _raise_error end def []=(_name, _content); _raise_error end private def _raise_error raise RuntimeError, "Can't add attachments after `mail` was called.\n" \ "Make sure to use `attachments[]=` before calling `mail`." end end # The main method that creates the message and renders the email templates. There are # two ways to call this method, with a block, or without a block. # # It accepts a headers hash. This hash allows you to specify # the most used headers in an email message, these are: # # * +:subject+ - The subject of the message, if this is omitted, Action Mailer will # ask the Rails I18n class for a translated +:subject+ in the scope of # [mailer_scope, action_name] or if this is missing, will translate the # humanized version of the +action_name+ # * +:to+ - Who the message is destined for, can be a string of addresses, or an array # of addresses. # * +:from+ - Who the message is from # * +:cc+ - Who you would like to Carbon-Copy on this email, can be a string of addresses, # or an array of addresses. # * +:bcc+ - Who you would like to Blind-Carbon-Copy on this email, can be a string of # addresses, or an array of addresses. # * +:reply_to+ - Who to set the Reply-To header of the email to. # * +:date+ - The date to say the email was sent on. # # You can set default values for any of the above headers (except +:date+) # by using the ::default class method: # # class Notifier < ActionMailer::Base # default from: 'no-reply@test.lindsaar.net', # bcc: 'email_logger@test.lindsaar.net', # reply_to: 'bounces@test.lindsaar.net' # end # # If you need other headers not listed above, you can either pass them in # as part of the headers hash or use the headers['name'] = value # method. # # When a +:return_path+ is specified as header, that value will be used as # the 'envelope from' address for the Mail message. Setting this is useful # when you want delivery notifications sent to a different address than the # one in +:from+. Mail will actually use the +:return_path+ in preference # to the +:sender+ in preference to the +:from+ field for the 'envelope # from' value. # # If you do not pass a block to the +mail+ method, it will find all # templates in the view paths using by default the mailer name and the # method name that it is being called from, it will then create parts for # each of these templates intelligently, making educated guesses on correct # content type and sequence, and return a fully prepared Mail::Message # ready to call :deliver on to send. # # For example: # # class Notifier < ActionMailer::Base # default from: 'no-reply@test.lindsaar.net' # # def welcome # mail(to: 'mikel@test.lindsaar.net') # end # end # # Will look for all templates at "app/views/notifier" with name "welcome". # If no welcome template exists, it will raise an ActionView::MissingTemplate error. # # However, those can be customized: # # mail(template_path: 'notifications', template_name: 'another') # # And now it will look for all templates at "app/views/notifications" with name "another". # # If you do pass a block, you can render specific templates of your choice: # # mail(to: 'mikel@test.lindsaar.net') do |format| # format.text # format.html # end # # You can even render plain text directly without using a template: # # mail(to: 'mikel@test.lindsaar.net') do |format| # format.text { render plain: "Hello Mikel!" } # format.html { render html: "

Hello Mikel!

".html_safe } # end # # Which will render a +multipart/alternative+ email with +text/plain+ and # +text/html+ parts. # # The block syntax also allows you to customize the part headers if desired: # # mail(to: 'mikel@test.lindsaar.net') do |format| # format.text(content_transfer_encoding: "base64") # format.html # end # def mail(headers = {}, &block) return @_message if @_mail_was_called && headers.blank? && !block m = @_message # At the beginning, do not consider class default for content_type content_type = headers[:content_type] # Call all the procs (if any) default_values = {} self.class.default.each do |k,v| default_values[k] = v.is_a?(Proc) ? instance_eval(&v) : v end # Handle defaults headers = headers.reverse_merge(default_values) headers[:subject] ||= default_i18n_subject # Apply charset at the beginning so all fields are properly quoted m.charset = charset = headers[:charset] # Set configure delivery behavior wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options)) # Assign all headers except parts_order, content_type and body assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path) assignable.each { |k, v| m[k] = v } # Render the templates and blocks responses = collect_responses(headers, &block) @_mail_was_called = true create_parts_from_responses(m, responses) # Setup content type, reapply charset and handle parts order m.content_type = set_content_type(m, content_type, headers[:content_type]) m.charset = charset if m.multipart? m.body.set_sort_order(headers[:parts_order]) m.body.sort_parts! end m end protected # Used by #mail to set the content type of the message. # # It will use the given +user_content_type+, or multipart if the mail # message has any attachments. If the attachments are inline, the content # type will be "multipart/related", otherwise "multipart/mixed". # # If there is no content type passed in via headers, and there are no # attachments, or the message is multipart, then the default content type is # used. def set_content_type(m, user_content_type, class_default) params = m.content_type_parameters || {} case when user_content_type.present? user_content_type when m.has_attachments? if m.attachments.detect { |a| a.inline? } ["multipart", "related", params] else ["multipart", "mixed", params] end when m.multipart? ["multipart", "alternative", params] else m.content_type || class_default end end # Translates the +subject+ using Rails I18n class under [mailer_scope, action_name] scope. # If it does not find a translation for the +subject+ under the specified scope it will default to a # humanized version of the action_name. # If the subject has interpolations, you can pass them through the +interpolations+ parameter. def default_i18n_subject(interpolations = {}) mailer_scope = self.class.mailer_name.tr('/', '.') I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize)) end def collect_responses(headers) #:nodoc: responses = [] if block_given? collector = ActionMailer::Collector.new(lookup_context) { render(action_name) } yield(collector) responses = collector.responses elsif headers[:body] responses << { body: headers.delete(:body), content_type: self.class.default[:content_type] || "text/plain" } else templates_path = headers.delete(:template_path) || self.class.mailer_name templates_name = headers.delete(:template_name) || action_name each_template(Array(templates_path), templates_name) do |template| self.formats = template.formats responses << { body: render(template: template), content_type: template.type.to_s } end end responses end def each_template(paths, name, &block) #:nodoc: templates = lookup_context.find_all(name, paths) if templates.empty? raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer') else templates.uniq { |t| t.formats }.each(&block) end end def create_parts_from_responses(m, responses) #:nodoc: if responses.size == 1 && !m.has_attachments? responses[0].each { |k,v| m[k] = v } elsif responses.size > 1 && m.has_attachments? container = Mail::Part.new container.content_type = "multipart/alternative" responses.each { |r| insert_part(container, r, m.charset) } m.add_part(container) else responses.each { |r| insert_part(m, r, m.charset) } end end def insert_part(container, response, charset) #:nodoc: response[:charset] ||= charset part = Mail::Part.new(response) container.add_part(part) end # Emails do not support relative path links. def self.supports_path? false end ActiveSupport.run_load_hooks(:action_mailer, self) end end rails-4.2.6/actionmailer/lib/action_mailer/collector.rb000066400000000000000000000015311266740050600232010ustar00rootroot00000000000000require 'abstract_controller/collector' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/array/extract_options' module ActionMailer class Collector include AbstractController::Collector attr_reader :responses def initialize(context, &block) @context = context @responses = [] @default_render = block end def any(*args, &block) options = args.extract_options! raise ArgumentError, "You have to supply at least one format" if args.empty? args.each { |type| send(type, options.dup, &block) } end alias :all :any def custom(mime, options = {}) options.reverse_merge!(content_type: mime.to_s) @context.formats = [mime.to_sym] options[:body] = block_given? ? yield : @default_render.call @responses << options end end end rails-4.2.6/actionmailer/lib/action_mailer/delivery_job.rb000066400000000000000000000006321266740050600236710ustar00rootroot00000000000000require 'active_job' module ActionMailer # The ActionMailer::DeliveryJob class is used when you # want to send emails outside of the request-response cycle. class DeliveryJob < ActiveJob::Base # :nodoc: queue_as :mailers def perform(mailer, mail_method, delivery_method, *args) # :nodoc: mailer.constantize.public_send(mail_method, *args).send(delivery_method) end end end rails-4.2.6/actionmailer/lib/action_mailer/delivery_methods.rb000066400000000000000000000055621266740050600245710ustar00rootroot00000000000000require 'tmpdir' module ActionMailer # This module handles everything related to mail delivery, from registering # new delivery methods to configuring the mail object to be sent. module DeliveryMethods extend ActiveSupport::Concern included do class_attribute :delivery_methods, :delivery_method # Do not make this inheritable, because we always want it to propagate cattr_accessor :raise_delivery_errors self.raise_delivery_errors = true cattr_accessor :perform_deliveries self.perform_deliveries = true self.delivery_methods = {}.freeze self.delivery_method = :smtp add_delivery_method :smtp, Mail::SMTP, address: "localhost", port: 25, domain: 'localhost.localdomain', user_name: nil, password: nil, authentication: nil, enable_starttls_auto: true add_delivery_method :file, Mail::FileDelivery, location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" add_delivery_method :sendmail, Mail::Sendmail, location: '/usr/sbin/sendmail', arguments: '-i -t' add_delivery_method :test, Mail::TestMailer end # Helpers for creating and wrapping delivery behavior, used by DeliveryMethods. module ClassMethods # Provides a list of emails that have been delivered by Mail::TestMailer delegate :deliveries, :deliveries=, to: Mail::TestMailer # Adds a new delivery method through the given class using the given # symbol as alias and the default options supplied. # # add_delivery_method :sendmail, Mail::Sendmail, # location: '/usr/sbin/sendmail', # arguments: '-i -t' def add_delivery_method(symbol, klass, default_options={}) class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings") send(:"#{symbol}_settings=", default_options) self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze end def wrap_delivery_behavior(mail, method=nil, options=nil) # :nodoc: method ||= self.delivery_method mail.delivery_handler = self case method when NilClass raise "Delivery method cannot be nil" when Symbol if klass = delivery_methods[method] mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {})) else raise "Invalid delivery method #{method.inspect}" end else mail.delivery_method(method) end mail.perform_deliveries = perform_deliveries mail.raise_delivery_errors = raise_delivery_errors end end def wrap_delivery_behavior!(*args) # :nodoc: self.class.wrap_delivery_behavior(message, *args) end end end rails-4.2.6/actionmailer/lib/action_mailer/gem_version.rb000066400000000000000000000004771266740050600235400ustar00rootroot00000000000000module ActionMailer # Returns the version of the currently loaded Action Mailer as a Gem::Version def self.gem_version Gem::Version.new VERSION::STRING end module VERSION MAJOR = 4 MINOR = 2 TINY = 6 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end end rails-4.2.6/actionmailer/lib/action_mailer/inline_preview_interceptor.rb000066400000000000000000000026361266740050600266570ustar00rootroot00000000000000require 'base64' module ActionMailer # Implements a mailer preview interceptor that converts image tag src attributes # that use inline cid: style urls to data: style urls so that they are visible # when previewing a HTML email in a web browser. # # This interceptor is not enabled by default, to use it just register it like any # other mailer preview interceptor: # # ActionMailer::Base.register_preview_interceptor(ActionMailer::InlinePreviewInterceptor) # class InlinePreviewInterceptor PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i include Base64 def self.previewing_email(message) #:nodoc: new(message).transform! end def initialize(message) #:nodoc: @message = message end def transform! #:nodoc: return message if html_part.blank? html_source.gsub!(PATTERN) do |match| if part = find_part(match[9..-2]) %[src="#{data_url(part)}"] else match end end message end private def message @message end def html_part @html_part ||= message.html_part end def html_source html_part.body.raw_source end def data_url(part) "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}" end def find_part(cid) message.all_parts.find{ |p| p.attachment? && p.cid == cid } end end end rails-4.2.6/actionmailer/lib/action_mailer/log_subscriber.rb000066400000000000000000000020621266740050600242170ustar00rootroot00000000000000require 'active_support/log_subscriber' module ActionMailer # Implements the ActiveSupport::LogSubscriber for logging notifications when # email is delivered and received. class LogSubscriber < ActiveSupport::LogSubscriber # An email was delivered. def deliver(event) info do recipients = Array(event.payload[:to]).join(', ') "\nSent mail to #{recipients} (#{event.duration.round(1)}ms)" end debug { event.payload[:mail] } end # An email was received. def receive(event) info { "\nReceived mail (#{event.duration.round(1)}ms)" } debug { event.payload[:mail] } end # An email was generated. def process(event) debug do mailer = event.payload[:mailer] action = event.payload[:action] "\n#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms" end end # Use the logger configured for ActionMailer::Base def logger ActionMailer::Base.logger end end end ActionMailer::LogSubscriber.attach_to :action_mailer rails-4.2.6/actionmailer/lib/action_mailer/mail_helper.rb000066400000000000000000000031641266740050600235000ustar00rootroot00000000000000module ActionMailer # Provides helper methods for ActionMailer::Base that can be used for easily # formatting messages, accessing mailer or message instances, and the # attachments list. module MailHelper # Take the text and format it, indented two spaces for each line, and # wrapped at 72 columns. def block_format(text) formatted = text.split(/\n\r?\n/).collect { |paragraph| format_paragraph(paragraph) }.join("\n\n") # Make list points stand on their own line formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { " #{$1} #{$2.strip}\n" } formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { " #{$1} #{$2.strip}\n" } formatted end # Access the mailer instance. def mailer @_controller end # Access the message instance. def message @_message end # Access the message attachments list. def attachments mailer.attachments end # Returns +text+ wrapped at +len+ columns and indented +indent+ spaces. # # my_text = 'Here is a sample text with more than 40 characters' # # format_paragraph(my_text, 25, 4) # # => " Here is a sample text with\n more than 40 characters" def format_paragraph(text, len = 72, indent = 2) sentences = [[]] text.split.each do |word| if sentences.first.present? && (sentences.last + [word]).join(' ').length > len sentences << [word] else sentences.last << word end end indentation = " " * indent sentences.map! { |sentence| "#{indentation}#{sentence.join(' ')}" }.join "\n" end end end rails-4.2.6/actionmailer/lib/action_mailer/message_delivery.rb000066400000000000000000000075321266740050600245510ustar00rootroot00000000000000require 'delegate' require 'active_support/core_ext/string/filters' module ActionMailer # The ActionMailer::MessageDelivery class is used by # ActionMailer::Base when creating a new mailer. # MessageDelivery is a wrapper (+Delegator+ subclass) around a lazy # created Mail::Message. You can get direct access to the # Mail::Message, deliver the email or schedule the email to be sent # through Active Job. # # Notifier.welcome(User.first) # an ActionMailer::MessageDelivery object # Notifier.welcome(User.first).deliver_now # sends the email # Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job # Notifier.welcome(User.first).message # a Mail::Message object class MessageDelivery < Delegator def initialize(mailer, mail_method, *args) #:nodoc: @mailer = mailer @mail_method = mail_method @args = args end def __getobj__ #:nodoc: @obj ||= @mailer.send(:new, @mail_method, *@args).message end def __setobj__(obj) #:nodoc: @obj = obj end # Returns the Mail::Message object def message __getobj__ end # Enqueues the email to be delivered through Active Job. When the # job runs it will send the email using +deliver_now!+. That means # that the message will be sent bypassing checking +perform_deliveries+ # and +raise_delivery_errors+, so use with caution. # # Notifier.welcome(User.first).deliver_later! # Notifier.welcome(User.first).deliver_later!(wait: 1.hour) # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now) # # Options: # # * :wait - Enqueue the email to be delivered with a delay # * :wait_until - Enqueue the email to be delivered at (after) a specific date / time # * :queue - Enqueue the email on the specified queue def deliver_later!(options={}) enqueue_delivery :deliver_now!, options end # Enqueues the email to be delivered through Active Job. When the # job runs it will send the email using +deliver_now+. # # Notifier.welcome(User.first).deliver_later # Notifier.welcome(User.first).deliver_later(wait: 1.hour) # Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now) # # Options: # # * :wait - Enqueue the email to be delivered with a delay # * :wait_until - Enqueue the email to be delivered at (after) a specific date / time # * :queue - Enqueue the email on the specified queue def deliver_later(options={}) enqueue_delivery :deliver_now, options end # Delivers an email without checking +perform_deliveries+ and +raise_delivery_errors+, # so use with caution. # # Notifier.welcome(User.first).deliver_now! # def deliver_now! message.deliver! end # Delivers an email: # # Notifier.welcome(User.first).deliver_now # def deliver_now message.deliver end def deliver! #:nodoc: ActiveSupport::Deprecation.warn(<<-MSG.squish) `#deliver!` is deprecated and will be removed in Rails 5. Use `#deliver_now!` to deliver immediately or `#deliver_later!` to deliver through Active Job. MSG deliver_now! end def deliver #:nodoc: ActiveSupport::Deprecation.warn(<<-MSG.squish) `#deliver` is deprecated and will be removed in Rails 5. Use `#deliver_now` to deliver immediately or `#deliver_later` to deliver through Active Job. MSG deliver_now end private def enqueue_delivery(delivery_method, options={}) args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args ActionMailer::DeliveryJob.set(options).perform_later(*args) end end end rails-4.2.6/actionmailer/lib/action_mailer/preview.rb000066400000000000000000000066441266740050600227060ustar00rootroot00000000000000require 'active_support/descendants_tracker' module ActionMailer module Previews #:nodoc: extend ActiveSupport::Concern included do # Set the location of mailer previews through app configuration: # # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews" # mattr_accessor :preview_path, instance_writer: false # Enable or disable mailer previews through app configuration: # # config.action_mailer.show_previews = true # # Defaults to true for development environment # mattr_accessor :show_previews, instance_writer: false # :nodoc: mattr_accessor :preview_interceptors, instance_writer: false self.preview_interceptors = [] end module ClassMethods # Register one or more Interceptors which will be called before mail is previewed. def register_preview_interceptors(*interceptors) interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) } end # Register an Interceptor which will be called before mail is previewed. # Either a class or a string can be passed in as the Interceptor. If a # string is passed in it will be constantized. def register_preview_interceptor(interceptor) preview_interceptor = case interceptor when String, Symbol interceptor.to_s.camelize.constantize else interceptor end unless preview_interceptors.include?(preview_interceptor) preview_interceptors << preview_interceptor end end end end class Preview extend ActiveSupport::DescendantsTracker class << self # Returns all mailer preview classes def all load_previews if descendants.empty? descendants end # Returns the mail object for the given email name. The registered preview # interceptors will be informed so that they can transform the message # as they would if the mail was actually being delivered. def call(email) preview = self.new message = preview.public_send(email) inform_preview_interceptors(message) message end # Returns all of the available email previews def emails public_instance_methods(false).map(&:to_s).sort end # Returns true if the email exists def email_exists?(email) emails.include?(email) end # Returns true if the preview exists def exists?(preview) all.any?{ |p| p.preview_name == preview } end # Find a mailer preview by its underscored class name def find(preview) all.find{ |p| p.preview_name == preview } end # Returns the underscored name of the mailer preview without the suffix def preview_name name.sub(/Preview$/, '').underscore end protected def load_previews #:nodoc: if preview_path Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file } end end def preview_path #:nodoc: Base.preview_path end def show_previews #:nodoc: Base.show_previews end def inform_preview_interceptors(message) #:nodoc: Base.preview_interceptors.each do |interceptor| interceptor.previewing_email(message) end end end end end rails-4.2.6/actionmailer/lib/action_mailer/railtie.rb000066400000000000000000000042741266740050600226530ustar00rootroot00000000000000require 'active_job/railtie' require "action_mailer" require "rails" require "abstract_controller/railties/routes_helpers" module ActionMailer class Railtie < Rails::Railtie # :nodoc: config.action_mailer = ActiveSupport::OrderedOptions.new config.eager_load_namespaces << ActionMailer initializer "action_mailer.logger" do ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger } end initializer "action_mailer.set_configs" do |app| paths = app.config.paths options = app.config.action_mailer options.assets_dir ||= paths["public"].first options.javascripts_dir ||= paths["public/javascripts"].first options.stylesheets_dir ||= paths["public/stylesheets"].first options.show_previews = Rails.env.development? if options.show_previews.nil? if options.show_previews options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil end # make sure readers methods get compiled options.asset_host ||= app.config.asset_host options.relative_url_root ||= app.config.relative_url_root ActiveSupport.on_load(:action_mailer) do include AbstractController::UrlFor extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false) include app.routes.mounted_helpers register_interceptors(options.delete(:interceptors)) register_preview_interceptors(options.delete(:preview_interceptors)) register_observers(options.delete(:observers)) options.each { |k,v| send("#{k}=", v) } if options.show_previews app.routes.append do get '/rails/mailers' => "rails/mailers#index" get '/rails/mailers/*path' => "rails/mailers#preview" end end end end initializer "action_mailer.compile_config_methods" do ActiveSupport.on_load(:action_mailer) do config.compile_methods! if config.respond_to?(:compile_methods!) end end config.after_initialize do if ActionMailer::Base.preview_path ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path end end end end rails-4.2.6/actionmailer/lib/action_mailer/test_case.rb000066400000000000000000000055611266740050600231740ustar00rootroot00000000000000require 'active_support/test_case' require 'rails-dom-testing' module ActionMailer class NonInferrableMailerError < ::StandardError def initialize(name) super "Unable to determine the mailer to test from #{name}. " + "You'll need to specify it using tests YourMailer in your " + "test case definition" end end class TestCase < ActiveSupport::TestCase module Behavior extend ActiveSupport::Concern include ActiveSupport::Testing::ConstantLookup include TestHelper include Rails::Dom::Testing::Assertions::SelectorAssertions include Rails::Dom::Testing::Assertions::DomAssertions included do class_attribute :_mailer_class setup :initialize_test_deliveries setup :set_expected_mail teardown :restore_test_deliveries end module ClassMethods def tests(mailer) case mailer when String, Symbol self._mailer_class = mailer.to_s.camelize.constantize when Module self._mailer_class = mailer else raise NonInferrableMailerError.new(mailer) end end def mailer_class if mailer = self._mailer_class mailer else tests determine_default_mailer(name) end end def determine_default_mailer(name) mailer = determine_constant_from_test_name(name) do |constant| Class === constant && constant < ActionMailer::Base end raise NonInferrableMailerError.new(name) if mailer.nil? mailer end end protected def initialize_test_deliveries set_delivery_method :test @old_perform_deliveries = ActionMailer::Base.perform_deliveries ActionMailer::Base.perform_deliveries = true end def restore_test_deliveries restore_delivery_method ActionMailer::Base.perform_deliveries = @old_perform_deliveries ActionMailer::Base.deliveries.clear end def set_delivery_method(method) @old_delivery_method = ActionMailer::Base.delivery_method ActionMailer::Base.delivery_method = method end def restore_delivery_method ActionMailer::Base.delivery_method = @old_delivery_method end def set_expected_mail @expected = Mail.new @expected.content_type ["text", "plain", { "charset" => charset }] @expected.mime_version = '1.0' end private def charset "UTF-8" end def encode(subject) Mail::Encodings.q_value_encode(subject, charset) end def read_fixture(action) IO.readlines(File.join(Rails.root, 'test', 'fixtures', self.class.mailer_class.name.underscore, action)) end end include Behavior end end rails-4.2.6/actionmailer/lib/action_mailer/test_helper.rb000066400000000000000000000034101266740050600235270ustar00rootroot00000000000000module ActionMailer # Provides helper methods for testing Action Mailer, including #assert_emails # and #assert_no_emails module TestHelper # Asserts that the number of emails sent matches the given number. # # def test_emails # assert_emails 0 # ContactMailer.welcome.deliver_now # assert_emails 1 # ContactMailer.welcome.deliver_now # assert_emails 2 # end # # If a block is passed, that block should cause the specified number of # emails to be sent. # # def test_emails_again # assert_emails 1 do # ContactMailer.welcome.deliver_now # end # # assert_emails 2 do # ContactMailer.welcome.deliver_now # ContactMailer.welcome.deliver_now # end # end def assert_emails(number) if block_given? original_count = ActionMailer::Base.deliveries.size yield new_count = ActionMailer::Base.deliveries.size assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent" else assert_equal number, ActionMailer::Base.deliveries.size end end # Assert that no emails have been sent. # # def test_emails # assert_no_emails # ContactMailer.welcome.deliver_now # assert_emails 1 # end # # If a block is passed, that block should not cause any emails to be sent. # # def test_emails_again # assert_no_emails do # # No emails should be sent from this block # end # end # # Note: This assertion is simply a shortcut for: # # assert_emails 0 def assert_no_emails(&block) assert_emails 0, &block end end end rails-4.2.6/actionmailer/lib/action_mailer/version.rb000066400000000000000000000002771266740050600227060ustar00rootroot00000000000000require_relative 'gem_version' module ActionMailer # Returns the version of the currently loaded Action Mailer as a # Gem::Version. def self.version gem_version end end rails-4.2.6/actionmailer/lib/rails/000077500000000000000000000000001266740050600171725ustar00rootroot00000000000000rails-4.2.6/actionmailer/lib/rails/generators/000077500000000000000000000000001266740050600213435ustar00rootroot00000000000000rails-4.2.6/actionmailer/lib/rails/generators/mailer/000077500000000000000000000000001266740050600226145ustar00rootroot00000000000000rails-4.2.6/actionmailer/lib/rails/generators/mailer/USAGE000066400000000000000000000011471266740050600234060ustar00rootroot00000000000000Description: ============ Stubs out a new mailer and its views. Passes the mailer name, either CamelCased or under_scored, and an optional list of emails as arguments. This generates a mailer class in app/mailers and invokes your template engine and test framework generators. Example: ======== rails generate mailer Notifications signup forgot_password invoice creates a Notifications mailer class, views, and test: Mailer: app/mailers/notifications.rb Views: app/views/notifications/signup.text.erb [...] Test: test/mailers/notifications_test.rb rails-4.2.6/actionmailer/lib/rails/generators/mailer/mailer_generator.rb000066400000000000000000000010571266740050600264630ustar00rootroot00000000000000module Rails module Generators class MailerGenerator < NamedBase source_root File.expand_path("../templates", __FILE__) argument :actions, type: :array, default: [], banner: "method method" check_class_collision def create_mailer_file template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}.rb") if self.behavior == :invoke template "application_mailer.rb", 'app/mailers/application_mailer.rb' end end hook_for :template_engine, :test_framework end end end rails-4.2.6/actionmailer/lib/rails/generators/mailer/templates/000077500000000000000000000000001266740050600246125ustar00rootroot00000000000000rails-4.2.6/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb000066400000000000000000000001461266740050600307740ustar00rootroot00000000000000class ApplicationMailer < ActionMailer::Base default from: "from@example.com" layout 'mailer' end rails-4.2.6/actionmailer/lib/rails/generators/mailer/templates/mailer.rb000066400000000000000000000005671266740050600264200ustar00rootroot00000000000000<% module_namespacing do -%> class <%= class_name %> < ApplicationMailer <% actions.each do |action| -%> # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.<%= file_path.tr("/",".") %>.<%= action %>.subject # def <%= action %> @greeting = "Hi" mail to: "to@example.org" end <% end -%> end <% end -%> rails-4.2.6/actionmailer/test/000077500000000000000000000000001266740050600162715ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/abstract_unit.rb000066400000000000000000000027411266740050600214640ustar00rootroot00000000000000require File.expand_path('../../../load_paths', __FILE__) require 'active_support/core_ext/kernel/reporting' # These are the normal settings that will be set up by Railties # TODO: Have these tests support other combinations of these values silence_warnings do Encoding.default_internal = "UTF-8" Encoding.default_external = "UTF-8" end require 'active_support/testing/autorun' require 'action_mailer' require 'action_mailer/test_case' require 'mail' # Emulate AV railtie require 'action_view' ActionMailer::Base.send(:include, ActionView::Layouts) # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__)) ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH module Rails def self.root File.expand_path('../', File.dirname(__FILE__)) end end # Skips the current run on Rubinius using Minitest::Assertions#skip def rubinius_skip(message = '') skip message if RUBY_ENGINE == 'rbx' end # Skips the current run on JRuby using Minitest::Assertions#skip def jruby_skip(message = '') skip message if defined?(JRUBY_VERSION) end require 'mocha/setup' # FIXME: stop using mocha # FIXME: we have tests that depend on run order, we should fix that and # remove this method call. require 'active_support/test_case' ActiveSupport::TestCase.test_order = :sorted rails-4.2.6/actionmailer/test/assert_select_email_test.rb000066400000000000000000000024731266740050600236720ustar00rootroot00000000000000require 'abstract_unit' class AssertSelectEmailTest < ActionMailer::TestCase class AssertSelectMailer < ActionMailer::Base def test(html) mail body: html, content_type: "text/html", subject: "Test e-mail", from: "test@test.host", to: "test " end end class AssertMultipartSelectMailer < ActionMailer::Base def test(options) mail subject: "Test e-mail", from: "test@test.host", to: "test " do |format| format.text { render text: options[:text] } format.html { render text: options[:html] } end end end # # Test assert_select_email # def test_assert_select_email assert_raise ActiveSupport::TestCase::Assertion do assert_select_email {} end AssertSelectMailer.test("

foo

bar

").deliver_now assert_select_email do assert_select "div:root" do assert_select "p:first-child", "foo" assert_select "p:last-child", "bar" end end end def test_assert_select_email_multipart AssertMultipartSelectMailer.test(html: "

foo

bar

", text: 'foo bar').deliver_now assert_select_email do assert_select "div:root" do assert_select "p:first-child", "foo" assert_select "p:last-child", "bar" end end end end rails-4.2.6/actionmailer/test/asset_host_test.rb000066400000000000000000000020321266740050600220260ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller' class AssetHostMailer < ActionMailer::Base def email_with_asset mail to: 'test@localhost', subject: 'testing email containing asset path while asset_host is set', from: 'tester@example.com' end end class AssetHostTest < ActionMailer::TestCase def setup AssetHostMailer.configure do |c| c.asset_host = "http://www.example.com" end end def teardown restore_delivery_method end def test_asset_host_as_string mail = AssetHostMailer.email_with_asset assert_dom_equal %Q{Somelogo}, mail.body.to_s.strip end def test_asset_host_as_one_argument_proc AssetHostMailer.config.asset_host = Proc.new { |source| if source.starts_with?('/images') 'http://images.example.com' end } mail = AssetHostMailer.email_with_asset assert_dom_equal %Q{Somelogo}, mail.body.to_s.strip end end rails-4.2.6/actionmailer/test/base_test.rb000066400000000000000000001004361266740050600205730ustar00rootroot00000000000000# encoding: utf-8 require 'abstract_unit' require 'set' require 'action_dispatch' require 'active_support/time' require 'mailers/base_mailer' require 'mailers/proc_mailer' require 'mailers/asset_mailer' class BaseTest < ActiveSupport::TestCase include Rails::Dom::Testing::Assertions::DomAssertions setup do @original_delivery_method = ActionMailer::Base.delivery_method ActionMailer::Base.delivery_method = :test @original_asset_host = ActionMailer::Base.asset_host @original_assets_dir = ActionMailer::Base.assets_dir end teardown do ActionMailer::Base.asset_host = @original_asset_host ActionMailer::Base.assets_dir = @original_assets_dir BaseMailer.deliveries.clear ActionMailer::Base.delivery_method = @original_delivery_method end test "method call to mail does not raise error" do assert_nothing_raised { BaseMailer.welcome } end # Basic mail usage without block test "mail() should set the headers of the mail message" do email = BaseMailer.welcome assert_equal(['system@test.lindsaar.net'], email.to) assert_equal(['jose@test.plataformatec.com'], email.from) assert_equal('The first email on new API!', email.subject) end test "mail() with from overwrites the class level default" do email = BaseMailer.welcome(from: 'someone@example.com', to: 'another@example.org') assert_equal(['someone@example.com'], email.from) assert_equal(['another@example.org'], email.to) end test "mail() with bcc, cc, content_type, charset, mime_version, reply_to and date" do time = Time.now.beginning_of_day.to_datetime email = BaseMailer.welcome(bcc: 'bcc@test.lindsaar.net', cc: 'cc@test.lindsaar.net', content_type: 'multipart/mixed', charset: 'iso-8559-1', mime_version: '2.0', reply_to: 'reply-to@test.lindsaar.net', date: time) assert_equal(['bcc@test.lindsaar.net'], email.bcc) assert_equal(['cc@test.lindsaar.net'], email.cc) assert_equal('multipart/mixed; charset=iso-8559-1', email.content_type) assert_equal('iso-8559-1', email.charset) assert_equal('2.0', email.mime_version) assert_equal(['reply-to@test.lindsaar.net'], email.reply_to) assert_equal(time, email.date) end test "mail() renders the template using the method being processed" do email = BaseMailer.welcome assert_equal("Welcome", email.body.encoded) end test "can pass in :body to the mail method hash" do email = BaseMailer.welcome(body: "Hello there") assert_equal("text/plain", email.mime_type) assert_equal("Hello there", email.body.encoded) end test "should set template content type if mail has only one part" do mail = BaseMailer.html_only assert_equal('text/html', mail.mime_type) mail = BaseMailer.plain_text_only assert_equal('text/plain', mail.mime_type) end # Custom headers test "custom headers" do email = BaseMailer.welcome assert_equal("Not SPAM", email['X-SPAM'].decoded) end test "can pass random headers in as a hash to mail" do hash = {'X-Special-Domain-Specific-Header' => "SecretValue", 'In-Reply-To' => '1234@mikel.me.com' } mail = BaseMailer.welcome(hash) assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded) assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded) end test "can pass random headers in as a hash to headers" do hash = {'X-Special-Domain-Specific-Header' => "SecretValue", 'In-Reply-To' => '1234@mikel.me.com' } mail = BaseMailer.welcome_with_headers(hash) assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded) assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded) end # Attachments test "attachment with content" do email = BaseMailer.attachment_with_content assert_equal(1, email.attachments.length) assert_equal('invoice.pdf', email.attachments[0].filename) assert_equal('This is test File content', email.attachments['invoice.pdf'].decoded) end test "attachment gets content type from filename" do email = BaseMailer.attachment_with_content assert_equal('invoice.pdf', email.attachments[0].filename) assert_equal('application/pdf', email.attachments[0].mime_type) end test "attachment with hash" do email = BaseMailer.attachment_with_hash assert_equal(1, email.attachments.length) assert_equal('invoice.jpg', email.attachments[0].filename) expected = "\312\213\254\232)b" expected.force_encoding(Encoding::BINARY) assert_equal expected, email.attachments['invoice.jpg'].decoded end test "attachment with hash using default mail encoding" do email = BaseMailer.attachment_with_hash_default_encoding assert_equal(1, email.attachments.length) assert_equal('invoice.jpg', email.attachments[0].filename) expected = "\312\213\254\232)b" expected.force_encoding(Encoding::BINARY) assert_equal expected, email.attachments['invoice.jpg'].decoded end test "sets mime type to multipart/mixed when attachment is included" do email = BaseMailer.attachment_with_content assert_equal(1, email.attachments.length) assert_equal("multipart/mixed", email.mime_type) end test "adds the rendered template as part" do email = BaseMailer.attachment_with_content assert_equal(2, email.parts.length) assert_equal("multipart/mixed", email.mime_type) assert_equal("text/html", email.parts[0].mime_type) assert_equal("Attachment with content", email.parts[0].body.encoded) assert_equal("application/pdf", email.parts[1].mime_type) assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded) end test "adds the given :body as part" do email = BaseMailer.attachment_with_content(body: "I'm the eggman") assert_equal(2, email.parts.length) assert_equal("multipart/mixed", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("I'm the eggman", email.parts[0].body.encoded) assert_equal("application/pdf", email.parts[1].mime_type) assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded) end test "can embed an inline attachment" do email = BaseMailer.inline_attachment # Need to call #encoded to force the JIT sort on parts email.encoded assert_equal(2, email.parts.length) assert_equal("multipart/related", email.mime_type) assert_equal("multipart/alternative", email.parts[0].mime_type) assert_equal("text/plain", email.parts[0].parts[0].mime_type) assert_equal("text/html", email.parts[0].parts[1].mime_type) assert_equal("logo.png", email.parts[1].filename) end # Defaults values test "uses default charset from class" do with_default BaseMailer, charset: "US-ASCII" do email = BaseMailer.welcome assert_equal("US-ASCII", email.charset) email = BaseMailer.welcome(charset: "iso-8559-1") assert_equal("iso-8559-1", email.charset) end end test "uses default content type from class" do with_default BaseMailer, content_type: "text/html" do email = BaseMailer.welcome assert_equal("text/html", email.mime_type) email = BaseMailer.welcome(content_type: "text/plain") assert_equal("text/plain", email.mime_type) end end test "uses default mime version from class" do with_default BaseMailer, mime_version: "2.0" do email = BaseMailer.welcome assert_equal("2.0", email.mime_version) email = BaseMailer.welcome(mime_version: "1.0") assert_equal("1.0", email.mime_version) end end test "uses random default headers from class" do with_default BaseMailer, "X-Custom" => "Custom" do email = BaseMailer.welcome assert_equal("Custom", email["X-Custom"].decoded) end end test "subject gets default from I18n" do with_default BaseMailer, subject: nil do email = BaseMailer.welcome(subject: nil) assert_equal "Welcome", email.subject with_translation 'en', base_mailer: {welcome: {subject: "New Subject!"}} do email = BaseMailer.welcome(subject: nil) assert_equal "New Subject!", email.subject end end end test 'default subject can have interpolations' do with_translation 'en', base_mailer: {with_subject_interpolations: {subject: 'Will the real %{rapper_or_impersonator} please stand up?'}} do email = BaseMailer.with_subject_interpolations assert_equal 'Will the real Slim Shady please stand up?', email.subject end end test "translations are scoped properly" do with_translation 'en', base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}} do email = BaseMailer.email_with_translations assert_equal 'Hello lifo!', email.body.encoded end end test "adding attachments after mail was called raises exception" do class LateAttachmentMailer < ActionMailer::Base def welcome mail body: "yay", from: "welcome@example.com", to: "to@example.com" attachments['invoice.pdf'] = 'This is test File content' end end e = assert_raises(RuntimeError) { LateAttachmentMailer.welcome.message } assert_match(/Can't add attachments after `mail` was called./, e.message) end test "adding inline attachments after mail was called raises exception" do class LateInlineAttachmentMailer < ActionMailer::Base def welcome mail body: "yay", from: "welcome@example.com", to: "to@example.com" attachments.inline['invoice.pdf'] = 'This is test File content' end end e = assert_raises(RuntimeError) { LateInlineAttachmentMailer.welcome.message } assert_match(/Can't add attachments after `mail` was called./, e.message) end test "adding inline attachments while rendering mail works" do class LateInlineAttachmentMailer < ActionMailer::Base def on_render mail from: "welcome@example.com", to: "to@example.com" end end mail = LateInlineAttachmentMailer.on_render assert_nothing_raised { mail.message } assert_equal ["image/jpeg; filename=controller_attachments.jpg", "image/jpeg; filename=attachments.jpg"], mail.attachments.inline.map {|a| a['Content-Type'].to_s } end test "accessing attachments works after mail was called" do class LateAttachmentAccessorMailer < ActionMailer::Base def welcome attachments['invoice.pdf'] = 'This is test File content' mail body: "yay", from: "welcome@example.com", to: "to@example.com" unless attachments.map(&:filename) == ["invoice.pdf"] raise Minitest::Assertion, "Should allow access to attachments" end end end assert_nothing_raised { LateAttachmentAccessorMailer.welcome } end # Implicit multipart test "implicit multipart" do email = BaseMailer.implicit_multipart assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("TEXT Implicit Multipart", email.parts[0].body.encoded) assert_equal("text/html", email.parts[1].mime_type) assert_equal("HTML Implicit Multipart", email.parts[1].body.encoded) end test "implicit multipart with sort order" do order = ["text/html", "text/plain"] with_default BaseMailer, parts_order: order do email = BaseMailer.implicit_multipart assert_equal("text/html", email.parts[0].mime_type) assert_equal("text/plain", email.parts[1].mime_type) email = BaseMailer.implicit_multipart(parts_order: order.reverse) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("text/html", email.parts[1].mime_type) end end test "implicit multipart with attachments creates nested parts" do email = BaseMailer.implicit_multipart(attachments: true) assert_equal("application/pdf", email.parts[0].mime_type) assert_equal("multipart/alternative", email.parts[1].mime_type) assert_equal("text/plain", email.parts[1].parts[0].mime_type) assert_equal("TEXT Implicit Multipart", email.parts[1].parts[0].body.encoded) assert_equal("text/html", email.parts[1].parts[1].mime_type) assert_equal("HTML Implicit Multipart", email.parts[1].parts[1].body.encoded) end test "implicit multipart with attachments and sort order" do order = ["text/html", "text/plain"] with_default BaseMailer, parts_order: order do email = BaseMailer.implicit_multipart(attachments: true) assert_equal("application/pdf", email.parts[0].mime_type) assert_equal("multipart/alternative", email.parts[1].mime_type) assert_equal("text/plain", email.parts[1].parts[1].mime_type) assert_equal("text/html", email.parts[1].parts[0].mime_type) end end test "implicit multipart with default locale" do email = BaseMailer.implicit_with_locale assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("Implicit with locale TEXT", email.parts[0].body.encoded) assert_equal("text/html", email.parts[1].mime_type) assert_equal("Implicit with locale EN HTML", email.parts[1].body.encoded) end test "implicit multipart with other locale" do swap I18n, locale: :pl do email = BaseMailer.implicit_with_locale assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("Implicit with locale PL TEXT", email.parts[0].body.encoded) assert_equal("text/html", email.parts[1].mime_type) assert_equal("Implicit with locale HTML", email.parts[1].body.encoded) end end test "implicit multipart with several view paths uses the first one with template" do old = BaseMailer.view_paths begin BaseMailer.view_paths = [File.join(FIXTURE_LOAD_PATH, "another.path")] + old.dup email = BaseMailer.welcome assert_equal("Welcome from another path", email.body.encoded) ensure BaseMailer.view_paths = old end end test "implicit multipart with inexistent templates uses the next view path" do old = BaseMailer.view_paths begin BaseMailer.view_paths = [File.join(FIXTURE_LOAD_PATH, "unknown")] + old.dup email = BaseMailer.welcome assert_equal("Welcome", email.body.encoded) ensure BaseMailer.view_paths = old end end # Explicit multipart test "explicit multipart" do email = BaseMailer.explicit_multipart assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("TEXT Explicit Multipart", email.parts[0].body.encoded) assert_equal("text/html", email.parts[1].mime_type) assert_equal("HTML Explicit Multipart", email.parts[1].body.encoded) end test "explicit multipart have a boundary" do mail = BaseMailer.explicit_multipart assert_not_nil(mail.content_type_parameters[:boundary]) end test "explicit multipart with attachments creates nested parts" do email = BaseMailer.explicit_multipart(attachments: true) assert_equal("application/pdf", email.parts[0].mime_type) assert_equal("multipart/alternative", email.parts[1].mime_type) assert_equal("text/plain", email.parts[1].parts[0].mime_type) assert_equal("TEXT Explicit Multipart", email.parts[1].parts[0].body.encoded) assert_equal("text/html", email.parts[1].parts[1].mime_type) assert_equal("HTML Explicit Multipart", email.parts[1].parts[1].body.encoded) end test "explicit multipart with templates" do email = BaseMailer.explicit_multipart_templates assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("TEXT Explicit Multipart Templates", email.parts[0].body.encoded) assert_equal("text/html", email.parts[1].mime_type) assert_equal("HTML Explicit Multipart Templates", email.parts[1].body.encoded) end test "explicit multipart with format.any" do email = BaseMailer.explicit_multipart_with_any assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("Format with any!", email.parts[0].body.encoded) assert_equal("text/html", email.parts[1].mime_type) assert_equal("Format with any!", email.parts[1].body.encoded) end test "explicit multipart with format(Hash)" do email = BaseMailer.explicit_multipart_with_options(true) email.ready_to_send! assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("base64", email.parts[0].content_transfer_encoding) assert_equal("text/html", email.parts[1].mime_type) assert_equal("7bit", email.parts[1].content_transfer_encoding) end test "explicit multipart with one part is rendered as body and options are merged" do email = BaseMailer.explicit_multipart_with_options assert_equal(0, email.parts.size) assert_equal("text/plain", email.mime_type) assert_equal("base64", email.content_transfer_encoding) end test "explicit multipart with one template has the expected format" do email = BaseMailer.explicit_multipart_with_one_template assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("[:text]", email.parts[0].body.encoded) assert_equal("text/html", email.parts[1].mime_type) assert_equal("[:html]", email.parts[1].body.encoded) end test "explicit multipart with sort order" do order = ["text/html", "text/plain"] with_default BaseMailer, parts_order: order do email = BaseMailer.explicit_multipart assert_equal("text/html", email.parts[0].mime_type) assert_equal("text/plain", email.parts[1].mime_type) email = BaseMailer.explicit_multipart(parts_order: order.reverse) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("text/html", email.parts[1].mime_type) end end # Class level API with method missing test "should respond to action methods" do assert_respond_to BaseMailer, :welcome assert_respond_to BaseMailer, :implicit_multipart assert !BaseMailer.respond_to?(:mail) assert !BaseMailer.respond_to?(:headers) end test "calling just the action should return the generated mail object" do email = BaseMailer.welcome assert_equal(0, BaseMailer.deliveries.length) assert_equal('The first email on new API!', email.subject) end test "calling deliver on the action should deliver the mail object" do BaseMailer.expects(:deliver_mail).once mail = BaseMailer.welcome.deliver_now assert_equal 'The first email on new API!', mail.subject end test "calling deliver on the action should increment the deliveries collection if using the test mailer" do BaseMailer.welcome.deliver_now assert_equal(1, BaseMailer.deliveries.length) end test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do mail = Mail::Message.new mail.expects(:do_delivery).once BaseMailer.expects(:welcome).returns(mail) BaseMailer.welcome.deliver end # Rendering test "you can specify a different template for implicit render" do mail = BaseMailer.implicit_different_template('implicit_multipart').deliver_now assert_equal("HTML Implicit Multipart", mail.html_part.body.decoded) assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded) end test "should raise if missing template in implicit render" do assert_raises ActionView::MissingTemplate do BaseMailer.implicit_different_template('missing_template').deliver_now end assert_equal(0, BaseMailer.deliveries.length) end test "you can specify a different template for explicit render" do mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver_now assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded) assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded) end test "you can specify a different layout" do mail = BaseMailer.different_layout('different_layout').deliver_now assert_equal("HTML -- HTML", mail.html_part.body.decoded) assert_equal("PLAIN -- PLAIN", mail.text_part.body.decoded) end test "you can specify the template path for implicit lookup" do mail = BaseMailer.welcome_from_another_path('another.path/base_mailer').deliver_now assert_equal("Welcome from another path", mail.body.encoded) mail = BaseMailer.welcome_from_another_path(['unknown/invalid', 'another.path/base_mailer']).deliver_now assert_equal("Welcome from another path", mail.body.encoded) end test "assets tags should use ActionMailer's asset_host settings" do ActionMailer::Base.config.asset_host = "http://global.com" ActionMailer::Base.config.assets_dir = "global/" mail = AssetMailer.welcome assert_dom_equal(%{Dummy}, mail.body.to_s.strip) end test "assets tags should use a Mailer's asset_host settings when available" do ActionMailer::Base.config.asset_host = "http://global.com" ActionMailer::Base.config.assets_dir = "global/" TempAssetMailer = Class.new(AssetMailer) do self.mailer_name = "asset_mailer" self.asset_host = "http://local.com" end mail = TempAssetMailer.welcome assert_dom_equal(%{Dummy}, mail.body.to_s.strip) end test 'the view is not rendered when mail was never called' do mail = BaseMailer.without_mail_call assert_equal('', mail.body.to_s.strip) mail.deliver_now end test 'the return value of mailer methods is not relevant' do mail = BaseMailer.with_nil_as_return_value assert_equal('Welcome', mail.body.to_s.strip) mail.deliver_now end # Before and After hooks class MyObserver def self.delivered_email(mail) end end class MySecondObserver def self.delivered_email(mail) end end test "you can register an observer to the mail object that gets informed on email delivery" do mail_side_effects do ActionMailer::Base.register_observer(MyObserver) mail = BaseMailer.welcome MyObserver.expects(:delivered_email).with(mail) mail.deliver_now end end test "you can register an observer using its stringified name to the mail object that gets informed on email delivery" do mail_side_effects do ActionMailer::Base.register_observer("BaseTest::MyObserver") mail = BaseMailer.welcome MyObserver.expects(:delivered_email).with(mail) mail.deliver_now end end test "you can register an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do mail_side_effects do ActionMailer::Base.register_observer(:"base_test/my_observer") mail = BaseMailer.welcome MyObserver.expects(:delivered_email).with(mail) mail.deliver_now end end test "you can register multiple observers to the mail object that both get informed on email delivery" do mail_side_effects do ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver) mail = BaseMailer.welcome MyObserver.expects(:delivered_email).with(mail) MySecondObserver.expects(:delivered_email).with(mail) mail.deliver_now end end class MyInterceptor def self.delivering_email(mail); end def self.previewing_email(mail); end end class MySecondInterceptor def self.delivering_email(mail); end def self.previewing_email(mail); end end test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do mail_side_effects do ActionMailer::Base.register_interceptor(MyInterceptor) mail = BaseMailer.welcome MyInterceptor.expects(:delivering_email).with(mail) mail.deliver_now end end test "you can register an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do mail_side_effects do ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor") mail = BaseMailer.welcome MyInterceptor.expects(:delivering_email).with(mail) mail.deliver_now end end test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do mail_side_effects do ActionMailer::Base.register_interceptor(:"base_test/my_interceptor") mail = BaseMailer.welcome MyInterceptor.expects(:delivering_email).with(mail) mail.deliver_now end end test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do mail_side_effects do ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor) mail = BaseMailer.welcome MyInterceptor.expects(:delivering_email).with(mail) MySecondInterceptor.expects(:delivering_email).with(mail) mail.deliver_now end end test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do mail1 = ProcMailer.welcome['X-Proc-Method'] yesterday = 1.day.ago Time.stubs(:now).returns(yesterday) mail2 = ProcMailer.welcome['X-Proc-Method'] assert(mail1.to_s.to_i > mail2.to_s.to_i) end test 'default values which have to_proc (e.g. symbols) should not be considered procs' do assert(ProcMailer.welcome['x-has-to-proc'].to_s == 'symbol') end test "we can call other defined methods on the class as needed" do mail = ProcMailer.welcome assert_equal("Thanks for signing up this afternoon", mail.subject) end test "modifying the mail message with a before_action" do class BeforeActionMailer < ActionMailer::Base before_action :add_special_header! def welcome ; mail ; end private def add_special_header! headers('X-Special-Header' => 'Wow, so special') end end assert_equal('Wow, so special', BeforeActionMailer.welcome['X-Special-Header'].to_s) end test "modifying the mail message with an after_action" do class AfterActionMailer < ActionMailer::Base after_action :add_special_header! def welcome ; mail ; end private def add_special_header! headers('X-Special-Header' => 'Testing') end end assert_equal('Testing', AfterActionMailer.welcome['X-Special-Header'].to_s) end test "adding an inline attachment using a before_action" do class DefaultInlineAttachmentMailer < ActionMailer::Base before_action :add_inline_attachment! def welcome ; mail ; end private def add_inline_attachment! attachments.inline["footer.jpg"] = 'hey there' end end mail = DefaultInlineAttachmentMailer.welcome assert_equal('image/jpeg; filename=footer.jpg', mail.attachments.inline.first['Content-Type'].to_s) end test "action methods should be refreshed after defining new method" do class FooMailer < ActionMailer::Base # this triggers action_methods self.respond_to?(:foo) def notify end end assert_equal Set.new(["notify"]), FooMailer.action_methods end test "mailer can be anonymous" do mailer = Class.new(ActionMailer::Base) do def welcome mail end end assert_equal "anonymous", mailer.mailer_name assert_equal "Welcome", mailer.welcome.subject assert_equal "Anonymous mailer body", mailer.welcome.body.encoded.strip end test "default_from can be set" do class DefaultFromMailer < ActionMailer::Base default to: 'system@test.lindsaar.net' self.default_options = {from: "robert.pankowecki@gmail.com"} def welcome mail(subject: "subject", body: "hello world") end end assert_equal ["robert.pankowecki@gmail.com"], DefaultFromMailer.welcome.from end test "mail() without arguments serves as getter for the current mail message" do class MailerWithCallback < ActionMailer::Base after_action :a_callback def welcome headers('X-Special-Header' => 'special indeed!') mail subject: "subject", body: "hello world", to: ["joe@example.com"] end def a_callback mail.to << "jane@example.com" end end mail = MailerWithCallback.welcome assert_equal "subject", mail.subject assert_equal ["joe@example.com", "jane@example.com"], mail.to assert_equal "hello world", mail.body.encoded.strip assert_equal "special indeed!", mail["X-Special-Header"].to_s end protected # Execute the block setting the given values and restoring old values after # the block is executed. def swap(klass, new_values) old_values = {} new_values.each do |key, value| old_values[key] = klass.send key klass.send :"#{key}=", value end yield ensure old_values.each do |key, value| klass.send :"#{key}=", value end end def with_default(klass, new_values) old = klass.default_params klass.default(new_values) yield ensure klass.default_params = old end # A simple hack to restore the observers and interceptors for Mail, as it # does not have an unregister API yet. def mail_side_effects old_observers = Mail.class_variable_get(:@@delivery_notification_observers) old_delivery_interceptors = Mail.class_variable_get(:@@delivery_interceptors) yield ensure Mail.class_variable_set(:@@delivery_notification_observers, old_observers) Mail.class_variable_set(:@@delivery_interceptors, old_delivery_interceptors) end def with_translation(locale, data) I18n.backend.store_translations(locale, data) yield ensure I18n.backend.reload! end end class BasePreviewInterceptorsTest < ActiveSupport::TestCase teardown do ActionMailer::Base.preview_interceptors.clear end class BaseMailerPreview < ActionMailer::Preview def welcome BaseMailer.welcome end end class MyInterceptor def self.delivering_email(mail); end def self.previewing_email(mail); end end class MySecondInterceptor def self.delivering_email(mail); end def self.previewing_email(mail); end end test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do ActionMailer::Base.register_preview_interceptor(MyInterceptor) mail = BaseMailer.welcome BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) MyInterceptor.expects(:previewing_email).with(mail) BaseMailerPreview.call(:welcome) end test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do ActionMailer::Base.register_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor") mail = BaseMailer.welcome BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) MyInterceptor.expects(:previewing_email).with(mail) BaseMailerPreview.call(:welcome) end test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do ActionMailer::Base.register_preview_interceptor(:"base_preview_interceptors_test/my_interceptor") mail = BaseMailer.welcome BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) MyInterceptor.expects(:previewing_email).with(mail) BaseMailerPreview.call(:welcome) end test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do ActionMailer::Base.register_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor) mail = BaseMailer.welcome BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) MyInterceptor.expects(:previewing_email).with(mail) MySecondInterceptor.expects(:previewing_email).with(mail) BaseMailerPreview.call(:welcome) end end rails-4.2.6/actionmailer/test/delivery_methods_test.rb000066400000000000000000000170261266740050600232310ustar00rootroot00000000000000require 'abstract_unit' require 'mail' class MyCustomDelivery end class MyOptionedDelivery attr_reader :options def initialize(options) @options = options end end class BogusDelivery def initialize(*) end def deliver!(mail) raise "failed" end end class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase test "default smtp settings" do settings = { address: "localhost", port: 25, domain: 'localhost.localdomain', user_name: nil, password: nil, authentication: nil, enable_starttls_auto: true } assert_equal settings, ActionMailer::Base.smtp_settings end test "default file delivery settings" do settings = {location: "#{Dir.tmpdir}/mails"} assert_equal settings, ActionMailer::Base.file_settings end test "default sendmail settings" do settings = { location: '/usr/sbin/sendmail', arguments: '-i -t' } assert_equal settings, ActionMailer::Base.sendmail_settings end end class CustomDeliveryMethodsTest < ActiveSupport::TestCase setup do @old_delivery_method = ActionMailer::Base.delivery_method ActionMailer::Base.add_delivery_method :custom, MyCustomDelivery end teardown do ActionMailer::Base.delivery_method = @old_delivery_method new = ActionMailer::Base.delivery_methods.dup new.delete(:custom) ActionMailer::Base.delivery_methods = new end test "allow to add custom delivery method" do ActionMailer::Base.delivery_method = :custom assert_equal :custom, ActionMailer::Base.delivery_method end test "allow to customize custom settings" do ActionMailer::Base.custom_settings = { foo: :bar } assert_equal Hash[foo: :bar], ActionMailer::Base.custom_settings end test "respond to custom settings" do assert_respond_to ActionMailer::Base, :custom_settings assert_respond_to ActionMailer::Base, :custom_settings= end test "does not respond to unknown settings" do assert_raise NoMethodError do ActionMailer::Base.another_settings end end end class MailDeliveryTest < ActiveSupport::TestCase class DeliveryMailer < ActionMailer::Base DEFAULT_HEADERS = { to: 'mikel@test.lindsaar.net', from: 'jose@test.plataformatec.com' } def welcome(hash={}) mail(DEFAULT_HEADERS.merge(hash)) end end setup do @old_delivery_method = DeliveryMailer.delivery_method end teardown do DeliveryMailer.delivery_method = @old_delivery_method DeliveryMailer.deliveries.clear end test "ActionMailer should be told when Mail gets delivered" do DeliveryMailer.expects(:deliver_mail).once DeliveryMailer.welcome.deliver_now end test "delivery method can be customized per instance" do Mail::SMTP.any_instance.expects(:deliver!) email = DeliveryMailer.welcome.deliver_now assert_instance_of Mail::SMTP, email.delivery_method email = DeliveryMailer.welcome(delivery_method: :test).deliver_now assert_instance_of Mail::TestMailer, email.delivery_method end test "delivery method can be customized in subclasses not changing the parent" do DeliveryMailer.delivery_method = :test assert_equal :smtp, ActionMailer::Base.delivery_method email = DeliveryMailer.welcome.deliver_now assert_instance_of Mail::TestMailer, email.delivery_method end test "delivery method options default to class level options" do default_options = {a: "b"} ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options mail_instance = DeliveryMailer.welcome(delivery_method: :optioned) assert_equal default_options, mail_instance.delivery_method.options end test "delivery method options can be overridden per mail instance" do default_options = {a: "b"} ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options overridden_options = {a: "a"} mail_instance = DeliveryMailer.welcome(delivery_method: :optioned, delivery_method_options: overridden_options) assert_equal overridden_options, mail_instance.delivery_method.options end test "default delivery options can be overridden per mail instance" do settings = { address: "localhost", port: 25, domain: 'localhost.localdomain', user_name: nil, password: nil, authentication: nil, enable_starttls_auto: true } assert_equal settings, ActionMailer::Base.smtp_settings overridden_options = {user_name: "overridden", password: "somethingobtuse"} mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options) delivery_method_instance = mail_instance.delivery_method assert_equal "overridden", delivery_method_instance.settings[:user_name] assert_equal "somethingobtuse", delivery_method_instance.settings[:password] assert_equal delivery_method_instance.settings.merge(overridden_options), delivery_method_instance.settings # make sure that overriding delivery method options per mail instance doesn't affect the Base setting assert_equal settings, ActionMailer::Base.smtp_settings end test "non registered delivery methods raises errors" do DeliveryMailer.delivery_method = :unknown assert_raise RuntimeError do DeliveryMailer.welcome.deliver_now end end test "undefined delivery methods raises errors" do DeliveryMailer.delivery_method = nil assert_raise RuntimeError do DeliveryMailer.welcome.deliver_now end end test "does not perform deliveries if requested" do old_perform_deliveries = DeliveryMailer.perform_deliveries begin DeliveryMailer.perform_deliveries = false Mail::Message.any_instance.expects(:deliver!).never DeliveryMailer.welcome.deliver_now ensure DeliveryMailer.perform_deliveries = old_perform_deliveries end end test "does not append the deliveries collection if told not to perform the delivery" do old_perform_deliveries = DeliveryMailer.perform_deliveries begin DeliveryMailer.perform_deliveries = false DeliveryMailer.welcome.deliver_now assert_equal [], DeliveryMailer.deliveries ensure DeliveryMailer.perform_deliveries = old_perform_deliveries end end test "raise errors on bogus deliveries" do DeliveryMailer.delivery_method = BogusDelivery assert_raise RuntimeError do DeliveryMailer.welcome.deliver_now end end test "does not increment the deliveries collection on error" do DeliveryMailer.delivery_method = BogusDelivery assert_raise RuntimeError do DeliveryMailer.welcome.deliver_now end assert_equal [], DeliveryMailer.deliveries end test "does not raise errors on bogus deliveries if set" do old_raise_delivery_errors = DeliveryMailer.raise_delivery_errors begin DeliveryMailer.delivery_method = BogusDelivery DeliveryMailer.raise_delivery_errors = false assert_nothing_raised do DeliveryMailer.welcome.deliver_now end ensure DeliveryMailer.raise_delivery_errors = old_raise_delivery_errors end end test "does not increment the deliveries collection on bogus deliveries" do old_raise_delivery_errors = DeliveryMailer.raise_delivery_errors begin DeliveryMailer.delivery_method = BogusDelivery DeliveryMailer.raise_delivery_errors = false DeliveryMailer.welcome.deliver_now assert_equal [], DeliveryMailer.deliveries ensure DeliveryMailer.raise_delivery_errors = old_raise_delivery_errors end end end rails-4.2.6/actionmailer/test/fixtures/000077500000000000000000000000001266740050600201425ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/anonymous/000077500000000000000000000000001266740050600221725ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/anonymous/welcome.erb000066400000000000000000000000261266740050600243150ustar00rootroot00000000000000Anonymous mailer body rails-4.2.6/actionmailer/test/fixtures/another.path/000077500000000000000000000000001266740050600225355ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/another.path/base_mailer/000077500000000000000000000000001266740050600250005ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/another.path/base_mailer/welcome.erb000066400000000000000000000000311266740050600271170ustar00rootroot00000000000000Welcome from another pathrails-4.2.6/actionmailer/test/fixtures/asset_host_mailer/000077500000000000000000000000001266740050600236475ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/asset_host_mailer/email_with_asset.html.erb000066400000000000000000000000371266740050600306250ustar00rootroot00000000000000<%= image_tag "somelogo.png" %>rails-4.2.6/actionmailer/test/fixtures/asset_mailer/000077500000000000000000000000001266740050600226125ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/asset_mailer/welcome.html.erb000066400000000000000000000000341266740050600256770ustar00rootroot00000000000000<%= image_tag "dummy.png" %>rails-4.2.6/actionmailer/test/fixtures/async_mailer/000077500000000000000000000000001266740050600226105ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/async_mailer/welcome.erb000066400000000000000000000000071266740050600247320ustar00rootroot00000000000000Welcomerails-4.2.6/actionmailer/test/fixtures/attachments/000077500000000000000000000000001266740050600224555ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/attachments/foo.jpg000066400000000000000000000037551266740050600237540ustar00rootroot00000000000000ÿØÿàJFIFddÿìDucky<ÿî&AdobedÀ wYèëÿÛ„       ÿÂ2dÿÄÅ! 01#@A2$456 ! 1AQq‘±Ñ"23“40@Áá’¢Â#st5`1!AQa 0ðq‘¡ÑÁñÿÚ 3Ëöy¿I,éÙ601 ’ÐÔñ˜«Ì•ùº]y«HØ¢u±ûD®$‚~<« WPó/›©÷šŽR±ïeR™¢¾J—±L/JÎ2l^9înÇÞL¼òU”[rd®-åfw(­ä¦¡å³xåy»^y*è3¥a:%q¶ø•¢EðvSË)ÍØóÎû¤{QÄ I HcÁƒew™3ÝŽRVÙaØüK€Å  [8ɾ¤+ÿÚP…Š<‘I‘G¨¤IžHB‡~Þ7=´[ë.Û÷-¼‘éG\@õ–â ºxp·n}¿×‰P}µÁ …’ýìvÖöûSö͸Þևdz.`ƒM¼Q¬Z³>ß‚!AˆAþ§í_Ö¿cÚ¸k^Û˜¾îÇmüv&vLÏGŠ!A£vý¦ícómŸe¹Vò=>"ë—6ÆÍ …3°Œ¦P´‰=I‘IvÍÎÅ”B<HHHH8N¶½m?ÿÚôÔä!Q0¨«Çì…p냹uÁ}wÿÚç©\+Jñ)\^î4áÐQâT(PÓ‚& Í^ÿÚ?7ÿÚ?7ÿÚ?óã¬)¾käG•xº=äyÖ'äSÃ¯ß úxš^üS½šÓF¤du‘q¡ÂëÇ*8ô£RïVð|ëx hÃ4D„Çè«´iŠq5A"6h+ÃÓâ[¼%fíê¶­ 3§Yle9\˜—Q& Ÿn^óúãäCîZì>)ó£‹Ñ9ÁP©@‘ I¥OBžÂ£ÃËä57üNÒžlµø ü>“³æâ_Ð=ØçPjò„àðÓ´!:ÕMS®€ˆÖ„ÛÓ¿ÿÚ?!ØVR.°oMC“âì+÷Ò.$¦$Ai•²:;Y WÎòUÚÏHîFžñ'úBL!ö“CŸäÉæ++XZåÜdÖ2Ý.öȆ„ Ü¡zýR<ÆÂ¥ƒŽ"GàîU–6b›‘ÞÄËkÈMÁ2c¬¬Ô|gaþ˜TUÐØBÖ&}ËOSô|«ƒåyË„¬¤M²œwwø‘T¼ z‰ì'n¾b)wÿcã³H}‘wq§ìïÁº·$ènLá*ÃA½5rJ„+ǧťYÿÚ?!½´!"gWؾ˜ìHR!¹Fškä“àn>°EÂñ ¡ô“æ³ôÆìþ2Oœ“¿«‹pm"JeL¹¥Bb>|›Ú?,ˆ50~nÒR_e{’Hü0u’Ðu3S//7³{Å—¨« g;9ÙfY” ¢º®1ÿÚ?B/CQÁ:Q.ÆËƒÄÏÂ#X åCh±cÈrì¤Ù1)tž ‡O¼^B`€Ó*-„ ‡Q:q¸4ÂP'M …Â`²Çé' YcõŸÿÚ?>HB°ÄìaÄ«cìDnEØ„5LŽÚK÷‡'c"ÂY(àRÐ]F‘<¶k´‘¸ØˆÚXrv8ø+}b]$†OT7EŸE?9Äìq†"@æâ3t¶„‰¢0øq‡„!Xq½Â…‡èÿÿÙrails-4.2.6/actionmailer/test/fixtures/attachments/test.jpg000066400000000000000000000037551266740050600241500ustar00rootroot00000000000000ÿØÿàJFIFddÿìDucky<ÿî&AdobedÀ wYèëÿÛ„       ÿÂ2dÿÄÅ! 01#@A2$456 ! 1AQq‘±Ñ"23“40@Áá’¢Â#st5`1!AQa 0ðq‘¡ÑÁñÿÚ 3Ëöy¿I,éÙ601 ’ÐÔñ˜«Ì•ùº]y«HØ¢u±ûD®$‚~<« WPó/›©÷šŽR±ïeR™¢¾J—±L/JÎ2l^9înÇÞL¼òU”[rd®-åfw(­ä¦¡å³xåy»^y*è3¥a:%q¶ø•¢EðvSË)ÍØóÎû¤{QÄ I HcÁƒew™3ÝŽRVÙaØüK€Å  [8ɾ¤+ÿÚP…Š<‘I‘G¨¤IžHB‡~Þ7=´[ë.Û÷-¼‘éG\@õ–â ºxp·n}¿×‰P}µÁ …’ýìvÖöûSö͸Þևdz.`ƒM¼Q¬Z³>ß‚!AˆAþ§í_Ö¿cÚ¸k^Û˜¾îÇmüv&vLÏGŠ!A£vý¦ícómŸe¹Vò=>"ë—6ÆÍ …3°Œ¦P´‰=I‘IvÍÎÅ”B<HHHH8N¶½m?ÿÚôÔä!Q0¨«Çì…p냹uÁ}wÿÚç©\+Jñ)\^î4áÐQâT(PÓ‚& Í^ÿÚ?7ÿÚ?7ÿÚ?óã¬)¾käG•xº=äyÖ'äSÃ¯ß úxš^üS½šÓF¤du‘q¡ÂëÇ*8ô£RïVð|ëx hÃ4D„Çè«´iŠq5A"6h+ÃÓâ[¼%fíê¶­ 3§Yle9\˜—Q& Ÿn^óúãäCîZì>)ó£‹Ñ9ÁP©@‘ I¥OBžÂ£ÃËä57üNÒžlµø ü>“³æâ_Ð=ØçPjò„àðÓ´!:ÕMS®€ˆÖ„ÛÓ¿ÿÚ?!ØVR.°oMC“âì+÷Ò.$¦$Ai•²:;Y WÎòUÚÏHîFžñ'úBL!ö“CŸäÉæ++XZåÜdÖ2Ý.öȆ„ Ü¡zýR<ÆÂ¥ƒŽ"GàîU–6b›‘ÞÄËkÈMÁ2c¬¬Ô|gaþ˜TUÐØBÖ&}ËOSô|«ƒåyË„¬¤M²œwwø‘T¼ z‰ì'n¾b)wÿcã³H}‘wq§ìïÁº·$ènLá*ÃA½5rJ„+ǧťYÿÚ?!½´!"gWؾ˜ìHR!¹Fškä“àn>°EÂñ ¡ô“æ³ôÆìþ2Oœ“¿«‹pm"JeL¹¥Bb>|›Ú?,ˆ50~nÒR_e{’Hü0u’Ðu3S//7³{Å—¨« g;9ÙfY” ¢º®1ÿÚ?B/CQÁ:Q.ÆËƒÄÏÂ#X åCh±cÈrì¤Ù1)tž ‡O¼^B`€Ó*-„ ‡Q:q¸4ÂP'M …Â`²Çé' YcõŸÿÚ?>HB°ÄìaÄ«cìDnEØ„5LŽÚK÷‡'c"ÂY(àRÐ]F‘<¶k´‘¸ØˆÚXrv8ø+}b]$†OT7EŸE?9Äìq†"@æâ3t¶„‰¢0øq‡„!Xq½Â…‡èÿÿÙrails-4.2.6/actionmailer/test/fixtures/auto_layout_mailer/000077500000000000000000000000001266740050600240405ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb000066400000000000000000000000061266740050600265740ustar00rootroot00000000000000Insiderails-4.2.6/actionmailer/test/fixtures/auto_layout_mailer/multipart.html.erb000066400000000000000000000000231266740050600275110ustar00rootroot00000000000000text/html multipartrails-4.2.6/actionmailer/test/fixtures/auto_layout_mailer/multipart.text.erb000066400000000000000000000000241266740050600275320ustar00rootroot00000000000000text/plain multipartrails-4.2.6/actionmailer/test/fixtures/base_mailer/000077500000000000000000000000001266740050600224055ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_mailer/attachment_with_content.erb000066400000000000000000000000271266740050600300130ustar00rootroot00000000000000Attachment with contentrails-4.2.6/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb000066400000000000000000000000001266740050600302160ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb000066400000000000000000000000001266740050600335700ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_mailer/different_layout.html.erb000066400000000000000000000000041266740050600273770ustar00rootroot00000000000000HTMLrails-4.2.6/actionmailer/test/fixtures/base_mailer/different_layout.text.erb000066400000000000000000000000051266740050600274200ustar00rootroot00000000000000PLAINrails-4.2.6/actionmailer/test/fixtures/base_mailer/email_custom_layout.text.html.erb000066400000000000000000000000111266740050600310730ustar00rootroot00000000000000body_textrails-4.2.6/actionmailer/test/fixtures/base_mailer/email_with_translations.html.erb000066400000000000000000000000451266740050600307640ustar00rootroot00000000000000<%= t('.greet_user', name: 'lifo') %>rails-4.2.6/actionmailer/test/fixtures/base_mailer/explicit_multipart_templates.html.erb000066400000000000000000000000411266740050600320350ustar00rootroot00000000000000HTML Explicit Multipart Templatesrails-4.2.6/actionmailer/test/fixtures/base_mailer/explicit_multipart_templates.text.erb000066400000000000000000000000411266740050600320550ustar00rootroot00000000000000TEXT Explicit Multipart Templatesrails-4.2.6/actionmailer/test/fixtures/base_mailer/explicit_multipart_with_one_template.erb000066400000000000000000000000331266740050600326040ustar00rootroot00000000000000<%= self.formats.inspect %>rails-4.2.6/actionmailer/test/fixtures/base_mailer/html_only.html.erb000066400000000000000000000000201266740050600260370ustar00rootroot00000000000000

Testing

rails-4.2.6/actionmailer/test/fixtures/base_mailer/implicit_multipart.html.erb000066400000000000000000000000271266740050600277540ustar00rootroot00000000000000HTML Implicit Multipartrails-4.2.6/actionmailer/test/fixtures/base_mailer/implicit_multipart.text.erb000066400000000000000000000000271266740050600277740ustar00rootroot00000000000000TEXT Implicit Multipartrails-4.2.6/actionmailer/test/fixtures/base_mailer/implicit_with_locale.en.html.erb000066400000000000000000000000341266740050600306240ustar00rootroot00000000000000Implicit with locale EN HTMLrails-4.2.6/actionmailer/test/fixtures/base_mailer/implicit_with_locale.html.erb000066400000000000000000000000311266740050600302200ustar00rootroot00000000000000Implicit with locale HTMLrails-4.2.6/actionmailer/test/fixtures/base_mailer/implicit_with_locale.pl.text.erb000066400000000000000000000000341266740050600306550ustar00rootroot00000000000000Implicit with locale PL TEXTrails-4.2.6/actionmailer/test/fixtures/base_mailer/implicit_with_locale.text.erb000066400000000000000000000000311266740050600302400ustar00rootroot00000000000000Implicit with locale TEXTrails-4.2.6/actionmailer/test/fixtures/base_mailer/inline_attachment.html.erb000066400000000000000000000001531266740050600275270ustar00rootroot00000000000000

Inline Image

<%= image_tag attachments['logo.png'].url %>

This is an image that is inline

rails-4.2.6/actionmailer/test/fixtures/base_mailer/inline_attachment.text.erb000066400000000000000000000000401266740050600275420ustar00rootroot00000000000000Inline Image No image for you rails-4.2.6/actionmailer/test/fixtures/base_mailer/plain_text_only.text.erb000066400000000000000000000000071266740050600272670ustar00rootroot00000000000000Testingrails-4.2.6/actionmailer/test/fixtures/base_mailer/welcome.erb000066400000000000000000000000071266740050600245270ustar00rootroot00000000000000Welcomerails-4.2.6/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb000066400000000000000000000000001266740050600302110ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_mailer/without_mail_call.erb000066400000000000000000000000611266740050600265740ustar00rootroot00000000000000<% raise 'the template should not be rendered' %>rails-4.2.6/actionmailer/test/fixtures/base_test/000077500000000000000000000000001266740050600221135ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_test/after_action_mailer/000077500000000000000000000000001266740050600261025ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_test/after_action_mailer/welcome.html.erb000066400000000000000000000000001266740050600311600ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_test/before_action_mailer/000077500000000000000000000000001266740050600262435ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_test/before_action_mailer/welcome.html.erb000066400000000000000000000000001266740050600313210ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/000077500000000000000000000000001266740050600306365ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb000066400000000000000000000000001266740050600337140ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_test/late_inline_attachment_mailer/000077500000000000000000000000001266740050600301375ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/base_test/late_inline_attachment_mailer/on_render.erb000066400000000000000000000005241266740050600326050ustar00rootroot00000000000000

Adding an inline image while rendering

<% controller.attachments.inline["controller_attachments.jpg"] = 'via controller.attachments.inline' %> <%= image_tag attachments['controller_attachments.jpg'].url %> <% attachments.inline["attachments.jpg"] = 'via attachments.inline' %> <%= image_tag attachments['attachments.jpg'].url %> rails-4.2.6/actionmailer/test/fixtures/explicit_layout_mailer/000077500000000000000000000000001266740050600247115ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb000066400000000000000000000000161266740050600276540ustar00rootroot00000000000000You logged outrails-4.2.6/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb000066400000000000000000000000161266740050600276500ustar00rootroot00000000000000We do not spamrails-4.2.6/actionmailer/test/fixtures/first_mailer/000077500000000000000000000000001266740050600226225ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/first_mailer/share.erb000066400000000000000000000000131266740050600244100ustar00rootroot00000000000000first mail rails-4.2.6/actionmailer/test/fixtures/i18n_test_mailer/000077500000000000000000000000001266740050600233115ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/i18n_test_mailer/mail_with_i18n_subject.erb000066400000000000000000000000771266740050600303420ustar00rootroot00000000000000Hello there, Mr. <%= @recipient %>. Be greeted, new member! rails-4.2.6/actionmailer/test/fixtures/layouts/000077500000000000000000000000001266740050600216425ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb000066400000000000000000000000361266740050600271740ustar00rootroot00000000000000Hello from layout <%= yield %>rails-4.2.6/actionmailer/test/fixtures/layouts/auto_layout_mailer.text.erb000066400000000000000000000000401266740050600272070ustar00rootroot00000000000000text/plain layout - <%= yield %>rails-4.2.6/actionmailer/test/fixtures/layouts/different_layout.html.erb000066400000000000000000000000241266740050600266360ustar00rootroot00000000000000HTML -- <%= yield %>rails-4.2.6/actionmailer/test/fixtures/layouts/different_layout.text.erb000066400000000000000000000000251266740050600266570ustar00rootroot00000000000000PLAIN -- <%= yield %>rails-4.2.6/actionmailer/test/fixtures/layouts/spam.html.erb000066400000000000000000000000331266740050600242330ustar00rootroot00000000000000Spammer layout <%= yield %>rails-4.2.6/actionmailer/test/fixtures/mail_delivery_test/000077500000000000000000000000001266740050600240265ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/000077500000000000000000000000001266740050600272025ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb000066400000000000000000000000001266740050600322600ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/nested_layout_mailer/000077500000000000000000000000001266740050600243525ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/nested_layout_mailer/signup.html.erb000066400000000000000000000000161266740050600273110ustar00rootroot00000000000000We do not spamrails-4.2.6/actionmailer/test/fixtures/path.with.dots/000077500000000000000000000000001266740050600230205ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/000077500000000000000000000000001266740050600265215ustar00rootroot00000000000000multipart_with_template_path_with_dots.erb000066400000000000000000000000261266740050600372010ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/path.with.dots/funky_path_mailerHave some dots. Enjoy!rails-4.2.6/actionmailer/test/fixtures/proc_mailer/000077500000000000000000000000001266740050600224365ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/proc_mailer/welcome.html.erb000066400000000000000000000000001266740050600255140ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/raw_email000066400000000000000000000010401266740050600220200ustar00rootroot00000000000000From jamis_buck@byu.edu Mon May 2 16:07:05 2005 Mime-Version: 1.0 (Apple Message framework v622) Content-Transfer-Encoding: base64 Message-Id: Content-Type: text/plain; charset=EUC-KR; format=flowed To: willard15georgina@jamis.backpackit.com From: Jamis Buck Subject: =?EUC-KR?Q?NOTE:_=C7=D1=B1=B9=B8=BB=B7=CE_=C7=CF=B4=C2_=B0=CD?= Date: Mon, 2 May 2005 16:07:05 -0600 tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin wLogSmFtaXPA1LTPtNku rails-4.2.6/actionmailer/test/fixtures/second_mailer/000077500000000000000000000000001266740050600227465ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/second_mailer/share.erb000066400000000000000000000000141266740050600245350ustar00rootroot00000000000000second mail rails-4.2.6/actionmailer/test/fixtures/templates/000077500000000000000000000000001266740050600221405ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/templates/signed_up.erb000066400000000000000000000000431266740050600246040ustar00rootroot00000000000000Hello there, Mr. <%= @recipient %>rails-4.2.6/actionmailer/test/fixtures/test_helper_mailer/000077500000000000000000000000001266740050600240115ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/test_helper_mailer/welcome000066400000000000000000000000101266740050600253560ustar00rootroot00000000000000Welcome!rails-4.2.6/actionmailer/test/fixtures/test_mailer/000077500000000000000000000000001266740050600224525ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/test_mailer/_subtemplate.text.erb000066400000000000000000000000111266740050600266030ustar00rootroot00000000000000let's go!rails-4.2.6/actionmailer/test/fixtures/test_mailer/custom_templating_extension.html.haml000066400000000000000000000000641266740050600321120ustar00rootroot00000000000000%p Hello there, %p Mr. = @recipient from hamlrails-4.2.6/actionmailer/test/fixtures/test_mailer/custom_templating_extension.text.haml000066400000000000000000000000641266740050600321320ustar00rootroot00000000000000%p Hello there, %p Mr. = @recipient from hamlrails-4.2.6/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb000066400000000000000000000003101266740050600320740ustar00rootroot00000000000000 HTML formatted message to <%= @recipient %>. HTML formatted message to <%= @recipient %>. rails-4.2.6/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~000066400000000000000000000003101266740050600322720ustar00rootroot00000000000000 HTML formatted message to <%= @recipient %>. HTML formatted message to <%= @recipient %>. rails-4.2.6/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.erb000066400000000000000000000000671266740050600325700ustar00rootroot00000000000000Ignored when searching for implicitly multipart parts. rails-4.2.6/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak000066400000000000000000000000671266740050600322540ustar00rootroot00000000000000Ignored when searching for implicitly multipart parts. rails-4.2.6/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.erb000066400000000000000000000001021266740050600321130ustar00rootroot00000000000000Plain text to <%= @recipient %>. Plain text to <%= @recipient %>. rails-4.2.6/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.yaml.erb000066400000000000000000000000321266740050600320730ustar00rootroot00000000000000yaml to: <%= @recipient %>rails-4.2.6/actionmailer/test/fixtures/test_mailer/included_subtemplate.text.erb000066400000000000000000000000541266740050600303220ustar00rootroot00000000000000Hey Ho, <%= render partial: "subtemplate" %>rails-4.2.6/actionmailer/test/fixtures/test_mailer/multipart_alternative.html.erb000066400000000000000000000000401266740050600305200ustar00rootroot00000000000000foo <%= @foo %>rails-4.2.6/actionmailer/test/fixtures/test_mailer/multipart_alternative.plain.erb000066400000000000000000000000201266740050600306550ustar00rootroot00000000000000foo: <%= @foo %>rails-4.2.6/actionmailer/test/fixtures/test_mailer/rxml_template.rxml000066400000000000000000000000261266740050600262310ustar00rootroot00000000000000xml.instruct! xml.testrails-4.2.6/actionmailer/test/fixtures/test_mailer/signed_up.html.erb000066400000000000000000000000431266740050600260610ustar00rootroot00000000000000Hello there, Mr. <%= @recipient %>rails-4.2.6/actionmailer/test/fixtures/url_test_mailer/000077500000000000000000000000001266740050600233345ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb000066400000000000000000000000451266740050600273640ustar00rootroot00000000000000<%= url_for(@options) %> <%= @url %> rails-4.2.6/actionmailer/test/fixtures/url_test_mailer/signed_up_with_url.erb000066400000000000000000000002071266740050600277170ustar00rootroot00000000000000Hello there, Mr. <%= @recipient %>. Please see our greeting at <%= @welcome_url %> <%= welcome_url %> <%= image_tag "somelogo.png" %>rails-4.2.6/actionmailer/test/i18n_with_controller_test.rb000066400000000000000000000030351266740050600237330ustar00rootroot00000000000000require 'abstract_unit' require 'action_view' require 'action_controller' class I18nTestMailer < ActionMailer::Base configure do |c| c.assets_dir = '' end def mail_with_i18n_subject(recipient) @recipient = recipient I18n.locale = :de mail(to: recipient, subject: I18n.t(:email_subject), from: "system@loudthinking.com", date: Time.local(2004, 12, 12)) end end class TestController < ActionController::Base def send_mail email = I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver_now render text: "Mail sent - Subject: #{email.subject}" end end class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new Routes.draw do get ':controller(/:action(/:id))' end class RoutedRackApp attr_reader :routes def initialize(routes, &blk) @routes = routes @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes) end def call(env) @stack.call(env) end end APP = RoutedRackApp.new(Routes) def app APP end teardown do I18n.locale = I18n.default_locale end def test_send_mail Mail::SMTP.any_instance.expects(:deliver!) with_translation 'de', email_subject: '[Anmeldung] Willkommen' do get '/test/send_mail' assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body end end protected def with_translation(locale, data) I18n.backend.store_translations(locale, data) yield ensure I18n.backend.reload! end end rails-4.2.6/actionmailer/test/log_subscriber_test.rb000066400000000000000000000025021266740050600226600ustar00rootroot00000000000000require 'abstract_unit' require 'mailers/base_mailer' require 'active_support/log_subscriber/test_helper' require 'action_mailer/log_subscriber' class AMLogSubscriberTest < ActionMailer::TestCase include ActiveSupport::LogSubscriber::TestHelper def setup super ActionMailer::LogSubscriber.attach_to :action_mailer end class TestMailer < ActionMailer::Base def receive(mail) # Do nothing end end def set_logger(logger) ActionMailer::Base.logger = logger end def test_deliver_is_notified BaseMailer.welcome.deliver_now wait assert_equal(1, @logger.logged(:info).size) assert_match(/Sent mail to system@test.lindsaar.net/, @logger.logged(:info).first) assert_equal(2, @logger.logged(:debug).size) assert_match(/BaseMailer#welcome: processed outbound mail in [\d.]+ms/, @logger.logged(:debug).first) assert_match(/Welcome/, @logger.logged(:debug).second) ensure BaseMailer.deliveries.clear end def test_receive_is_notified fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email") TestMailer.receive(fixture) wait assert_equal(1, @logger.logged(:info).size) assert_match(/Received mail/, @logger.logged(:info).first) assert_equal(1, @logger.logged(:debug).size) assert_match(/Jamis/, @logger.logged(:debug).first) end end rails-4.2.6/actionmailer/test/mail_helper_test.rb000066400000000000000000000062741266740050600221470ustar00rootroot00000000000000require 'abstract_unit' class HelperMailer < ActionMailer::Base def use_mail_helper @text = "But soft! What light through yonder window breaks? It is the east, " + "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " + "which is sick and pale with grief that thou, her maid, art far more " + "fair than she. Be not her maid, for she is envious! Her vestal " + "livery is but sick and green, and none but fools do wear it. Cast " + "it off!" mail_with_defaults do |format| format.html { render(inline: "<%= block_format @text %>") } end end def use_format_paragraph @text = "But soft! What light through yonder window breaks?" mail_with_defaults do |format| format.html { render(inline: "<%= format_paragraph @text, 15, 1 %>") } end end def use_format_paragraph_with_long_first_word @text = "Antidisestablishmentarianism is very long." mail_with_defaults do |format| format.html { render(inline: "<%= format_paragraph @text, 10, 1 %>") } end end def use_mailer mail_with_defaults do |format| format.html { render(inline: "<%= mailer.message.subject %>") } end end def use_message mail_with_defaults do |format| format.html { render(inline: "<%= message.subject %>") } end end def use_block_format @text = <<-TEXT This is the first paragraph. The second paragraph. * item1 * item2 * item3 TEXT mail_with_defaults do |format| format.html { render(inline: "<%= block_format @text %>") } end end def use_cache mail_with_defaults do |format| format.html { render(inline: "<% cache(:foo) do %>Greetings from a cache helper block<% end %>") } end end protected def mail_with_defaults(&block) mail(to: "test@localhost", from: "tester@example.com", subject: "using helpers", &block) end end class MailerHelperTest < ActionMailer::TestCase def test_use_mail_helper mail = HelperMailer.use_mail_helper assert_match %r{ But soft!}, mail.body.encoded assert_match %r{east, and\r\n Juliet}, mail.body.encoded end def test_use_mailer mail = HelperMailer.use_mailer assert_match "using helpers", mail.body.encoded end def test_use_message mail = HelperMailer.use_message assert_match "using helpers", mail.body.encoded end def test_use_format_paragraph mail = HelperMailer.use_format_paragraph assert_match " But soft! What\r\n light through\r\n yonder window\r\n breaks?", mail.body.encoded end def test_use_format_paragraph_with_long_first_word mail = HelperMailer.use_format_paragraph_with_long_first_word assert_equal " Antidisestablishmentarianism\r\n is very\r\n long.", mail.body.encoded end def test_use_block_format mail = HelperMailer.use_block_format expected = <<-TEXT This is the first paragraph. The second paragraph. * item1 * item2 * item3 TEXT assert_equal expected.gsub("\n", "\r\n"), mail.body.encoded end def test_use_cache assert_nothing_raised do mail = HelperMailer.use_cache assert_equal "Greetings from a cache helper block", mail.body.encoded end end end rails-4.2.6/actionmailer/test/mail_layout_test.rb000066400000000000000000000047631266740050600222060ustar00rootroot00000000000000require 'abstract_unit' class AutoLayoutMailer < ActionMailer::Base default to: 'test@localhost', subject: "You have a mail", from: "tester@example.com" def hello mail() end def spam @world = "Earth" mail(body: render(inline: "Hello, <%= @world %>", layout: 'spam')) end def nolayout @world = "Earth" mail(body: render(inline: "Hello, <%= @world %>", layout: false)) end def multipart(type = nil) mail(content_type: type) do |format| format.text { render } format.html { render } end end end class ExplicitLayoutMailer < ActionMailer::Base layout 'spam', except: [:logout] default to: 'test@localhost', subject: "You have a mail", from: "tester@example.com" def signup mail() end def logout mail() end end class LayoutMailerTest < ActiveSupport::TestCase def test_should_pickup_default_layout mail = AutoLayoutMailer.hello assert_equal "Hello from layout Inside", mail.body.to_s.strip end def test_should_pickup_multipart_layout mail = AutoLayoutMailer.multipart assert_equal "multipart/alternative", mail.mime_type assert_equal 2, mail.parts.size assert_equal 'text/plain', mail.parts.first.mime_type assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s assert_equal 'text/html', mail.parts.last.mime_type assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end def test_should_pickup_multipartmixed_layout mail = AutoLayoutMailer.multipart("multipart/mixed") assert_equal "multipart/mixed", mail.mime_type assert_equal 2, mail.parts.size assert_equal 'text/plain', mail.parts.first.mime_type assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s assert_equal 'text/html', mail.parts.last.mime_type assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end def test_should_pickup_layout_given_to_render mail = AutoLayoutMailer.spam assert_equal "Spammer layout Hello, Earth", mail.body.to_s.strip end def test_should_respect_layout_false mail = AutoLayoutMailer.nolayout assert_equal "Hello, Earth", mail.body.to_s.strip end def test_explicit_class_layout mail = ExplicitLayoutMailer.signup assert_equal "Spammer layout We do not spam", mail.body.to_s.strip end def test_explicit_layout_exceptions mail = ExplicitLayoutMailer.logout assert_equal "You logged out", mail.body.to_s.strip end end rails-4.2.6/actionmailer/test/mailers/000077500000000000000000000000001266740050600177255ustar00rootroot00000000000000rails-4.2.6/actionmailer/test/mailers/asset_mailer.rb000066400000000000000000000001541266740050600227220ustar00rootroot00000000000000class AssetMailer < ActionMailer::Base self.mailer_name = "asset_mailer" def welcome mail end end rails-4.2.6/actionmailer/test/mailers/base_mailer.rb000066400000000000000000000062721266740050600225240ustar00rootroot00000000000000class BaseMailer < ActionMailer::Base self.mailer_name = "base_mailer" default to: 'system@test.lindsaar.net', from: 'jose@test.plataformatec.com', reply_to: 'mikel@test.lindsaar.net' def welcome(hash = {}) headers['X-SPAM'] = "Not SPAM" mail({subject: "The first email on new API!"}.merge!(hash)) end def welcome_with_headers(hash = {}) headers hash mail end def welcome_from_another_path(path) mail(template_name: "welcome", template_path: path) end def html_only(hash = {}) mail(hash) end def plain_text_only(hash = {}) mail(hash) end def inline_attachment attachments.inline['logo.png'] = "\312\213\254\232" mail end def attachment_with_content(hash = {}) attachments['invoice.pdf'] = 'This is test File content' mail(hash) end def attachment_with_hash attachments['invoice.jpg'] = { data: ::Base64.encode64("\312\213\254\232)b"), mime_type: "image/x-jpg", transfer_encoding: "base64" } mail end def attachment_with_hash_default_encoding attachments['invoice.jpg'] = { data: "\312\213\254\232)b", mime_type: "image/x-jpg" } mail end def implicit_multipart(hash = {}) attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments) mail(hash) end def implicit_with_locale(hash = {}) mail(hash) end def explicit_multipart(hash = {}) attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments) mail(hash) do |format| format.text { render text: "TEXT Explicit Multipart" } format.html { render text: "HTML Explicit Multipart" } end end def explicit_multipart_templates(hash = {}) mail(hash) do |format| format.html format.text end end def explicit_multipart_with_any(hash = {}) mail(hash) do |format| format.any(:text, :html){ render text: "Format with any!" } end end def explicit_multipart_with_options(include_html = false) mail do |format| format.text(content_transfer_encoding: "base64"){ render "welcome" } format.html{ render "welcome" } if include_html end end def explicit_multipart_with_one_template(hash = {}) mail(hash) do |format| format.html format.text end end def implicit_different_template(template_name='') mail(template_name: template_name) end def explicit_different_template(template_name='') mail do |format| format.text { render template: "#{mailer_name}/#{template_name}" } format.html { render template: "#{mailer_name}/#{template_name}" } end end def different_layout(layout_name='') mail do |format| format.text { render layout: layout_name } format.html { render layout: layout_name } end end def email_with_translations mail body: render("email_with_translations", formats: [:html]) end def without_mail_call end def with_nil_as_return_value mail(template_name: "welcome") nil end def with_subject_interpolations mail(subject: default_i18n_subject(rapper_or_impersonator: 'Slim Shady'), body: '') end end rails-4.2.6/actionmailer/test/mailers/delayed_mailer.rb000066400000000000000000000002731266740050600232140ustar00rootroot00000000000000class DelayedMailer < ActionMailer::Base def test_message(*) mail(from: 'test-sender@test.com', to: 'test-receiver@test.com', subject: 'Test Subject', body: 'Test Body') end end rails-4.2.6/actionmailer/test/mailers/proc_mailer.rb000066400000000000000000000005321266740050600225460ustar00rootroot00000000000000class ProcMailer < ActionMailer::Base default to: 'system@test.lindsaar.net', 'X-Proc-Method' => Proc.new { Time.now.to_i.to_s }, subject: Proc.new { give_a_greeting }, 'x-has-to-proc' => :symbol def welcome mail end private def give_a_greeting "Thanks for signing up this afternoon" end end rails-4.2.6/actionmailer/test/message_delivery_test.rb000066400000000000000000000057621266740050600232160ustar00rootroot00000000000000# encoding: utf-8 require 'abstract_unit' require 'active_job' require 'minitest/mock' require 'mailers/delayed_mailer' require 'active_support/core_ext/numeric/time' class MessageDeliveryTest < ActiveSupport::TestCase include ActiveJob::TestHelper setup do @previous_logger = ActiveJob::Base.logger @previous_delivery_method = ActionMailer::Base.delivery_method ActionMailer::Base.delivery_method = :test ActiveJob::Base.logger = Logger.new(nil) @mail = DelayedMailer.test_message(1, 2, 3) ActionMailer::Base.deliveries.clear ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true end teardown do ActiveJob::Base.logger = @previous_logger ActionMailer::Base.delivery_method = @previous_delivery_method end test 'should have a message' do assert @mail.message end test 'its message should be a Mail::Message' do assert_equal Mail::Message , @mail.message.class end test 'should respond to .deliver' do assert_respond_to @mail, :deliver end test 'should respond to .deliver!' do assert_respond_to @mail, :deliver! end test '.deliver is deprecated' do assert_deprecated do @mail.deliver end end test '.deliver! is deprecated' do assert_deprecated do @mail.deliver! end end test 'should respond to .deliver_later' do assert_respond_to @mail, :deliver_later end test 'should respond to .deliver_later!' do assert_respond_to @mail, :deliver_later! end test 'should respond to .deliver_now' do assert_respond_to @mail, :deliver_now end test 'should respond to .deliver_now!' do assert_respond_to @mail, :deliver_now! end def test_should_enqueue_and_run_correctly_in_activejob @mail.deliver_later! assert_equal 1, ActionMailer::Base.deliveries.size ensure ActionMailer::Base.deliveries.clear end test 'should enqueue the email with :deliver_now delivery method' do assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do @mail.deliver_later end end test 'should enqueue the email with :deliver_now! delivery method' do assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now!', 1, 2, 3]) do @mail.deliver_later! end end test 'should enqueue a delivery with a delay' do travel_to Time.new(2004, 11, 24, 01, 04, 44) do assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do @mail.deliver_later wait: 600.seconds end end end test 'should enqueue a delivery at a specific time' do later_time = Time.now.to_f + 3600 assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do @mail.deliver_later wait_until: later_time end end end rails-4.2.6/actionmailer/test/test_case_test.rb000066400000000000000000000012121266740050600216230ustar00rootroot00000000000000require 'abstract_unit' class TestTestMailer < ActionMailer::Base end class CrazyNameMailerTest < ActionMailer::TestCase tests TestTestMailer def test_set_mailer_class_manual assert_equal TestTestMailer, self.class.mailer_class end end class CrazySymbolNameMailerTest < ActionMailer::TestCase tests :test_test_mailer def test_set_mailer_class_manual_using_symbol assert_equal TestTestMailer, self.class.mailer_class end end class CrazyStringNameMailerTest < ActionMailer::TestCase tests 'test_test_mailer' def test_set_mailer_class_manual_using_string assert_equal TestTestMailer, self.class.mailer_class end end rails-4.2.6/actionmailer/test/test_helper_test.rb000066400000000000000000000067041266740050600222020ustar00rootroot00000000000000# encoding: utf-8 require 'abstract_unit' class TestHelperMailer < ActionMailer::Base def test @world = "Earth" mail body: render(inline: "Hello, <%= @world %>"), to: "test@example.com", from: "tester@example.com" end end class TestHelperMailerTest < ActionMailer::TestCase def test_setup_sets_right_action_mailer_options assert_equal :test, ActionMailer::Base.delivery_method assert ActionMailer::Base.perform_deliveries assert_equal [], ActionMailer::Base.deliveries end def test_setup_creates_the_expected_mailer assert_kind_of Mail::Message, @expected assert_equal "1.0", @expected.mime_version assert_equal "text/plain", @expected.mime_type end def test_mailer_class_is_correctly_inferred assert_equal TestHelperMailer, self.class.mailer_class end def test_determine_default_mailer_raises_correct_error assert_raise(ActionMailer::NonInferrableMailerError) do self.class.determine_default_mailer("NotAMailerTest") end end def test_charset_is_utf_8 assert_equal "UTF-8", charset end def test_encode assert_equal '=?UTF-8?Q?This_is_=E3=81=82_string?=', encode('This is ã‚ string') end def test_read_fixture assert_equal ['Welcome!'], read_fixture('welcome') end def test_assert_emails assert_nothing_raised do assert_emails 1 do TestHelperMailer.test.deliver_now end end end def test_repeated_assert_emails_calls assert_nothing_raised do assert_emails 1 do TestHelperMailer.test.deliver_now end end assert_nothing_raised do assert_emails 2 do TestHelperMailer.test.deliver_now TestHelperMailer.test.deliver_now end end end def test_assert_emails_with_no_block assert_nothing_raised do TestHelperMailer.test.deliver_now assert_emails 1 end assert_nothing_raised do TestHelperMailer.test.deliver_now TestHelperMailer.test.deliver_now assert_emails 3 end end def test_assert_no_emails assert_nothing_raised do assert_no_emails do TestHelperMailer.test end end end def test_assert_emails_too_few_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_emails 2 do TestHelperMailer.test.deliver_now end end assert_match(/2 .* but 1/, error.message) end def test_assert_emails_too_many_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_emails 1 do TestHelperMailer.test.deliver_now TestHelperMailer.test.deliver_now end end assert_match(/1 .* but 2/, error.message) end def test_assert_emails_message TestHelperMailer.test.deliver_now error = assert_raise ActiveSupport::TestCase::Assertion do assert_emails 2 do TestHelperMailer.test.deliver_now end end assert_match "Expected: 2", error.message assert_match "Actual: 1", error.message end def test_assert_no_emails_failure error = assert_raise ActiveSupport::TestCase::Assertion do assert_no_emails do TestHelperMailer.test.deliver_now end end assert_match(/0 .* but 1/, error.message) end end class AnotherTestHelperMailerTest < ActionMailer::TestCase tests TestHelperMailer def setup @test_var = "a value" end def test_setup_shouldnt_conflict_with_mailer_setup assert_kind_of Mail::Message, @expected assert_equal 'a value', @test_var end end rails-4.2.6/actionmailer/test/url_test.rb000066400000000000000000000072251266740050600204650ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller' class WelcomeController < ActionController::Base end AppRoutes = ActionDispatch::Routing::RouteSet.new class ActionMailer::Base include AppRoutes.url_helpers end class UrlTestMailer < ActionMailer::Base default_url_options[:host] = 'www.basecamphq.com' configure do |c| c.assets_dir = '' # To get the tests to pass end def signed_up_with_url(recipient) @recipient = recipient @welcome_url = url_for host: "example.com", controller: "welcome", action: "greeting" mail(to: recipient, subject: "[Signed up] Welcome #{recipient}", from: "system@loudthinking.com", date: Time.local(2004, 12, 12)) end def exercise_url_for(options) @options = options @url = url_for(@options) mail(from: 'from@example.com', to: 'to@example.com', subject: 'subject') end end class ActionMailerUrlTest < ActionMailer::TestCase class DummyModel def self.model_name OpenStruct.new(route_key: 'dummy_model') end def persisted? false end def model_name self.class.model_name end def to_model self end end def encode( text, charset="UTF-8" ) quoted_printable( text, charset ) end def new_mail( charset="UTF-8" ) mail = Mail.new mail.mime_version = "1.0" if charset mail.content_type ["text", "plain", { "charset" => charset }] end mail end def assert_url_for(expected, options, relative = false) expected = "http://www.basecamphq.com#{expected}" if expected.start_with?('/') && !relative urls = UrlTestMailer.exercise_url_for(options).body.to_s.chomp.split assert_equal expected, urls.first assert_equal expected, urls.second end def setup @recipient = 'test@localhost' end def test_url_for UrlTestMailer.delivery_method = :test AppRoutes.draw do get ':controller(/:action(/:id))' get '/welcome' => 'foo#bar', as: 'welcome' get '/dummy_model' => 'foo#baz', as: 'dummy_model' end # string assert_url_for 'http://foo/', 'http://foo/' # symbol assert_url_for '/welcome', :welcome # hash assert_url_for '/a/b/c', controller: 'a', action: 'b', id: 'c' assert_url_for '/a/b/c', {controller: 'a', action: 'b', id: 'c', only_path: true}, true # model assert_url_for '/dummy_model', DummyModel.new # class assert_url_for '/dummy_model', DummyModel # array assert_url_for '/dummy_model' , [DummyModel] end def test_signed_up_with_url UrlTestMailer.delivery_method = :test AppRoutes.draw do get ':controller(/:action(/:id))' get '/welcome' => "foo#bar", as: "welcome" end expected = new_mail expected.to = @recipient expected.subject = "[Signed up] Welcome #{@recipient}" expected.body = "Hello there,\n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n\"Somelogo\"" expected.from = "system@loudthinking.com" expected.date = Time.local(2004, 12, 12) expected.content_type = "text/html" created = nil assert_nothing_raised { created = UrlTestMailer.signed_up_with_url(@recipient) } assert_not_nil created expected.message_id = '<123@456>' created.message_id = '<123@456>' assert_dom_equal expected.encoded, created.encoded assert_nothing_raised { UrlTestMailer.signed_up_with_url(@recipient).deliver_now } assert_not_nil ActionMailer::Base.deliveries.first delivered = ActionMailer::Base.deliveries.first delivered.message_id = '<123@456>' assert_dom_equal expected.encoded, delivered.encoded end end rails-4.2.6/actionpack/000077500000000000000000000000001266740050600147575ustar00rootroot00000000000000rails-4.2.6/actionpack/CHANGELOG.md000066400000000000000000000440521266740050600165750ustar00rootroot00000000000000## Rails 4.2.6 (March 07, 2016) ## * No changes. ## Rails 4.2.5.2 (February 26, 2016) ## * Do not allow render with unpermitted parameter. Fixes CVE-2016-2098. *Arthur Neves* ## Rails 4.2.5.1 (January 25, 2015) ## * No changes. ## Rails 4.2.5 (November 12, 2015) ## * `ActionController::TestCase` can teardown gracefully if an error is raised early in the `setup` chain. *Yves Senn* * Parse RSS/ATOM responses as XML, not HTML. *Alexander Kaupanin* * Fix regression in mounted engine named routes generation for app deployed to a subdirectory. `relative_url_root` was prepended to the path twice (e.g. "/subdir/subdir/engine_path" instead of "/subdir/engine_path") Fixes #20920. Fixes #21459. *Matthew Erhard* * `url_for` does not modify its arguments when generating polymorphic URLs. *Bernerd Schaefer* * Update `ActionController::TestSession#fetch` to behave more like `ActionDispatch::Request::Session#fetch` when using non-string keys. *Jeremy Friesen* ## Rails 4.2.4 (August 24, 2015) ## * ActionController::TestSession now accepts a default value as well as a block for generating a default value based off the key provided. This fixes calls to session#fetch in ApplicationController instances that take more two arguments or a block from raising `ArgumentError: wrong number of arguments (2 for 1)` when performing controller tests. *Matthew Gerrior* * Fix to keep original header instance in `ActionDispatch::SSL` `ActionDispatch::SSL` changes headers to `Hash`. So some headers will be broken if there are some middlewares on `ActionDispatch::SSL` and if it uses `Rack::Utils::HeaderHash`. *Fumiaki Matsushima* ## Rails 4.2.3 (June 25, 2015) ## * Fix rake routes not showing the right format when nesting multiple routes. See #18373. *Ravil Bayramgalin* * Fix regression where a gzip file response would have a Content-type, even when it was a 304 status code. See #19271. *Kohei Suzuki* * Fix handling of empty X_FORWARDED_HOST header in raw_host_with_port Previously, an empty X_FORWARDED_HOST header would cause Actiondispatch::Http:URL.raw_host_with_port to return nil, causing Actiondispatch::Http:URL.host to raise a NoMethodError. *Adam Forsyth* * Fallback to `ENV['RAILS_RELATIVE_URL_ROOT']` in `url_for`. Fixed an issue where the `RAILS_RELATIVE_URL_ROOT` environment variable is not prepended to the path when `url_for` is called. If `SCRIPT_NAME` (used by Rack) is set, it takes precedence. Fixes #5122. *Yasyf Mohamedali* * Fix regression in functional tests. Responses should have default headers assigned. See #18423. *Jeremy Kemper*, *Yves Senn* ## Rails 4.2.2 (June 16, 2015) ## * No Changes * ## Rails 4.2.1 (March 19, 2015) ## * Non-string authenticity tokens do not raise NoMethodError when decoding the masked token. *Ville Lautanala* * Explicitly ignored wildcard verbs when searching for HEAD routes before fallback Fixes an issue where a mounted rack app at root would intercept the HEAD request causing an incorrect behavior during the fall back to GET requests. Example: ```ruby draw do get '/home' => 'test#index' mount rack_app, at: '/' end head '/home' assert_response :success ``` In this case, a HEAD request runs through the routes the first time and fails to match anything. Then, it runs through the list with the fallback and matches `get '/home'`. The original behavior would match the rack app in the first pass. *Terence Sun* * Preserve default format when generating URLs Fixes an issue that would cause the format set in default_url_options to be lost when generating URLs with fewer positional arguments than parameters in the route definition. Backport of #18627 *Tekin Suleyman*, *Dominic Baggott* * Default headers, removed in controller actions, are no longer reapplied on the test response. *Jonas Baumann* * Ensure `append_info_to_payload` is called even if an exception is raised. Fixes an issue where when an exception is raised in the request the additonal payload data is not available. See: * #14903 * https://github.com/roidrage/lograge/issues/37 *Dieter Komendera*, *Margus Pärt* * Correctly rely on the response's status code to handle calls to `head`. *Robin Dupret* * Using `head` method returns empty response_body instead of returning a single space " ". The old behavior was added as a workaround for a bug in an early version of Safari, where the HTTP headers are not returned correctly if the response body has a 0-length. This is been fixed since and the workaround is no longer necessary. Fixes #18253. *Prathamesh Sonpatki* * Fix how polymorphic routes works with objects that implement `to_model`. *Travis Grathwell* * Fixed handling of positional url helper arguments when `format: false`. Fixes #17819. *Andrew White*, *Tatiana Soukiassian* * Fixed usage of optional scopes in URL helpers. *Alex Robbin* ## Rails 4.2.0 (December 20, 2014) ## * Add `ActionController::Parameters#to_unsafe_h` to return an unfiltered `Hash` representation of Parameters object. This is now a preferred way to retrieve unfiltered parameters as we will stop inheriting `AC::Parameters` object in Rails 5.0. *Prem Sichanugrist* * Restore handling of a bare `Authorization` header, without `token=` prefix. Fixes #17108. *Guo Xiang Tan* * Deprecate use of string keys in URL helpers. Use symbols instead. Fixes #16958. *Byron Bischoff*, *Melanie Gilman* * Deprecate the `only_path` option on `*_path` helpers. In cases where this option is set to `true`, the option is redundant and can be safely removed; otherwise, the corresponding `*_url` helper should be used instead. Fixes #17294. *Dan Olson*, *Godfrey Chan* * Improve Journey compliance to RFC 3986. The scanner in Journey failed to recognize routes that use literals from the sub-delims section of RFC 3986. It's now able to parse those authorized delimiters and route as expected. Fixes #17212. *Nicolas Cavigneaux* * Deprecate implicit Array conversion for Response objects. It was added (using `#to_ary`) so we could conveniently use implicit splatting: status, headers, body = response But it also means `response + response` works and `[response].flatten` cascades down to the Rack body. Nonsense behavior. Instead, rely on explicit conversion and splatting with `#to_a`: status, header, body = *response *Jeremy Kemper* * Don't rescue `IPAddr::InvalidAddressError`. `IPAddr::InvalidAddressError` does not exist in Ruby 1.9.3 and fails for JRuby in 1.9 mode. *Peter Suschlik* * Fix bug where the router would ignore any constraints added to redirect routes. Fixes #16605. *Agis Anastasopoulos* * Allow `config.action_dispatch.trusted_proxies` to accept an IPAddr object. Example: # config/environments/production.rb config.action_dispatch.trusted_proxies = IPAddr.new('4.8.15.0/16') *Sam Aarons* * Avoid duplicating routes for HEAD requests. Instead of duplicating the routes, we will first match the HEAD request to HEAD routes. If no match is found, we will then map the HEAD request to GET routes. *Guo Xiang Tan*, *Andrew White* * Requests that hit `ActionDispatch::Static` can now take advantage of gzipped assets on disk. By default a gzip asset will be served if the client supports gzip and a compressed file is on disk. *Richard Schneeman* * `ActionController::Parameters` will stop inheriting from `Hash` and `HashWithIndifferentAccess` in the next major release. If you use any method that is not available on `ActionController::Parameters` you should consider calling `#to_h` to convert it to a `Hash` first before calling that method. *Prem Sichanugrist* * `ActionController::Parameters#to_h` now returns a `Hash` with unpermitted keys removed. This change is to reflect on a security concern where some method performed on an `ActionController::Parameters` may yield a `Hash` object which does not maintain `permitted?` status. If you would like to get a `Hash` with all the keys intact, duplicate and mark it as permitted before calling `#to_h`. params = ActionController::Parameters.new({ name: 'Senjougahara Hitagi', oddity: 'Heavy stone crab' }) params.to_h # => {} unsafe_params = params.dup.permit! unsafe_params.to_h # => {"name"=>"Senjougahara Hitagi", "oddity"=>"Heavy stone crab"} safe_params = params.permit(:name) safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} This change is consider a stopgap as we cannot change the code to stop `ActionController::Parameters` to inherit from `HashWithIndifferentAccess` in the next minor release. *Prem Sichanugrist* * Deprecated `TagAssertions`. *Kasper Timm Hansen* * Use the Active Support JSON encoder for cookie jars using the `:json` or `:hybrid` serializer. This allows you to serialize custom Ruby objects into cookies by defining the `#as_json` hook on such objects. Fixes #16520. *Godfrey Chan* * Add `config.action_dispatch.cookies_digest` option for setting custom digest. The default remains the same - 'SHA1'. *Åukasz StrzaÅ‚kowski* * Move `respond_with` (and the class-level `respond_to`) to the `responders` gem. *José Valim* * When your templates change, browser caches bust automatically. New default: the template digest is automatically included in your ETags. When you call `fresh_when @post`, the digest for `posts/show.html.erb` is mixed in so future changes to the HTML will blow HTTP caches for you. This makes it easy to HTTP-cache many more of your actions. If you render a different template, you can now pass the `:template` option to include its digest instead: fresh_when @post, template: 'widgets/show' Pass `template: false` to skip the lookup. To turn this off entirely, set: config.action_controller.etag_with_template_digest = false *Jeremy Kemper* * Remove deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError` in favor of `AbstractController::Helpers::MissingHelperError`. *Yves Senn* * Fix `assert_template` not being able to assert that no files were rendered. *Guo Xiang Tan* * Extract source code for the entire exception stack trace for better debugging and diagnosis. *Ryan Dao* * Allows ActionDispatch::Request::LOCALHOST to match any IPv4 127.0.0.0/8 loopback address. *Earl St Sauver*, *Sven Riedel* * Preserve original path in `ShowExceptions` middleware by stashing it as `env["action_dispatch.original_path"]` `ActionDispatch::ShowExceptions` overwrites `PATH_INFO` with the status code for the exception defined in `ExceptionWrapper`, so the path the user was visiting when an exception occurred was not previously available to any custom exceptions_app. The original `PATH_INFO` is now stashed in `env["action_dispatch.original_path"]`. *Grey Baker* * Use `String#bytesize` instead of `String#size` when checking for cookie overflow. *Agis Anastasopoulos* * `render nothing: true` or rendering a `nil` body no longer add a single space to the response body. The old behavior was added as a workaround for a bug in an early version of Safari, where the HTTP headers are not returned correctly if the response body has a 0-length. This is been fixed since and the workaround is no longer necessary. Use `render body: ' '` if the old behavior is desired. See #14883 for details. *Godfrey Chan* * Prepend a JS comment to JSONP callbacks. Addresses CVE-2014-4671 ("Rosetta Flash"). *Greg Campbell* * Because URI paths may contain non US-ASCII characters we need to force the encoding of any unescaped URIs to UTF-8 if they are US-ASCII. This essentially replicates the functionality of the monkey patch to URI.parser.unescape in active_support/core_ext/uri.rb. Fixes #16104. *Karl Entwistle* * Generate shallow paths for all children of shallow resources. Fixes #15783. *Seb Jacobs* * JSONP responses are now rendered with the `text/javascript` content type when rendering through a `respond_to` block. Fixes #15081. *Lucas Mazza* * Add `config.action_controller.always_permitted_parameters` to configure which parameters are permitted globally. The default value of this configuration is `['controller', 'action']`. *Gary S. Weaver*, *Rafael Chacon* * Fix env['PATH_INFO'] missing leading slash when a rack app mounted at '/'. Fixes #15511. *Larry Lv* * ActionController::Parameters#require now accepts `false` values. Fixes #15685. *Sergio Romano* * With authorization header `Authorization: Token token=`, `authenticate` now recognize token as nil, instead of "token". Fixes #14846. *Larry Lv* * Ensure the controller is always notified as soon as the client disconnects during live streaming, even when the controller is blocked on a write. *Nicholas Jakobsen*, *Matthew Draper* * Routes specifying 'to:' must be a string that contains a "#" or a rack application. Use of a symbol should be replaced with `action: symbol`. Use of a string without a "#" should be replaced with `controller: string`. *Aaron Patterson* * Fix URL generation with `:trailing_slash` such that it does not add a trailing slash after `.:format` *Dan Langevin* * Build full URI as string when processing path in integration tests for performance reasons. One consequence of this is that the leading slash is now required in integration test `process` helpers, whereas previously it could be omitted. The fact that this worked was a unintended consequence of the implementation and was never an intentional feature. *Guo Xiang Tan* * Fix `'Stack level too deep'` when rendering `head :ok` in an action method called 'status' in a controller. Fixes #13905. *Christiaan Van den Poel* * Add MKCALENDAR HTTP method (RFC 4791). *Sergey Karpesh* * Instrument fragment cache metrics. Adds `:controller`: and `:action` keys to the instrumentation payload for the `*_fragment.action_controller` notifications. This allows tracking e.g. the fragment cache hit rates for each controller action. *Daniel Schierbeck* * Always use the provided port if the protocol is relative. Fixes #15043. *Guilherme Cavalcanti*, *Andrew White* * Moved `params[request_forgery_protection_token]` into its own method and improved tests. Fixes #11316. *Tom Kadwill* * Added verification of route constraints given as a Proc or an object responding to `:matches?`. Previously, when given an non-complying object, it would just silently fail to enforce the constraint. It will now raise an `ArgumentError` when setting up the routes. *Xavier Defrang* * Properly treat the entire IPv6 User Local Address space as private for purposes of remote IP detection. Also handle uppercase private IPv6 addresses. Fixes #12638. *Caleb Spare* * Fixed an issue with migrating legacy json cookies. Previously, the `VerifyAndUpgradeLegacySignedMessage` assumes all incoming cookies are marshal-encoded. This is not the case when `secret_token` is used in conjunction with the `:json` or `:hybrid` serializer. In those case, when upgrading to use `secret_key_base`, this would cause a `TypeError: incompatible marshal file format` and a 500 error for the user. Fixes #14774. *Godfrey Chan* * Make URL escaping more consistent: 1. Escape '%' characters in URLs - only unescaped data should be passed to URL helpers 2. Add an `escape_segment` helper to `Router::Utils` that escapes '/' characters 3. Use `escape_segment` rather than `escape_fragment` in optimized URL generation 4. Use `escape_segment` rather than `escape_path` in URL generation For point 4 there are two exceptions. Firstly, when a route uses wildcard segments (e.g. `*foo`) then we use `escape_path` as the value may contain '/' characters. This means that wildcard routes can't be optimized. Secondly, if a `:controller` segment is used in the path then this uses `escape_path` as the controller may be namespaced. Fixes #14629, #14636 and #14070. *Andrew White*, *Edho Arief* * Add alias `ActionDispatch::Http::UploadedFile#to_io` to `ActionDispatch::Http::UploadedFile#tempfile`. *Tim Linquist* * Returns null type format when format is not know and controller is using `any` format block. Fixes #14462. *Rafael Mendonça França* * Improve routing error page with fuzzy matching search. *Winston* * Only make deeply nested routes shallow when parent is shallow. Fixes #14684. *Andrew White*, *James Coglan* * Append link to bad code to backtrace when exception is `SyntaxError`. *Boris Kuznetsov* * Swapped the parameters of assert_equal in `assert_select` so that the proper values were printed correctly. Fixes #14422. *Vishal Lal* * The method `shallow?` returns false if the parent resource is a singleton so we need to check if we're not inside a nested scope before copying the :path and :as options to their shallow equivalents. Fixes #14388. *Andrew White* * Make logging of CSRF failures optional (but on by default) with the `log_warning_on_csrf_failure` configuration setting in `ActionController::RequestForgeryProtection`. *John Barton* * Fix URL generation in controller tests with request-dependent `default_url_options` methods. *Tony Wooster* Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionpack/CHANGELOG.md) for previous changes. rails-4.2.6/actionpack/MIT-LICENSE000066400000000000000000000020621266740050600164130ustar00rootroot00000000000000Copyright (c) 2004-2014 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rails-4.2.6/actionpack/README.rdoc000066400000000000000000000036411266740050600165710ustar00rootroot00000000000000= Action Pack -- From request to response Action Pack is a framework for handling and responding to web requests. It provides mechanisms for *routing* (mapping request URLs to actions), defining *controllers* that implement actions, and generating responses by rendering *views*, which are templates of various formats. In short, Action Pack provides the view and controller layers in the MVC paradigm. It consists of several modules: * Action Dispatch, which parses information about the web request, handles routing as defined by the user, and does advanced processing related to HTTP such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies, handling HTTP caching logic, cookies and sessions. * Action Controller, which provides a base controller class that can be subclassed to implement filters and actions to handle requests. The result of an action is typically content generated from views. With the Ruby on Rails framework, users only directly interface with the Action Controller module. Necessary Action Dispatch functionality is activated by default and Action View rendering is implicitly triggered by Action Controller. However, these modules are designed to function on their own and can be used outside of Rails. == Download and installation The latest version of Action Pack can be installed with RubyGems: % [sudo] gem install actionpack Source code can be downloaded as part of the Rails project on GitHub * https://github.com/rails/rails/tree/4-2-stable/actionpack == License Action Pack is released under the MIT license: * http://www.opensource.org/licenses/MIT == Support API documentation is at * http://api.rubyonrails.org Bug reports can be filed for the Ruby on Rails project here: * https://github.com/rails/rails/issues Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core rails-4.2.6/actionpack/Rakefile000066400000000000000000000023101266740050600164200ustar00rootroot00000000000000require 'rake/testtask' require 'rubygems/package_task' test_files = Dir.glob('test/**/*_test.rb') desc "Default Task" task :default => :test # Run the unit tests Rake::TestTask.new do |t| t.libs << 'test' # make sure we include the tests in alphabetical order as on some systems # this will not happen automatically and the tests (as a whole) will error t.test_files = test_files.sort t.warning = true t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end namespace :test do task :isolated do test_files.all? do |file| sh(Gem.ruby, '-w', '-Ilib:test', file) end or raise "Failures" end end spec = eval(File.read('actionpack.gemspec')) Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end desc "Release to rubygems" task :release => :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define Rake::Task['gem:push'].invoke end task :lines do load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics' files = FileList["lib/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end rule '.rb' => '.y' do |t| sh "racc -l -o #{t.name} #{t.source}" end task compile: 'lib/action_dispatch/journey/parser.rb' rails-4.2.6/actionpack/actionpack.gemspec000066400000000000000000000021531266740050600204410ustar00rootroot00000000000000version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'actionpack' s.version = version s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.' s.required_ruby_version = '>= 1.9.3' s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] s.require_path = 'lib' s.requirements << 'none' s.add_dependency 'activesupport', version s.add_dependency 'rack', '~> 1.6' s.add_dependency 'rack-test', '~> 0.6.2' s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2' s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' s.add_dependency 'actionview', version s.add_development_dependency 'activemodel', version end rails-4.2.6/actionpack/lib/000077500000000000000000000000001266740050600155255ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/abstract_controller.rb000066400000000000000000000007711266740050600221250ustar00rootroot00000000000000require 'action_pack' require 'active_support/rails' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/anonymous' require 'active_support/i18n' module AbstractController extend ActiveSupport::Autoload autoload :Base autoload :Callbacks autoload :Collector autoload :DoubleRenderError, "abstract_controller/rendering" autoload :Helpers autoload :Logger autoload :Rendering autoload :Translation autoload :AssetPaths autoload :UrlFor end rails-4.2.6/actionpack/lib/abstract_controller/000077500000000000000000000000001266740050600215735ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/abstract_controller/asset_paths.rb000066400000000000000000000004111266740050600244320ustar00rootroot00000000000000module AbstractController module AssetPaths #:nodoc: extend ActiveSupport::Concern included do config_accessor :asset_host, :assets_dir, :javascripts_dir, :stylesheets_dir, :default_asset_host_protocol, :relative_url_root end end end rails-4.2.6/actionpack/lib/abstract_controller/base.rb000066400000000000000000000232011266740050600230300ustar00rootroot00000000000000require 'erubis' require 'set' require 'active_support/configurable' require 'active_support/descendants_tracker' require 'active_support/core_ext/module/anonymous' module AbstractController class Error < StandardError #:nodoc: end # Raised when a non-existing controller action is triggered. class ActionNotFound < StandardError end # AbstractController::Base is a low-level API. Nobody should be # using it directly, and subclasses (like ActionController::Base) are # expected to provide their own +render+ method, since rendering means # different things depending on the context. class Base attr_internal :response_body attr_internal :action_name attr_internal :formats include ActiveSupport::Configurable extend ActiveSupport::DescendantsTracker undef_method :not_implemented class << self attr_reader :abstract alias_method :abstract?, :abstract # Define a controller as abstract. See internal_methods for more # details. def abstract! @abstract = true end def inherited(klass) # :nodoc: # Define the abstract ivar on subclasses so that we don't get # uninitialized ivar warnings unless klass.instance_variable_defined?(:@abstract) klass.instance_variable_set(:@abstract, false) end super end # A list of all internal methods for a controller. This finds the first # abstract superclass of a controller, and gets a list of all public # instance methods on that abstract class. Public instance methods of # a controller would normally be considered action methods, so methods # declared on abstract classes are being removed. # (ActionController::Metal and ActionController::Base are defined as abstract) def internal_methods controller = self controller = controller.superclass until controller.abstract? controller.public_instance_methods(true) end # The list of hidden actions. Defaults to an empty array. # This can be modified by other modules or subclasses # to specify particular actions as hidden. # # ==== Returns # * Array - An array of method names that should not be considered actions. def hidden_actions [] end # A list of method names that should be considered actions. This # includes all public instance methods on a controller, less # any internal methods (see #internal_methods), adding back in # any methods that are internal, but still exist on the class # itself. Finally, #hidden_actions are removed. # # ==== Returns # * Set - A set of all methods that should be considered actions. def action_methods @action_methods ||= begin # All public instance methods of this class, including ancestors methods = (public_instance_methods(true) - # Except for public instance methods of Base and its ancestors internal_methods + # Be sure to include shadowed public instance methods of this class public_instance_methods(false)).uniq.map { |x| x.to_s } - # And always exclude explicitly hidden actions hidden_actions.to_a # Clear out AS callback method pollution Set.new(methods.reject { |method| method =~ /_one_time_conditions/ }) end end # action_methods are cached and there is sometimes need to refresh # them. clear_action_methods! allows you to do that, so next time # you run action_methods, they will be recalculated def clear_action_methods! @action_methods = nil end # Returns the full controller name, underscored, without the ending Controller. # For instance, MyApp::MyPostsController would return "my_app/my_posts" for # controller_path. # # ==== Returns # * String def controller_path @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous? end # Refresh the cached action_methods when a new action_method is added. def method_added(name) super clear_action_methods! end end abstract! # Calls the action going through the entire action dispatch stack. # # The actual method that is called is determined by calling # #method_for_action. If no method can handle the action, then an # AbstractController::ActionNotFound error is raised. # # ==== Returns # * self def process(action, *args) @_action_name = action.to_s unless action_name = _find_action_name(@_action_name) raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}" end @_response_body = nil process_action(action_name, *args) end # Delegates to the class' #controller_path def controller_path self.class.controller_path end # Delegates to the class' #action_methods def action_methods self.class.action_methods end # Returns true if a method for the action is available and # can be dispatched, false otherwise. # # Notice that action_methods.include?("foo") may return # false and available_action?("foo") returns true because # this method considers actions that are also available # through other means, for example, implicit render ones. # # ==== Parameters # * action_name - The name of an action to be tested # # ==== Returns # * TrueClass, FalseClass def available_action?(action_name) _find_action_name(action_name).present? end # Returns true if the given controller is capable of rendering # a path. A subclass of +AbstractController::Base+ # may return false. An Email controller for example does not # support paths, only full URLs. def self.supports_path? true end private # Returns true if the name can be considered an action because # it has a method defined in the controller. # # ==== Parameters # * name - The name of an action to be tested # # ==== Returns # * TrueClass, FalseClass # # :api: private def action_method?(name) self.class.action_methods.include?(name) end # Call the action. Override this in a subclass to modify the # behavior around processing an action. This, and not #process, # is the intended way to override action dispatching. # # Notice that the first argument is the method to be dispatched # which is *not* necessarily the same as the action name. def process_action(method_name, *args) send_action(method_name, *args) end # Actually call the method associated with the action. Override # this method if you wish to change how action methods are called, # not to add additional behavior around it. For example, you would # override #send_action if you want to inject arguments into the # method. alias send_action send # If the action name was not found, but a method called "action_missing" # was found, #method_for_action will return "_handle_action_missing". # This method calls #action_missing with the current action name. def _handle_action_missing(*args) action_missing(@_action_name, *args) end # Takes an action name and returns the name of the method that will # handle the action. # # It checks if the action name is valid and returns false otherwise. # # See method_for_action for more information. # # ==== Parameters # * action_name - An action name to find a method name for # # ==== Returns # * string - The name of the method that handles the action # * false - No valid method name could be found. # Raise AbstractController::ActionNotFound. def _find_action_name(action_name) _valid_action_name?(action_name) && method_for_action(action_name) end # Takes an action name and returns the name of the method that will # handle the action. In normal cases, this method returns the same # name as it receives. By default, if #method_for_action receives # a name that is not an action, it will look for an #action_missing # method and return "_handle_action_missing" if one is found. # # Subclasses may override this method to add additional conditions # that should be considered an action. For instance, an HTTP controller # with a template matching the action name is considered to exist. # # If you override this method to handle additional cases, you may # also provide a method (like _handle_method_missing) to handle # the case. # # If none of these conditions are true, and method_for_action # returns nil, an AbstractController::ActionNotFound exception will be raised. # # ==== Parameters # * action_name - An action name to find a method name for # # ==== Returns # * string - The name of the method that handles the action # * nil - No method name could be found. def method_for_action(action_name) if action_method?(action_name) action_name elsif respond_to?(:action_missing, true) "_handle_action_missing" end end # Checks if the action name is valid and returns false otherwise. def _valid_action_name?(action_name) !action_name.to_s.include? File::SEPARATOR end end end rails-4.2.6/actionpack/lib/abstract_controller/callbacks.rb000066400000000000000000000153451266740050600240470ustar00rootroot00000000000000module AbstractController module Callbacks extend ActiveSupport::Concern # Uses ActiveSupport::Callbacks as the base functionality. For # more details on the whole callback system, read the documentation # for ActiveSupport::Callbacks. include ActiveSupport::Callbacks included do define_callbacks :process_action, terminator: ->(controller,_) { controller.response_body }, skip_after_callbacks_if_terminated: true end # Override AbstractController::Base's process_action to run the # process_action callbacks around the normal behavior. def process_action(*args) run_callbacks(:process_action) do super end end module ClassMethods # If :only or :except are used, convert the options into the # :unless and :if options of ActiveSupport::Callbacks. # The basic idea is that :only => :index gets converted to # :if => proc {|c| c.action_name == "index" }. # # ==== Options # * only - The callback should be run only for this action # * except - The callback should be run for all actions except this action def _normalize_callback_options(options) _normalize_callback_option(options, :only, :if) _normalize_callback_option(options, :except, :unless) end def _normalize_callback_option(options, from, to) # :nodoc: if from = options[from] from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ") options[to] = Array(options[to]).unshift(from) end end # Skip before, after, and around action callbacks matching any of the names. # # ==== Parameters # * names - A list of valid names that could be used for # callbacks. Note that skipping uses Ruby equality, so it's # impossible to skip a callback defined using an anonymous proc # using #skip_action_callback def skip_action_callback(*names) skip_before_action(*names) skip_after_action(*names) skip_around_action(*names) end alias_method :skip_filter, :skip_action_callback # Take callback names and an optional callback proc, normalize them, # then call the block with each callback. This allows us to abstract # the normalization across several methods that use it. # # ==== Parameters # * callbacks - An array of callbacks, with an optional # options hash as the last parameter. # * block - A proc that should be added to the callbacks. # # ==== Block Parameters # * name - The callback to be added # * options - A hash of options to be used when adding the callback def _insert_callbacks(callbacks, block = nil) options = callbacks.extract_options! _normalize_callback_options(options) callbacks.push(block) if block callbacks.each do |callback| yield callback, options end end ## # :method: before_action # # :call-seq: before_action(names, block) # # Append a callback before actions. See _insert_callbacks for parameter details. ## # :method: prepend_before_action # # :call-seq: prepend_before_action(names, block) # # Prepend a callback before actions. See _insert_callbacks for parameter details. ## # :method: skip_before_action # # :call-seq: skip_before_action(names) # # Skip a callback before actions. See _insert_callbacks for parameter details. ## # :method: append_before_action # # :call-seq: append_before_action(names, block) # # Append a callback before actions. See _insert_callbacks for parameter details. ## # :method: after_action # # :call-seq: after_action(names, block) # # Append a callback after actions. See _insert_callbacks for parameter details. ## # :method: prepend_after_action # # :call-seq: prepend_after_action(names, block) # # Prepend a callback after actions. See _insert_callbacks for parameter details. ## # :method: skip_after_action # # :call-seq: skip_after_action(names) # # Skip a callback after actions. See _insert_callbacks for parameter details. ## # :method: append_after_action # # :call-seq: append_after_action(names, block) # # Append a callback after actions. See _insert_callbacks for parameter details. ## # :method: around_action # # :call-seq: around_action(names, block) # # Append a callback around actions. See _insert_callbacks for parameter details. ## # :method: prepend_around_action # # :call-seq: prepend_around_action(names, block) # # Prepend a callback around actions. See _insert_callbacks for parameter details. ## # :method: skip_around_action # # :call-seq: skip_around_action(names) # # Skip a callback around actions. See _insert_callbacks for parameter details. ## # :method: append_around_action # # :call-seq: append_around_action(names, block) # # Append a callback around actions. See _insert_callbacks for parameter details. # set up before_action, prepend_before_action, skip_before_action, etc. # for each of before, after, and around. [:before, :after, :around].each do |callback| define_method "#{callback}_action" do |*names, &blk| _insert_callbacks(names, blk) do |name, options| set_callback(:process_action, callback, name, options) end end alias_method :"#{callback}_filter", :"#{callback}_action" define_method "prepend_#{callback}_action" do |*names, &blk| _insert_callbacks(names, blk) do |name, options| set_callback(:process_action, callback, name, options.merge(:prepend => true)) end end alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action" # Skip a before, after or around callback. See _insert_callbacks # for details on the allowed parameters. define_method "skip_#{callback}_action" do |*names| _insert_callbacks(names) do |name, options| skip_callback(:process_action, callback, name, options) end end alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action" # *_action is the same as append_*_action alias_method :"append_#{callback}_action", :"#{callback}_action" alias_method :"append_#{callback}_filter", :"#{callback}_action" end end end end rails-4.2.6/actionpack/lib/abstract_controller/collector.rb000066400000000000000000000027721266740050600241160ustar00rootroot00000000000000require "action_dispatch/http/mime_type" module AbstractController module Collector def self.generate_method_for_mime(mime) sym = mime.is_a?(Symbol) ? mime : mime.to_sym const = sym.upcase class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym}(*args, &block) # def html(*args, &block) custom(Mime::#{const}, *args, &block) # custom(Mime::HTML, *args, &block) end # end RUBY end Mime::SET.each do |mime| generate_method_for_mime(mime) end Mime::Type.register_callback do |mime| generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym) end protected def method_missing(symbol, &block) const_name = symbol.upcase unless Mime.const_defined?(const_name) raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \ "http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \ "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \ "be sure to nest your variant response within a format response: " \ "format.html { |html| html.tablet { ... } }" end mime_constant = Mime.const_get(const_name) if Mime::SET.include?(mime_constant) AbstractController::Collector.generate_method_for_mime(mime_constant) send(symbol, &block) else super end end end end rails-4.2.6/actionpack/lib/abstract_controller/helpers.rb000066400000000000000000000155051266740050600235700ustar00rootroot00000000000000require 'active_support/dependencies' module AbstractController module Helpers extend ActiveSupport::Concern included do class_attribute :_helpers self._helpers = Module.new class_attribute :_helper_methods self._helper_methods = Array.new end class MissingHelperError < LoadError def initialize(error, path) @error = error @path = "helpers/#{path}.rb" set_backtrace error.backtrace if error.path =~ /^#{path}(\.rb)?$/ super("Missing helper file helpers/%s.rb" % path) else raise error end end end module ClassMethods # When a class is inherited, wrap its helper module in a new module. # This ensures that the parent class's module can be changed # independently of the child class's. def inherited(klass) helpers = _helpers klass._helpers = Module.new { include helpers } klass.class_eval { default_helper_module! } unless klass.anonymous? super end # Declare a controller method as a helper. For example, the following # makes the +current_user+ controller method available to the view: # class ApplicationController < ActionController::Base # helper_method :current_user, :logged_in? # # def current_user # @current_user ||= User.find_by(id: session[:user]) # end # # def logged_in? # current_user != nil # end # end # # In a view: # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> # # ==== Parameters # * method[, method] - A name or names of a method on the controller # to be made available on the view. def helper_method(*meths) meths.flatten! self._helper_methods += meths meths.each do |meth| _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 def #{meth}(*args, &blk) # def current_user(*args, &blk) controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk) end # end ruby_eval end end # The +helper+ class method can take a series of helper module names, a block, or both. # # ==== Options # * *args - Module, Symbol, String # * block - A block defining helper methods # # When the argument is a module it will be included directly in the template class. # helper FooHelper # => includes FooHelper # # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file # and include the module in the template class. The second form illustrates how to include custom helpers # when working with namespaced controllers, or other cases where the file containing the helper definition is not # in one of Rails' standard load paths: # helper :foo # => requires 'foo_helper' and includes FooHelper # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper # # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available # to the template. # # # One line # helper { def hello() "Hello, world!" end } # # # Multi-line # helper do # def foo(bar) # "#{bar} is the very best" # end # end # # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of # +symbols+, +strings+, +modules+ and blocks. # # helper(:three, BlindHelper) { def mice() 'mice' end } # def helper(*args, &block) modules_for_helpers(args).each do |mod| add_template_helper(mod) end _helpers.module_eval(&block) if block_given? end # Clears up all existing helpers in this class, only keeping the helper # with the same name as this class. def clear_helpers inherited_helper_methods = _helper_methods self._helpers = Module.new self._helper_methods = Array.new inherited_helper_methods.each { |meth| helper_method meth } default_helper_module! unless anonymous? end # Returns a list of modules, normalized from the acceptable kinds of # helpers with the following behavior: # # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", # and "foo_bar_helper.rb" is loaded using require_dependency. # # Module:: No further processing # # After loading the appropriate files, the corresponding modules # are returned. # # ==== Parameters # * args - An array of helpers # # ==== Returns # * Array - A normalized list of modules for the list of # helpers provided. def modules_for_helpers(args) args.flatten.map! do |arg| case arg when String, Symbol file_name = "#{arg.to_s.underscore}_helper" begin require_dependency(file_name) rescue LoadError => e raise AbstractController::Helpers::MissingHelperError.new(e, file_name) end mod_name = file_name.camelize begin mod_name.constantize rescue LoadError # dependencies.rb gives a similar error message but its wording is # not as clear because it mentions autoloading. To the user all it # matters is that a helper module couldn't be loaded, autoloading # is an internal mechanism that should not leak. raise NameError, "Couldn't find #{mod_name}, expected it to be defined in helpers/#{file_name}.rb" end when Module arg else raise ArgumentError, "helper must be a String, Symbol, or Module" end end end private # Makes all the (instance) methods in the helper module available to templates # rendered through this controller. # # ==== Parameters # * module - The module to include into the current helper module # for the class def add_template_helper(mod) _helpers.module_eval { include mod } end def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path rescue MissingSourceFile => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" end end end end rails-4.2.6/actionpack/lib/abstract_controller/logger.rb000066400000000000000000000003511266740050600233760ustar00rootroot00000000000000require "active_support/benchmarkable" module AbstractController module Logger #:nodoc: extend ActiveSupport::Concern included do config_accessor :logger include ActiveSupport::Benchmarkable end end end rails-4.2.6/actionpack/lib/abstract_controller/railties/000077500000000000000000000000001266740050600234075ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/abstract_controller/railties/routes_helpers.rb000066400000000000000000000011021266740050600267710ustar00rootroot00000000000000module AbstractController module Railties module RoutesHelpers def self.with(routes, include_path_helpers = true) Module.new do define_method(:inherited) do |klass| super(klass) if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) } klass.send(:include, namespace.railtie_routes_url_helpers(include_path_helpers)) else klass.send(:include, routes.url_helpers(include_path_helpers)) end end end end end end end rails-4.2.6/actionpack/lib/abstract_controller/rendering.rb000066400000000000000000000074361266740050600241070ustar00rootroot00000000000000require 'active_support/concern' require 'active_support/core_ext/class/attribute' require 'action_view' require 'action_view/view_paths' require 'set' module AbstractController class DoubleRenderError < Error DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." def initialize(message = nil) super(message || DEFAULT_MESSAGE) end end module Rendering extend ActiveSupport::Concern include ActionView::ViewPaths # Normalize arguments, options and then delegates render_to_body and # sticks the result in self.response_body. # :api: public def render(*args, &block) options = _normalize_render(*args, &block) self.response_body = render_to_body(options) _process_format(rendered_format, options) if rendered_format self.response_body end # Raw rendering of a template to a string. # # It is similar to render, except that it does not # set the response_body and it should be guaranteed # to always return a string. # # If a component extends the semantics of response_body # (as Action Controller extends it to be anything that # responds to the method each), this method needs to be # overridden in order to still return a string. # :api: plugin def render_to_string(*args, &block) options = _normalize_render(*args, &block) render_to_body(options) end # Performs the actual template rendering. # :api: public def render_to_body(options = {}) end # Returns Content-Type of rendered content # :api: public def rendered_format Mime::TEXT end DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %w( @_action_name @_response_body @_formats @_prefixes @_config @_view_context_class @_view_renderer @_lookup_context @_routes @_db_runtime ).map(&:to_sym) # This method should return a hash with assigns. # You can overwrite this configuration per controller. # :api: public def view_assigns protected_vars = _protected_ivars variables = instance_variables variables.reject! { |s| protected_vars.include? s } variables.each_with_object({}) { |name, hash| hash[name.slice(1, name.length)] = instance_variable_get(name) } end # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :file => "foo/bar". # :api: plugin def _normalize_args(action=nil, options={}) if action.respond_to?(:permitted?) if action.permitted? action else raise ArgumentError, "render parameters are not permitted" end elsif action.is_a?(Hash) action else options end end # Normalize options. # :api: plugin def _normalize_options(options) options end # Process extra options. # :api: plugin def _process_options(options) options end # Process the rendered format. # :api: private def _process_format(format, options = {}) end # Normalize args and options. # :api: private def _normalize_render(*args, &block) options = _normalize_args(*args, &block) #TODO: remove defined? when we restore AP <=> AV dependency if defined?(request) && request && request.variant.present? options[:variant] = request.variant end _normalize_options(options) options end def _protected_ivars # :nodoc: DEFAULT_PROTECTED_INSTANCE_VARIABLES end end end rails-4.2.6/actionpack/lib/abstract_controller/translation.rb000066400000000000000000000017361266740050600244650ustar00rootroot00000000000000module AbstractController module Translation # Delegates to I18n.translate. Also aliased as t. # # When the given key starts with a period, it will be scoped by the current # controller and action. So if you call translate(".foo") from # PeopleController#index, it will convert the call to # I18n.translate("people.index.foo"). This makes it less repetitive # to translate many keys within the same controller / action and gives you a # simple framework for scoping them consistently. def translate(*args) key = args.first if key.is_a?(String) && (key[0] == '.') key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }" args[0] = key end I18n.translate(*args) end alias :t :translate # Delegates to I18n.localize. Also aliased as l. def localize(*args) I18n.localize(*args) end alias :l :localize end end rails-4.2.6/actionpack/lib/abstract_controller/url_for.rb000066400000000000000000000016631266740050600235760ustar00rootroot00000000000000module AbstractController # Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class # has to provide a +RouteSet+ by implementing the _routes methods. Otherwise, an # exception will be raised. # # Note that this module is completely decoupled from HTTP - the only requirement is a valid # _routes implementation. module UrlFor extend ActiveSupport::Concern include ActionDispatch::Routing::UrlFor def _routes raise "In order to use #url_for, you must include routing helpers explicitly. " \ "For instance, `include Rails.application.routes.url_helpers`." end module ClassMethods def _routes nil end def action_methods @action_methods ||= begin if _routes super - _routes.named_routes.helper_names else super end end end end end end rails-4.2.6/actionpack/lib/action_controller.rb000066400000000000000000000030221266740050600215670ustar00rootroot00000000000000require 'active_support/rails' require 'abstract_controller' require 'action_dispatch' require 'action_controller/metal/live' require 'action_controller/metal/strong_parameters' module ActionController extend ActiveSupport::Autoload autoload :Base autoload :Caching autoload :Metal autoload :Middleware autoload_under "metal" do autoload :Compatibility autoload :ConditionalGet autoload :Cookies autoload :DataStreaming autoload :EtagWithTemplateDigest autoload :Flash autoload :ForceSSL autoload :Head autoload :Helpers autoload :HideActions autoload :HttpAuthentication autoload :ImplicitRender autoload :Instrumentation autoload :MimeResponds autoload :ParamsWrapper autoload :RackDelegation autoload :Redirecting autoload :Renderers autoload :Rendering autoload :RequestForgeryProtection autoload :Rescue autoload :Streaming autoload :StrongParameters autoload :Testing autoload :UrlFor end autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' def self.eager_load! super ActionController::Caching.eager_load! end end # Common Active Support usage in Action Controller require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/load_error' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/name_error' require 'active_support/core_ext/uri' require 'active_support/inflector' rails-4.2.6/actionpack/lib/action_controller/000077500000000000000000000000001266740050600212455ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_controller/base.rb000066400000000000000000000253531266740050600225140ustar00rootroot00000000000000require 'action_view' require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" module ActionController # Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed # on request and then either it renders a template or redirects to another action. An action is defined as a public method # on the controller, which will automatically be made accessible to the web-server through \Rails Routes. # # By default, only the ApplicationController in a \Rails application inherits from ActionController::Base. All other # controllers in turn inherit from ApplicationController. This gives you one class to configure things such as # request forgery protection and filtering of sensitive request parameters. # # A sample controller could look like this: # # class PostsController < ApplicationController # def index # @posts = Post.all # end # # def create # @post = Post.create params[:post] # redirect_to posts_path # end # end # # Actions, by default, render a template in the app/views directory corresponding to the name of the controller and action # after executing code in the action. For example, the +index+ action of the PostsController would render the # template app/views/posts/index.html.erb by default after populating the @posts instance variable. # # Unlike index, the create action will not render a template. After performing its main purpose (creating a # new post), it initiates a redirect instead. This redirect works by returning an external # "302 Moved" HTTP response that takes the user to the index action. # # These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect. # Most actions are variations on these themes. # # == Requests # # For every request, the router determines the value of the +controller+ and +action+ keys. These determine which controller # and action are called. The remaining request parameters, the session (if one is available), and the full request with # all the HTTP headers are made available to the action through accessor methods. Then the action is performed. # # The full request object is available via the request accessor and is primarily used to query for HTTP headers: # # def server_ip # location = request.env["REMOTE_ADDR"] # render plain: "This server hosted at #{location}" # end # # == Parameters # # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method # which returns a hash. For example, an action that was performed through /posts?category=All&limit=5 will include # { "category" => "All", "limit" => "5" } in params. # # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as: # # # # # A request stemming from a form holding these inputs will include { "post" => { "name" => "david", "address" => "hyacintvej" } }. # If the address input had been named post[address][street], the params would have included # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting. # # == Sessions # # Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted, # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at. # # You can place objects in the session by using the session method, which accesses a hash: # # session[:person] = Person.authenticate(user_name, password) # # And retrieved again through the same hash: # # Hello #{session[:person]} # # For removing objects from the session, you can either assign a single key to +nil+: # # # removes :person from session # session[:person] = nil # # or you can remove the entire session with +reset_session+. # # Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted. # This prevents the user from tampering with the session but also allows them to see its contents. # # Do not put secret information in cookie-based sessions! # # == Responses # # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response # object is generated automatically through the use of renders and redirects and requires no user intervention. # # == Renders # # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering # of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured. # The controller passes objects to the view by assigning instance variables: # # def show # @post = Post.find(params[:id]) # end # # Which are then automatically available to the view: # # Title: <%= @post.title %> # # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates # will use the manual rendering methods: # # def search # @results = Search.find(params[:query]) # case @results.count # when 0 then render action: "no_results" # when 1 then render action: "show" # when 2..10 then render action: "show_many" # end # end # # Read more about writing ERB and Builder templates in ActionView::Base. # # == Redirects # # Redirects are used to move from one action to another. For example, after a create action, which stores a blog entry to the # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're # going to reuse (and redirect to) a show action that we'll assume has already been created. The code might look like this: # # def create # @entry = Entry.new(params[:entry]) # if @entry.save # # The entry was saved correctly, redirect to show # redirect_to action: 'show', id: @entry.id # else # # things didn't go so well, do something else # end # end # # In this case, after saving our new entry to the database, the user is redirected to the show method, which is then executed. # Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action), # and not some internal re-routing which calls both "create" and then "show" within one request. # # Learn more about redirect_to and what options you have in ActionController::Redirecting. # # == Calling multiple redirects or renders # # An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError: # # def do_something # redirect_to action: "elsewhere" # render action: "overthere" # raises DoubleRenderError # end # # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution. # # def do_something # redirect_to(action: "elsewhere") and return if monkeys.nil? # render action: "overthere" # won't be called if monkeys is nil # end # class Base < Metal abstract! # We document the request and response methods here because albeit they are # implemented in ActionController::Metal, the type of the returned objects # is unknown at that level. ## # :method: request # # Returns an ActionDispatch::Request instance that represents the # current request. ## # :method: response # # Returns an ActionDispatch::Response that represents the current # response. # Shortcut helper that returns all the modules included in # ActionController::Base except the ones passed as arguments: # # class MyBaseController < ActionController::Metal # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left| # include left # end # end # # This gives better control over what you want to exclude and makes it # easier to create a bare controller class, instead of listing the modules # required manually. def self.without_modules(*modules) modules = modules.map do |m| m.is_a?(Symbol) ? ActionController.const_get(m) : m end MODULES - modules end MODULES = [ AbstractController::Rendering, AbstractController::Translation, AbstractController::AssetPaths, Helpers, HideActions, UrlFor, Redirecting, ActionView::Layouts, Rendering, Renderers::All, ConditionalGet, EtagWithTemplateDigest, RackDelegation, Caching, MimeResponds, ImplicitRender, StrongParameters, Cookies, Flash, RequestForgeryProtection, ForceSSL, Streaming, DataStreaming, HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, # Before callbacks should also be executed the earliest as possible, so # also include them at the bottom. AbstractController::Callbacks, # Append rescue at the bottom to wrap as much as possible. Rescue, # Add instrumentations hooks at the bottom, to ensure they instrument # all the methods properly. Instrumentation, # Params wrapper should come before instrumentation so they are # properly showed in logs ParamsWrapper ] MODULES.each do |mod| include mod end # Define some internal variables that should not be propagated to the view. PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request, :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ] def _protected_ivars # :nodoc: PROTECTED_IVARS end def self.protected_instance_variables PROTECTED_IVARS end ActiveSupport.run_load_hooks(:action_controller, self) end end rails-4.2.6/actionpack/lib/action_controller/caching.rb000066400000000000000000000047711266740050600231770ustar00rootroot00000000000000require 'fileutils' require 'uri' require 'set' module ActionController # \Caching is a cheap way of speeding up slow applications by keeping the result of # calculations, renderings, and database calls around for subsequent requests. # # You can read more about each approach by clicking the modules below. # # Note: To turn off all caching, set # config.action_controller.perform_caching = false # # == \Caching stores # # All the caching stores from ActiveSupport::Cache are available to be used as backends # for Action Controller caching. # # Configuration examples (FileStore is the default): # # config.action_controller.cache_store = :memory_store # config.action_controller.cache_store = :file_store, '/path/to/cache/directory' # config.action_controller.cache_store = :mem_cache_store, 'localhost' # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211') # config.action_controller.cache_store = MyOwnStore.new('parameter') module Caching extend ActiveSupport::Concern extend ActiveSupport::Autoload eager_autoload do autoload :Fragments end module ConfigMethods def cache_store config.cache_store end def cache_store=(store) config.cache_store = ActiveSupport::Cache.lookup_store(store) end private def cache_configured? perform_caching && cache_store end end include RackDelegation include AbstractController::Callbacks include ConfigMethods include Fragments included do extend ConfigMethods config_accessor :default_static_extension self.default_static_extension ||= '.html' config_accessor :perform_caching self.perform_caching = true if perform_caching.nil? class_attribute :_view_cache_dependencies self._view_cache_dependencies = [] helper_method :view_cache_dependencies if respond_to?(:helper_method) end module ClassMethods def view_cache_dependency(&dependency) self._view_cache_dependencies += [dependency] end end def view_cache_dependencies self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact end protected # Convenience accessor. def cache(key, options = {}, &block) if cache_configured? cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) else yield end end end end rails-4.2.6/actionpack/lib/action_controller/caching/000077500000000000000000000000001266740050600226415ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_controller/caching/fragments.rb000066400000000000000000000077501266740050600251650ustar00rootroot00000000000000module ActionController module Caching # Fragment caching is used for caching various blocks within # views without caching the entire action as a whole. This is # useful when certain elements of an action change frequently or # depend on complicated state while other parts rarely change or # can be shared amongst multiple parties. The caching is done using # the +cache+ helper available in the Action View. See # ActionView::Helpers::CacheHelper for more information. # # While it's strongly recommended that you use key-based cache # expiration (see links in CacheHelper for more information), # it is also possible to manually expire caches. For example: # # expire_fragment('name_of_cache') module Fragments # Given a key (as described in +expire_fragment+), returns # a key suitable for use in reading, writing, or expiring a # cached fragment. All keys are prefixed with views/ and uses # ActiveSupport::Cache.expand_cache_key for the expansion. def fragment_cache_key(key) ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views) end # Writes +content+ to the location signified by # +key+ (see +expire_fragment+ for acceptable formats). def write_fragment(key, content, options = nil) return content unless cache_configured? key = fragment_cache_key(key) instrument_fragment_cache :write_fragment, key do content = content.to_str cache_store.write(key, content, options) end content end # Reads a cached fragment from the location signified by +key+ # (see +expire_fragment+ for acceptable formats). def read_fragment(key, options = nil) return unless cache_configured? key = fragment_cache_key(key) instrument_fragment_cache :read_fragment, key do result = cache_store.read(key, options) result.respond_to?(:html_safe) ? result.html_safe : result end end # Check if a cached fragment from the location signified by # +key+ exists (see +expire_fragment+ for acceptable formats). def fragment_exist?(key, options = nil) return unless cache_configured? key = fragment_cache_key(key) instrument_fragment_cache :exist_fragment?, key do cache_store.exist?(key, options) end end # Removes fragments from the cache. # # +key+ can take one of three forms: # # * String - This would normally take the form of a path, like # pages/45/notes. # * Hash - Treated as an implicit call to +url_for+, like # { controller: 'pages', action: 'notes', id: 45} # * Regexp - Will remove any fragment that matches, so # %r{pages/\d*/notes} might remove all notes. Make sure you # don't use anchors in the regex (^ or $) because # the actual filename matched looks like # ./cache/filename/path.cache. Note: Regexp expiration is # only supported on caches that can iterate over all keys (unlike # memcached). # # +options+ is passed through to the cache store's +delete+ # method (or delete_matched, for Regexp keys). def expire_fragment(key, options = nil) return unless cache_configured? key = fragment_cache_key(key) unless key.is_a?(Regexp) instrument_fragment_cache :expire_fragment, key do if key.is_a?(Regexp) cache_store.delete_matched(key, options) else cache_store.delete(key, options) end end end def instrument_fragment_cache(name, key) # :nodoc: payload = { controller: controller_name, action: action_name, key: key } ActiveSupport::Notifications.instrument("#{name}.action_controller", payload) { yield } end end end end rails-4.2.6/actionpack/lib/action_controller/log_subscriber.rb000066400000000000000000000053611266740050600246030ustar00rootroot00000000000000module ActionController class LogSubscriber < ActiveSupport::LogSubscriber INTERNAL_PARAMS = %w(controller action format _method only_path) def start_processing(event) return unless logger.info? payload = event.payload params = payload[:params].except(*INTERNAL_PARAMS) format = payload[:format] format = format.to_s.upcase if format.is_a?(Symbol) info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}" info " Parameters: #{params.inspect}" unless params.empty? end def process_action(event) info do payload = event.payload additions = ActionController::Base.log_process_action(payload) status = payload[:status] if status.nil? && payload[:exception].present? exception_class_name = payload[:exception].first status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" message << " (#{additions.join(" | ")})" unless additions.blank? message end end def halted_callback(event) info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" } end def send_file(event) info { "Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)" } end def redirect_to(event) info { "Redirected to #{event.payload[:location]}" } end def send_data(event) info { "Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)" } end def unpermitted_parameters(event) debug do unpermitted_keys = event.payload[:keys] "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}" end end def deep_munge(event) debug do "Value for params[:#{event.payload[:keys].join('][:')}] was set "\ "to nil, because it was one of [], [null] or [null, null, ...]. "\ "Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation "\ "for more information."\ end end %w(write_fragment read_fragment exist_fragment? expire_fragment expire_page write_page).each do |method| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(event) return unless logger.info? key_or_path = event.payload[:key] || event.payload[:path] human_name = #{method.to_s.humanize.inspect} info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)") end METHOD end def logger ActionController::Base.logger end end end ActionController::LogSubscriber.attach_to :action_controller rails-4.2.6/actionpack/lib/action_controller/metal.rb000066400000000000000000000152121266740050600226750ustar00rootroot00000000000000require 'active_support/core_ext/array/extract_options' require 'action_dispatch/middleware/stack' module ActionController # Extend ActionDispatch middleware stack to make it aware of options # allowing the following syntax in controllers: # # class PostsController < ApplicationController # use AuthenticationMiddleware, except: [:index, :show] # end # class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc: class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc: def initialize(klass, *args, &block) options = args.extract_options! @only = Array(options.delete(:only)).map(&:to_s) @except = Array(options.delete(:except)).map(&:to_s) args << options unless options.empty? super end def valid?(action) if @only.present? @only.include?(action) elsif @except.present? !@except.include?(action) else true end end end def build(action, app = Proc.new) action = action.to_s middlewares.reverse.inject(app) do |a, middleware| middleware.valid?(action) ? middleware.build(a) : a end end end # ActionController::Metal is the simplest possible controller, providing a # valid Rack interface without the additional niceties provided by # ActionController::Base. # # A sample metal controller might look like this: # # class HelloController < ActionController::Metal # def index # self.response_body = "Hello World!" # end # end # # And then to route requests to your metal controller, you would add # something like this to config/routes.rb: # # get 'hello', to: HelloController.action(:index) # # The +action+ method returns a valid Rack application for the \Rails # router to dispatch to. # # == Rendering Helpers # # ActionController::Metal by default provides no utilities for rendering # views, partials, or other responses aside from explicitly calling of # response_body=, content_type=, and status=. To # add the render helpers you're used to having in a normal controller, you # can do the following: # # class HelloController < ActionController::Metal # include AbstractController::Rendering # include ActionView::Layouts # append_view_path "#{Rails.root}/app/views" # # def index # render "hello/index" # end # end # # == Redirection Helpers # # To add redirection helpers to your metal controller, do the following: # # class HelloController < ActionController::Metal # include ActionController::Redirecting # include Rails.application.routes.url_helpers # # def index # redirect_to root_url # end # end # # == Other Helpers # # You can refer to the modules included in ActionController::Base to see # other features you can bring into your metal controller. # class Metal < AbstractController::Base abstract! attr_internal_writer :env def env @_env ||= {} end # Returns the last part of the controller's name, underscored, without the ending # Controller. For instance, PostsController returns posts. # Namespaces are left out, so Admin::PostsController returns posts as well. # # ==== Returns # * string def self.controller_name @controller_name ||= name.demodulize.sub(/Controller$/, '').underscore end # Delegates to the class' controller_name def controller_name self.class.controller_name end # The details below can be overridden to support a specific # Request and Response object. The default ActionController::Base # implementation includes RackDelegation, which makes a request # and response object available. You might wish to control the # environment and response manually for performance reasons. attr_internal :headers, :response, :request delegate :session, :to => "@_request" def initialize @_headers = {"Content-Type" => "text/html"} @_status = 200 @_request = nil @_response = nil @_routes = nil super end def params @_params ||= request.parameters end def params=(val) @_params = val end # Basic implementations for content_type=, location=, and headers are # provided to reduce the dependency on the RackDelegation module # in Renderer and Redirector. def content_type=(type) headers["Content-Type"] = type.to_s end def content_type headers["Content-Type"] end def location headers["Location"] end def location=(url) headers["Location"] = url end # Basic url_for that can be overridden for more robust functionality def url_for(string) string end def status @_status end alias :response_code :status # :nodoc: def status=(status) @_status = Rack::Utils.status_code(status) end def response_body=(body) body = [body] unless body.nil? || body.respond_to?(:each) super end # Tests if render or redirect has already happened. def performed? response_body || (response && response.committed?) end def dispatch(name, request) #:nodoc: @_request = request @_env = request.env @_env['action_controller.instance'] = self process(name) to_a end def to_a #:nodoc: response ? response.to_a : [status, headers, response_body] end class_attribute :middleware_stack self.middleware_stack = ActionController::MiddlewareStack.new def self.inherited(base) # :nodoc: base.middleware_stack = middleware_stack.dup super end # Pushes the given Rack middleware and its arguments to the bottom of the # middleware stack. def self.use(*args, &block) middleware_stack.use(*args, &block) end # Alias for +middleware_stack+. def self.middleware middleware_stack end # Makes the controller a Rack endpoint that runs the action in the given # +env+'s +action_dispatch.request.path_parameters+ key. def self.call(env) req = ActionDispatch::Request.new env action(req.path_parameters[:action]).call(env) end # Returns a Rack endpoint for the given action name. def self.action(name, klass = ActionDispatch::Request) if middleware_stack.any? middleware_stack.build(name) do |env| new.dispatch(name, klass.new(env)) end else lambda { |env| new.dispatch(name, klass.new(env)) } end end end end rails-4.2.6/actionpack/lib/action_controller/metal/000077500000000000000000000000001266740050600223475ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_controller/metal/conditional_get.rb000066400000000000000000000165611266740050600260470ustar00rootroot00000000000000require 'active_support/core_ext/hash/keys' module ActionController module ConditionalGet extend ActiveSupport::Concern include RackDelegation include Head included do class_attribute :etaggers self.etaggers = [] end module ClassMethods # Allows you to consider additional controller-wide information when generating an ETag. # For example, if you serve pages tailored depending on who's logged in at the moment, you # may want to add the current user id to be part of the ETag to prevent authorized displaying # of cached pages. # # class InvoicesController < ApplicationController # etag { current_user.try :id } # # def show # # Etag will differ even for the same invoice when it's viewed by a different current_user # @invoice = Invoice.find(params[:id]) # fresh_when(@invoice) # end # end def etag(&etagger) self.etaggers += [etagger] end end # Sets the +etag+, +last_modified+, or both on the response and renders a # 304 Not Modified response if the request is already fresh. # # === Parameters: # # * :etag. # * :last_modified. # * :public By default the Cache-Control header is private, set this to # +true+ if you want your application to be cachable by other devices (proxy caches). # * :template By default, the template digest for the current # controller/action is included in ETags. If the action renders a # different template, you can include its digest instead. If the action # doesn't render a template at all, you can pass template: false # to skip any attempt to check for a template digest. # # === Example: # # def show # @article = Article.find(params[:id]) # fresh_when(etag: @article, last_modified: @article.created_at, public: true) # end # # This will render the show template if the request isn't sending a matching ETag or # If-Modified-Since header and just a 304 Not Modified response if there's a match. # # You can also just pass a record where +last_modified+ will be set by calling # +updated_at+ and the +etag+ by passing the object itself. # # def show # @article = Article.find(params[:id]) # fresh_when(@article) # end # # When passing a record, you can still set whether the public header: # # def show # @article = Article.find(params[:id]) # fresh_when(@article, public: true) # end # # When rendering a different template than the default controller/action # style, you can indicate which digest to include in the ETag: # # before_action { fresh_when @article, template: 'widgets/show' } # def fresh_when(record_or_options, additional_options = {}) if record_or_options.is_a? Hash options = record_or_options options.assert_valid_keys(:etag, :last_modified, :public, :template) else record = record_or_options options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options) end response.etag = combine_etags(options) if options[:etag] || options[:template] response.last_modified = options[:last_modified] if options[:last_modified] response.cache_control[:public] = true if options[:public] head :not_modified if request.fresh?(response) end # Sets the +etag+ and/or +last_modified+ on the response and checks it against # the client request. If the request doesn't match the options provided, the # request is considered stale and should be generated from scratch. Otherwise, # it's fresh and we don't need to generate anything and a reply of 304 Not Modified is sent. # # === Parameters: # # * :etag. # * :last_modified. # * :public By default the Cache-Control header is private, set this to # +true+ if you want your application to be cachable by other devices (proxy caches). # * :template By default, the template digest for the current # controller/action is included in ETags. If the action renders a # different template, you can include its digest instead. If the action # doesn't render a template at all, you can pass template: false # to skip any attempt to check for a template digest. # # === Example: # # def show # @article = Article.find(params[:id]) # # if stale?(etag: @article, last_modified: @article.created_at) # @statistics = @article.really_expensive_call # respond_to do |format| # # all the supported formats # end # end # end # # You can also just pass a record where +last_modified+ will be set by calling # +updated_at+ and the +etag+ by passing the object itself. # # def show # @article = Article.find(params[:id]) # # if stale?(@article) # @statistics = @article.really_expensive_call # respond_to do |format| # # all the supported formats # end # end # end # # When passing a record, you can still set whether the public header: # # def show # @article = Article.find(params[:id]) # # if stale?(@article, public: true) # @statistics = @article.really_expensive_call # respond_to do |format| # # all the supported formats # end # end # end # # When rendering a different template than the default controller/action # style, you can indicate which digest to include in the ETag: # # def show # super if stale? @article, template: 'widgets/show' # end # def stale?(record_or_options, additional_options = {}) fresh_when(record_or_options, additional_options) !request.fresh?(response) end # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+ # instruction, so that intermediate caches must not cache the response. # # expires_in 20.minutes # expires_in 3.hours, public: true # expires_in 3.hours, public: true, must_revalidate: true # # This method will overwrite an existing Cache-Control header. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. # # The method will also ensure a HTTP Date header for client compatibility. def expires_in(seconds, options = {}) response.cache_control.merge!( :max_age => seconds, :public => options.delete(:public), :must_revalidate => options.delete(:must_revalidate) ) options.delete(:private) response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"} response.date = Time.now unless response.date? end # Sets a HTTP 1.1 Cache-Control header of no-cache so no caching should # occur by the browser or intermediate caches (like caching proxy servers). def expires_now response.cache_control.replace(:no_cache => true) end private def combine_etags(options) etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact etags.unshift options[:etag] end end end rails-4.2.6/actionpack/lib/action_controller/metal/cookies.rb000066400000000000000000000003641266740050600243330ustar00rootroot00000000000000module ActionController #:nodoc: module Cookies extend ActiveSupport::Concern include RackDelegation included do helper_method :cookies end private def cookies request.cookie_jar end end end rails-4.2.6/actionpack/lib/action_controller/metal/data_streaming.rb000066400000000000000000000173471266740050600256720ustar00rootroot00000000000000require 'action_controller/metal/exceptions' module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, # instead of rendering. module DataStreaming extend ActiveSupport::Concern include ActionController::Rendering DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc: DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc: protected # Sends the file. This uses a server-appropriate method (such as X-Sendfile) # via the Rack::Sendfile middleware. The header to use is set via # +config.action_dispatch.x_sendfile_header+. # Your server can also configure this for you by setting the X-Sendfile-Type header. # # Be careful to sanitize the path parameter if it is coming from a web # page. send_file(params[:path]) allows a malicious user to # download any file on your server. # # Options: # * :filename - suggests a filename for the browser to use. # Defaults to File.basename(path). # * :type - specifies an HTTP content type. # You can specify either a string or a symbol for a registered type register with # Mime::Type.register, for example :json # If omitted, type will be guessed from the file extension specified in :filename. # If no content type is registered for the extension, default type 'application/octet-stream' will be used. # * :disposition - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * :status - specifies the status code to send with the response. Defaults to 200. # * :url_based_filename - set to +true+ if you want the browser guess the filename from # the URL, which is necessary for i18n filenames on certain browsers # (setting :filename overrides this option). # # The default Content-Type and Content-Disposition headers are # set to download arbitrary binary files in as many browsers as # possible. IE versions 4, 5, 5.5, and 6 are all known to have # a variety of quirks (especially when downloading over SSL). # # Simple download: # # send_file '/path/to.zip' # # Show a JPEG in the browser: # # send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline' # # Show a 404 page in the browser: # # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404 # # Read about the other Content-* HTTP headers if you'd like to # provide the user with more information (such as Content-Description) in # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. # # Also be aware that the document may be cached by proxies and browsers. # The Pragma and Cache-Control headers declare how the file may be cached # by intermediaries. They default to require clients to validate with # the server before releasing cached responses. See # http://www.mnot.net/cache_docs/ for an overview of web caching and # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 # for the Cache-Control header spec. def send_file(path, options = {}) #:doc: raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) options[:filename] ||= File.basename(path) unless options[:url_based_filename] send_file_headers! options self.status = options[:status] || 200 self.content_type = options[:content_type] if options.key?(:content_type) self.response_body = FileBody.new(path) end # Avoid having to pass an open file handle as the response body. # Rack::Sendfile will usually intercept the response and uses # the path directly, so there is no reason to open the file. class FileBody #:nodoc: attr_reader :to_path def initialize(path) @to_path = path end # Stream the file's contents if Rack::Sendfile isn't present. def each File.open(to_path, 'rb') do |file| while chunk = file.read(16384) yield chunk end end end end # Sends the given binary data to the browser. This method is similar to # render plain: data, but also allows you to specify whether # the browser should display the response as a file attachment (i.e. in a # download dialog) or as inline data. You may also set the content type, # the apparent file name, and other things. # # Options: # * :filename - suggests a filename for the browser to use. # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify # either a string or a symbol for a registered type register with Mime::Type.register, for example :json # If omitted, type will be guessed from the file extension specified in :filename. # If no content type is registered for the extension, default type 'application/octet-stream' will be used. # * :disposition - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * :status - specifies the status code to send with the response. Defaults to 200. # # Generic data download: # # send_data buffer # # Download a dynamically-generated tarball: # # send_data generate_tgz('dir'), filename: 'dir.tgz' # # Display an image Active Record in the browser: # # send_data image.data, type: image.content_type, disposition: 'inline' # # See +send_file+ for more information on HTTP Content-* headers and caching. def send_data(data, options = {}) #:doc: send_file_headers! options render options.slice(:status, :content_type).merge(:text => data) end private def send_file_headers!(options) type_provided = options.has_key?(:type) content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) raise ArgumentError, ":type option required" if content_type.nil? if content_type.is_a?(Symbol) extension = Mime[content_type] raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension self.content_type = extension else if !type_provided && options[:filename] # If type wasn't provided, try guessing from file extension. content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type end self.content_type = content_type end disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION) unless disposition.nil? disposition = disposition.to_s disposition += %(; filename="#{options[:filename]}") if options[:filename] headers['Content-Disposition'] = disposition end headers['Content-Transfer-Encoding'] = 'binary' response.sending_file = true # Fix a problem with IE 6.0 on opening downloaded files: # If Cache-Control: no-cache is set (which Rails does by default), # IE removes the file it just downloaded from its cache immediately # after it displays the "open/save" dialog, which means that if you # hit "open" the file isn't there anymore when the application that # is called for handling the download is run, so let's workaround that response.cache_control[:public] ||= false end end end rails-4.2.6/actionpack/lib/action_controller/metal/etag_with_template_digest.rb000066400000000000000000000030341266740050600301010ustar00rootroot00000000000000module ActionController # When our views change, they should bubble up into HTTP cache freshness # and bust browser caches. So the template digest for the current action # is automatically included in the ETag. # # Enabled by default for apps that use Action View. Disable by setting # # config.action_controller.etag_with_template_digest = false # # Override the template to digest by passing +:template+ to +fresh_when+ # and +stale?+ calls. For example: # # # We're going to render widgets/show, not posts/show # fresh_when @post, template: 'widgets/show' # # # We're not going to render a template, so omit it from the ETag. # fresh_when @post, template: false # module EtagWithTemplateDigest extend ActiveSupport::Concern include ActionController::ConditionalGet included do class_attribute :etag_with_template_digest self.etag_with_template_digest = true ActiveSupport.on_load :action_view, yield: true do |action_view_base| etag do |options| determine_template_etag(options) if etag_with_template_digest end end end private def determine_template_etag(options) if template = pick_template_for_etag(options) lookup_and_digest_template(template) end end def pick_template_for_etag(options) options.fetch(:template) { "#{controller_name}/#{action_name}" } end def lookup_and_digest_template(template) ActionView::Digestor.digest name: template, finder: lookup_context end end end rails-4.2.6/actionpack/lib/action_controller/metal/exceptions.rb000066400000000000000000000030361266740050600250570ustar00rootroot00000000000000module ActionController class ActionControllerError < StandardError #:nodoc: end class BadRequest < ActionControllerError #:nodoc: attr_reader :original_exception def initialize(type = nil, e = nil) return super() unless type && e super("Invalid #{type} parameters: #{e.message}") @original_exception = e set_backtrace e.backtrace end end class RenderError < ActionControllerError #:nodoc: end class RoutingError < ActionControllerError #:nodoc: attr_reader :failures def initialize(message, failures=[]) super(message) @failures = failures end end class ActionController::UrlGenerationError < ActionControllerError #:nodoc: end class MethodNotAllowed < ActionControllerError #:nodoc: def initialize(*allowed_methods) super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") end end class NotImplemented < MethodNotAllowed #:nodoc: end class UnknownController < ActionControllerError #:nodoc: end class MissingFile < ActionControllerError #:nodoc: end class SessionOverflowError < ActionControllerError #:nodoc: DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' def initialize(message = nil) super(message || DEFAULT_MESSAGE) end end class UnknownHttpMethod < ActionControllerError #:nodoc: end class UnknownFormat < ActionControllerError #:nodoc: end end rails-4.2.6/actionpack/lib/action_controller/metal/flash.rb000066400000000000000000000033061266740050600237730ustar00rootroot00000000000000module ActionController #:nodoc: module Flash extend ActiveSupport::Concern included do class_attribute :_flash_types, instance_accessor: false self._flash_types = [] delegate :flash, to: :request add_flash_types(:alert, :notice) end module ClassMethods # Creates new flash types. You can pass as many types as you want to create # flash types other than the default alert and notice in # your controllers and views. For instance: # # # in application_controller.rb # class ApplicationController < ActionController::Base # add_flash_types :warning # end # # # in your controller # redirect_to user_path(@user), warning: "Incomplete profile" # # # in your view # <%= warning %> # # This method will automatically define a new method for each of the given # names, and it will be available in your views. def add_flash_types(*types) types.each do |type| next if _flash_types.include?(type) define_method(type) do request.flash[type] end helper_method type self._flash_types += [type] end end end protected def redirect_to(options = {}, response_status_and_flash = {}) #:doc: self.class._flash_types.each do |flash_type| if type = response_status_and_flash.delete(flash_type) flash[flash_type] = type end end if other_flashes = response_status_and_flash.delete(:flash) flash.update(other_flashes) end super(options, response_status_and_flash) end end end rails-4.2.6/actionpack/lib/action_controller/metal/force_ssl.rb000066400000000000000000000102761266740050600246610ustar00rootroot00000000000000require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/slice' module ActionController # This module provides a method which will redirect browser to use HTTPS # protocol. This will ensure that user's sensitive information will be # transferred safely over the internet. You _should_ always force browser # to use HTTPS when you're transferring sensitive information such as # user authentication, account information, or credit card information. # # Note that if you are really concerned about your application security, # you might consider using +config.force_ssl+ in your config file instead. # That will ensure all the data transferred via HTTPS protocol and prevent # user from getting session hijacked when accessing the site under unsecured # HTTP protocol. module ForceSSL extend ActiveSupport::Concern include AbstractController::Callbacks ACTION_OPTIONS = [:only, :except, :if, :unless] URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path] REDIRECT_OPTIONS = [:status, :flash, :alert, :notice] module ClassMethods # Force the request to this particular controller or specified actions to be # under HTTPS protocol. # # If you need to disable this for any reason (e.g. development) then you can use # an +:if+ or +:unless+ condition. # # class AccountsController < ApplicationController # force_ssl if: :ssl_configured? # # def ssl_configured? # !Rails.env.development? # end # end # # ==== URL Options # You can pass any of the following options to affect the redirect url # * host - Redirect to a different host name # * subdomain - Redirect to a different subdomain # * domain - Redirect to a different domain # * port - Redirect to a non-standard port # * path - Redirect to a different path # # ==== Redirect Options # You can pass any of the following options to affect the redirect status and response # * status - Redirect with a custom status (default is 301 Moved Permanently) # * flash - Set a flash message when redirecting # * alert - Set an alert message when redirecting # * notice - Set a notice message when redirecting # # ==== Action Options # You can pass any of the following options to affect the before_action callback # * only - The callback should be run only for this action # * except - The callback should be run for all actions except this action # * if - A symbol naming an instance method or a proc; the callback # will be called only when it returns a true value. # * unless - A symbol naming an instance method or a proc; the callback # will be called only when it returns a false value. def force_ssl(options = {}) action_options = options.slice(*ACTION_OPTIONS) redirect_options = options.except(*ACTION_OPTIONS) before_action(action_options) do force_ssl_redirect(redirect_options) end end end # Redirect the existing request to use the HTTPS protocol. # # ==== Parameters # * host_or_options - Either a host name or any of the url & redirect options # available to the force_ssl method. def force_ssl_redirect(host_or_options = nil) unless request.ssl? options = { :protocol => 'https://', :host => request.host, :path => request.fullpath, :status => :moved_permanently } if host_or_options.is_a?(Hash) options.merge!(host_or_options) elsif host_or_options options[:host] = host_or_options end secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS)) flash.keep if respond_to?(:flash) redirect_to secure_url, options.slice(*REDIRECT_OPTIONS) end end end end rails-4.2.6/actionpack/lib/action_controller/metal/head.rb000066400000000000000000000032401266740050600235740ustar00rootroot00000000000000module ActionController module Head # Returns a response that has no content (merely headers). The options # argument is interpreted to be a hash of header names and values. # This allows you to easily return a response that consists only of # significant headers: # # head :created, location: person_path(@person) # # head :created, location: @person # # It can also be used to return exceptional conditions: # # return head(:method_not_allowed) unless request.post? # return head(:bad_request) unless valid_request? # render # # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols. def head(status, options = {}) options, status = status, nil if status.is_a?(Hash) status ||= options.delete(:status) || :ok location = options.delete(:location) content_type = options.delete(:content_type) options.each do |key, value| headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s end self.status = status self.location = url_for(location) if location self.response_body = "" if include_content?(self.response_code) self.content_type = content_type || (Mime[formats.first] if formats) self.response.charset = false if self.response else headers.delete('Content-Type') headers.delete('Content-Length') end true end private # :nodoc: def include_content?(status) case status when 100..199 false when 204, 205, 304 false else true end end end end rails-4.2.6/actionpack/lib/action_controller/metal/helpers.rb000066400000000000000000000100331266740050600243330ustar00rootroot00000000000000module ActionController # The \Rails framework provides a large number of helpers for working with assets, dates, forms, # numbers and model objects, to name a few. These helpers are available to all templates # by default. # # In addition to using the standard template helpers provided, creating custom helpers to # extract complicated logic or reusable functionality is strongly encouraged. By default, each controller # will include all helpers. These helpers are only accessible on the controller through .helpers # # In previous versions of \Rails the controller will include a helper whose # name matches that of the controller, e.g., MyController will automatically # include MyHelper. To return old behavior set +config.action_controller.include_all_helpers+ to +false+. # # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any # controller which inherits from it. # # The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if # a \Time object is blank: # # module FormattedTimeHelper # def format_time(time, format=:long, blank_message=" ") # time.blank? ? blank_message : time.to_s(format) # end # end # # FormattedTimeHelper can now be included in a controller, using the +helper+ class method: # # class EventsController < ActionController::Base # helper FormattedTimeHelper # def index # @events = Event.all # end # end # # Then, in any view rendered by EventController, the format_time method can be called: # # <% @events.each do |event| -%> #

# <%= format_time(event.time, :short, "N/A") %> | <%= event.name %> #

# <% end -%> # # Finally, assuming we have two event instances, one which has a time and one which does not, # the output might look like this: # # 23 Aug 11:30 | Carolina Railhawks Soccer Match # N/A | Carolina Railhaws Training Workshop # module Helpers extend ActiveSupport::Concern class << self; attr_accessor :helpers_path; end include AbstractController::Helpers included do class_attribute :helpers_path, :include_all_helpers self.helpers_path ||= [] self.include_all_helpers = true end module ClassMethods # Declares helper accessors for controller attributes. For example, the # following adds new +name+ and name= instance methods to a # controller and makes them available to the view: # attr_accessor :name # helper_attr :name # # ==== Parameters # * attrs - Names of attributes to be converted into helpers. def helper_attr(*attrs) attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } end # Provides a proxy to access helpers methods from outside the view. def helpers @helper_proxy ||= begin proxy = ActionView::Base.new proxy.config = config.inheritable_copy proxy.extend(_helpers) end end # Overwrite modules_for_helpers to accept :all as argument, which loads # all helpers in helpers_path. # # ==== Parameters # * args - A list of helpers # # ==== Returns # * array - A normalized list of modules for the list of helpers provided. def modules_for_helpers(args) args += all_application_helpers if args.delete(:all) super(args) end def all_helpers_from_path(path) helpers = Array(path).flat_map do |_path| extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } names.sort! end helpers.uniq! helpers end private # Extract helper names from files in app/helpers/**/*_helper.rb def all_application_helpers all_helpers_from_path(helpers_path) end end end end rails-4.2.6/actionpack/lib/action_controller/metal/hide_actions.rb000066400000000000000000000022261266740050600253270ustar00rootroot00000000000000 module ActionController # Adds the ability to prevent public methods on a controller to be called as actions. module HideActions extend ActiveSupport::Concern included do class_attribute :hidden_actions self.hidden_actions = Set.new.freeze end private # Overrides AbstractController::Base#action_method? to return false if the # action name is in the list of hidden actions. def method_for_action(action_name) self.class.visible_action?(action_name) && super end module ClassMethods # Sets all of the actions passed in as hidden actions. # # ==== Parameters # * args - A list of actions def hide_action(*args) self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze end def visible_action?(action_name) not hidden_actions.include?(action_name) end # Overrides AbstractController::Base#action_methods to remove any methods # that are listed as hidden methods. def action_methods @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze end end end end rails-4.2.6/actionpack/lib/action_controller/metal/http_authentication.rb000066400000000000000000000503121266740050600267530ustar00rootroot00000000000000require 'base64' require 'active_support/security_utils' module ActionController # Makes it dead easy to do HTTP Basic, Digest and Token authentication. module HttpAuthentication # Makes it dead easy to do HTTP \Basic authentication. # # === Simple \Basic example # # class PostsController < ApplicationController # http_basic_authenticate_with name: "dhh", password: "secret", except: :index # # def index # render plain: "Everyone can see me!" # end # # def edit # render plain: "I'm only accessible if you know the password" # end # end # # === Advanced \Basic example # # Here is a more advanced \Basic example where only Atom feeds and the XML API is protected by HTTP authentication, # the regular HTML interface is protected by a session approach: # # class ApplicationController < ActionController::Base # before_action :set_account, :authenticate # # protected # def set_account # @account = Account.find_by(url_name: request.subdomains.first) # end # # def authenticate # case request.format # when Mime::XML, Mime::ATOM # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) } # @current_user = user # else # request_http_basic_authentication # end # else # if session_authenticated? # @current_user = @account.users.find(session[:authenticated][:user_id]) # else # redirect_to(login_url) and return false # end # end # end # end # # In your integration tests, you can do something like this: # # def test_access_granted_from_xml # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) # get "/notes/1.xml" # # assert_equal 200, status # end module Basic extend self module ControllerMethods extend ActiveSupport::Concern module ClassMethods def http_basic_authenticate_with(options = {}) before_action(options.except(:name, :password, :realm)) do authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password| # This comparison uses & so that it doesn't short circuit and # uses `variable_size_secure_compare` so that length information # isn't leaked. ActiveSupport::SecurityUtils.variable_size_secure_compare(name, options[:name]) & ActiveSupport::SecurityUtils.variable_size_secure_compare(password, options[:password]) end end end end def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure) authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm) end def authenticate_with_http_basic(&login_procedure) HttpAuthentication::Basic.authenticate(request, &login_procedure) end def request_http_basic_authentication(realm = "Application") HttpAuthentication::Basic.authentication_request(self, realm) end end def authenticate(request, &login_procedure) if has_basic_credentials?(request) login_procedure.call(*user_name_and_password(request)) end end def has_basic_credentials?(request) request.authorization.present? && (auth_scheme(request) == 'Basic') end def user_name_and_password(request) decode_credentials(request).split(':', 2) end def decode_credentials(request) ::Base64.decode64(auth_param(request) || '') end def auth_scheme(request) request.authorization.split(' ', 2).first end def auth_param(request) request.authorization.split(' ', 2).second end def encode_credentials(user_name, password) "Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}" end def authentication_request(controller, realm) controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}") controller.status = 401 controller.response_body = "HTTP Basic: Access denied.\n" end end # Makes it dead easy to do HTTP \Digest authentication. # # === Simple \Digest example # # require 'digest/md5' # class PostsController < ApplicationController # REALM = "SuperSecret" # USERS = {"dhh" => "secret", #plain text password # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password # # before_action :authenticate, except: [:index] # # def index # render plain: "Everyone can see me!" # end # # def edit # render plain: "I'm only accessible if you know the password" # end # # private # def authenticate # authenticate_or_request_with_http_digest(REALM) do |username| # USERS[username] # end # end # end # # === Notes # # The +authenticate_or_request_with_http_digest+ block must return the user's password # or the ha1 digest hash so the framework can appropriately hash to check the user's # credentials. Returning +nil+ will cause authentication to fail. # # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If # the password file or database is compromised, the attacker would be able to use the ha1 hash to # authenticate as the user at this +realm+, but would not have the user's password to try using at # other sites. # # In rare instances, web servers or front proxies strip authorization headers before # they reach your application. You can debug this situation by logging all environment # variables, and check for HTTP_AUTHORIZATION, amongst others. module Digest extend self module ControllerMethods def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure) authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm) end # Authenticate with HTTP Digest, returns true or false def authenticate_with_http_digest(realm = "Application", &password_procedure) HttpAuthentication::Digest.authenticate(request, realm, &password_procedure) end # Render output including the HTTP Digest authentication header def request_http_digest_authentication(realm = "Application", message = nil) HttpAuthentication::Digest.authentication_request(self, realm, message) end end # Returns false on a valid response, true otherwise def authenticate(request, realm, &password_procedure) request.authorization && validate_digest_response(request, realm, &password_procedure) end # Returns false unless the request credentials response value matches the expected value. # First try the password as a ha1 digest password. If this fails, then try it as a plain # text password. def validate_digest_response(request, realm, &password_procedure) secret_key = secret_token(request) credentials = decode_credentials_header(request) valid_nonce = validate_nonce(secret_key, request, credentials[:nonce]) if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque] password = password_procedure.call(credentials[:username]) return false unless password method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] uri = credentials[:uri] [true, false].any? do |trailing_question_mark| [true, false].any? do |password_is_ha1| _uri = trailing_question_mark ? uri + "?" : uri expected = expected_response(method, _uri, credentials, password, password_is_ha1) expected == credentials[:response] end end end end # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead # of a plain-text password. def expected_response(http_method, uri, credentials, password, password_is_ha1=true) ha1 = password_is_ha1 ? password : ha1(credentials, password) ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':')) ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':')) end def ha1(credentials, password) ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) end def encode_credentials(http_method, credentials, password, password_is_ha1) credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) "Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ') end def decode_credentials_header(request) decode_credentials(request.authorization) end def decode_credentials(header) ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair| key, value = pair.split('=', 2) [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')] end] end def authentication_header(controller, realm) secret_key = secret_token(controller.request) nonce = self.nonce(secret_key) opaque = opaque(secret_key) controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") end def authentication_request(controller, realm, message = nil) message ||= "HTTP Digest: Access denied.\n" authentication_header(controller, realm) controller.status = 401 controller.response_body = message end def secret_token(request) key_generator = request.env["action_dispatch.key_generator"] http_auth_salt = request.env["action_dispatch.http_auth_salt"] key_generator.generate_key(http_auth_salt) end # Uses an MD5 digest based on time to generate a value to be used only once. # # A server-specified data string which should be uniquely generated each time a 401 response is made. # It is recommended that this string be base64 or hexadecimal data. # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed. # # The contents of the nonce are implementation dependent. # The quality of the implementation depends on a good choice. # A nonce might, for example, be constructed as the base 64 encoding of # # time-stamp H(time-stamp ":" ETag ":" private-key) # # where time-stamp is a server-generated time or other non-repeating value, # ETag is the value of the HTTP ETag header associated with the requested entity, # and private-key is data known only to the server. # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and # reject the request if it did not match the nonce from that header or # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity. # The inclusion of the ETag prevents a replay request for an updated version of the resource. # (Note: including the IP address of the client in the nonce would appear to offer the server the ability # to limit the reuse of the nonce to the same client that originally got it. # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm. # Also, IP address spoofing is not that hard.) # # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4 # of this document. # # The nonce is opaque to the client. Composed of Time, and hash of Time with secret # key from the Rails session secret generated upon creation of project. Ensures # the time cannot be modified by client. def nonce(secret_key, time = Time.now) t = time.to_i hashed = [t, secret_key] digest = ::Digest::MD5.hexdigest(hashed.join(":")) ::Base64.strict_encode64("#{t}:#{digest}") end # Might want a shorter timeout depending on whether the request # is a PATCH, PUT, or POST, and if client is browser or web service. # Can be much shorter if the Stale directive is implemented. This would # allow a user to use new nonce without prompting user again for their # username and password. def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60) return false if value.nil? t = ::Base64.decode64(value).split(":").first.to_i nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout end # Opaque based on random generation - but changing each request? def opaque(secret_key) ::Digest::MD5.hexdigest(secret_key) end end # Makes it dead easy to do HTTP Token authentication. # # Simple Token example: # # class PostsController < ApplicationController # TOKEN = "secret" # # before_action :authenticate, except: [ :index ] # # def index # render plain: "Everyone can see me!" # end # # def edit # render plain: "I'm only accessible if you know the password" # end # # private # def authenticate # authenticate_or_request_with_http_token do |token, options| # token == TOKEN # end # end # end # # # Here is a more advanced Token example where only Atom feeds and the XML API is protected by HTTP token authentication, # the regular HTML interface is protected by a session approach: # # class ApplicationController < ActionController::Base # before_action :set_account, :authenticate # # protected # def set_account # @account = Account.find_by(url_name: request.subdomains.first) # end # # def authenticate # case request.format # when Mime::XML, Mime::ATOM # if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) } # @current_user = user # else # request_http_token_authentication # end # else # if session_authenticated? # @current_user = @account.users.find(session[:authenticated][:user_id]) # else # redirect_to(login_url) and return false # end # end # end # end # # # In your integration tests, you can do something like this: # # def test_access_granted_from_xml # get( # "/notes/1.xml", nil, # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token) # ) # # assert_equal 200, status # end # # # On shared hosts, Apache sometimes doesn't pass authentication headers to # FCGI instances. If your environment matches this description and you cannot # authenticate, try this rule in your Apache setup: # # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] module Token TOKEN_KEY = 'token=' TOKEN_REGEX = /^Token / AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/ extend self module ControllerMethods def authenticate_or_request_with_http_token(realm = "Application", &login_procedure) authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm) end def authenticate_with_http_token(&login_procedure) Token.authenticate(self, &login_procedure) end def request_http_token_authentication(realm = "Application") Token.authentication_request(self, realm) end end # If token Authorization header is present, call the login # procedure with the present token and options. # # [controller] # ActionController::Base instance for the current request. # # [login_procedure] # Proc to call if a token is present. The Proc should take two arguments: # # authenticate(controller) { |token, options| ... } # # Returns the return value of login_procedure if a # token is found. Returns nil if no token is found. def authenticate(controller, &login_procedure) token, options = token_and_options(controller.request) unless token.blank? login_procedure.call(token, options) end end # Parses the token and options out of the token authorization header. If # the header looks like this: # Authorization: Token token="abc", nonce="def" # Then the returned token is "abc", and the options is {nonce: "def"} # # request - ActionDispatch::Request instance with the current headers. # # Returns an Array of [String, Hash] if a token is present. # Returns nil if no token is found. def token_and_options(request) authorization_request = request.authorization.to_s if authorization_request[TOKEN_REGEX] params = token_params_from authorization_request [params.shift[1], Hash[params].with_indifferent_access] end end def token_params_from(auth) rewrite_param_values params_array_from raw_params auth end # Takes raw_params and turns it into an array of parameters def params_array_from(raw_params) raw_params.map { |param| param.split %r/=(.+)?/ } end # This removes the " characters wrapping the value. def rewrite_param_values(array_params) array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' } end # This method takes an authorization body and splits up the key-value # pairs by the standardized :, ;, or \t # delimiters defined in +AUTHN_PAIR_DELIMITERS+. def raw_params(auth) _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/) if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}}) _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}" end _raw_params end # Encodes the given token and options into an Authorization header value. # # token - String token. # options - optional Hash of the options. # # Returns String. def encode_credentials(token, options = {}) values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value| "#{key}=#{value.to_s.inspect}" end "Token #{values * ", "}" end # Sets a WWW-Authenticate to let the client know a token is desired. # # controller - ActionController::Base instance for the outgoing response. # realm - String realm to use in the header. # # Returns nothing. def authentication_request(controller, realm) controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}") controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized end end end end rails-4.2.6/actionpack/lib/action_controller/metal/implicit_render.rb000066400000000000000000000005661266740050600260540ustar00rootroot00000000000000module ActionController module ImplicitRender def send_action(method, *args) ret = super default_render unless performed? ret end def default_render(*args) render(*args) end def method_for_action(action_name) super || if template_exists?(action_name.to_s, _prefixes) "default_render" end end end end rails-4.2.6/actionpack/lib/action_controller/metal/instrumentation.rb000066400000000000000000000064461266740050600261510ustar00rootroot00000000000000require 'benchmark' require 'abstract_controller/logger' module ActionController # Adds instrumentation to several ends in ActionController::Base. It also provides # some hooks related with process_action, this allows an ORM like Active Record # and/or DataMapper to plug in ActionController and show related information. # # Check ActiveRecord::Railties::ControllerRuntime for an example. module Instrumentation extend ActiveSupport::Concern include AbstractController::Logger include ActionController::RackDelegation attr_internal :view_runtime def process_action(*args) raw_payload = { :controller => self.class.name, :action => self.action_name, :params => request.filtered_parameters, :format => request.format.try(:ref), :method => request.request_method, :path => (request.fullpath rescue "unknown") } ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload| begin result = super payload[:status] = response.status result ensure append_info_to_payload(payload) end end end def render(*args) render_output = nil self.view_runtime = cleanup_view_runtime do Benchmark.ms { render_output = super } end render_output end def send_file(path, options={}) ActiveSupport::Notifications.instrument("send_file.action_controller", options.merge(:path => path)) do super end end def send_data(data, options = {}) ActiveSupport::Notifications.instrument("send_data.action_controller", options) do super end end def redirect_to(*args) ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload| result = super payload[:status] = response.status payload[:location] = response.filtered_location result end end private # A hook invoked every time a before callback is halted. def halted_callback_hook(filter) ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter) end # A hook which allows you to clean up any time taken into account in # views wrongly, like database querying time. # # def cleanup_view_runtime # super - time_taken_in_something_expensive # end # # :api: plugin def cleanup_view_runtime #:nodoc: yield end # Every time after an action is processed, this method is invoked # with the payload, so you can add more information. # :api: plugin def append_info_to_payload(payload) #:nodoc: payload[:view_runtime] = view_runtime end module ClassMethods # A hook which allows other frameworks to log what happened during # controller process action. This method should return an array # with the messages to be added. # :api: plugin def log_process_action(payload) #:nodoc: messages, view_runtime = [], payload[:view_runtime] messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime messages end end end end rails-4.2.6/actionpack/lib/action_controller/metal/live.rb000066400000000000000000000223611266740050600236370ustar00rootroot00000000000000require 'action_dispatch/http/response' require 'delegate' require 'active_support/json' module ActionController # Mix this module in to your controller, and all actions in that controller # will be able to stream data to the client as it's written. # # class MyController < ActionController::Base # include ActionController::Live # # def stream # response.headers['Content-Type'] = 'text/event-stream' # 100.times { # response.stream.write "hello world\n" # sleep 1 # } # ensure # response.stream.close # end # end # # There are a few caveats with this use. You *cannot* write headers after the # response has been committed (Response#committed? will return truthy). # Calling +write+ or +close+ on the response stream will cause the response # object to be committed. Make sure all headers are set before calling write # or close on your stream. # # You *must* call close on your stream when you're finished, otherwise the # socket may be left open forever. # # The final caveat is that your actions are executed in a separate thread than # the main thread. Make sure your actions are thread safe, and this shouldn't # be a problem (don't share state across threads, etc). module Live # This class provides the ability to write an SSE (Server Sent Event) # to an IO stream. The class is initialized with a stream and can be used # to either write a JSON string or an object which can be converted to JSON. # # Writing an object will convert it into standard SSE format with whatever # options you have configured. You may choose to set the following options: # # 1) Event. If specified, an event with this name will be dispatched on # the browser. # 2) Retry. The reconnection time in milliseconds used when attempting # to send the event. # 3) Id. If the connection dies while sending an SSE to the browser, then # the server will receive a +Last-Event-ID+ header with value equal to +id+. # # After setting an option in the constructor of the SSE object, all future # SSEs sent across the stream will use those options unless overridden. # # Example Usage: # # class MyController < ActionController::Base # include ActionController::Live # # def index # response.headers['Content-Type'] = 'text/event-stream' # sse = SSE.new(response.stream, retry: 300, event: "event-name") # sse.write({ name: 'John'}) # sse.write({ name: 'John'}, id: 10) # sse.write({ name: 'John'}, id: 10, event: "other-event") # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) # ensure # sse.close # end # end # # Note: SSEs are not currently supported by IE. However, they are supported # by Chrome, Firefox, Opera, and Safari. class SSE WHITELISTED_OPTIONS = %w( retry event id ) def initialize(stream, options = {}) @stream = stream @options = options end def close @stream.close end def write(object, options = {}) case object when String perform_write(object, options) else perform_write(ActiveSupport::JSON.encode(object), options) end end private def perform_write(json, options) current_options = @options.merge(options).stringify_keys WHITELISTED_OPTIONS.each do |option_name| if (option_value = current_options[option_name]) @stream.write "#{option_name}: #{option_value}\n" end end message = json.gsub(/\n/, "\ndata: ") @stream.write "data: #{message}\n\n" end end class ClientDisconnected < RuntimeError end class Buffer < ActionDispatch::Response::Buffer #:nodoc: include MonitorMixin # Ignore that the client has disconnected. # # If this value is `true`, calling `write` after the client # disconnects will result in the written content being silently # discarded. If this value is `false` (the default), a # ClientDisconnected exception will be raised. attr_accessor :ignore_disconnect def initialize(response) @error_callback = lambda { true } @cv = new_cond @aborted = false @ignore_disconnect = false super(response, SizedQueue.new(10)) end def write(string) unless @response.committed? @response.headers["Cache-Control"] = "no-cache" @response.headers.delete "Content-Length" end super unless connected? @buf.clear unless @ignore_disconnect # Raise ClientDisconnected, which is a RuntimeError (not an # IOError), because that's more appropriate for something beyond # the developer's control. raise ClientDisconnected, "client disconnected" end end end def each @response.sending! while str = @buf.pop yield str end @response.sent! end # Write a 'close' event to the buffer; the producer/writing thread # uses this to notify us that it's finished supplying content. # # See also #abort. def close synchronize do super @buf.push nil @cv.broadcast end end # Inform the producer/writing thread that the client has # disconnected; the reading thread is no longer interested in # anything that's being written. # # See also #close. def abort synchronize do @aborted = true @buf.clear end end # Is the client still connected and waiting for content? # # The result of calling `write` when this is `false` is determined # by `ignore_disconnect`. def connected? !@aborted end def await_close synchronize do @cv.wait_until { @closed } end end def on_error(&block) @error_callback = block end def call_on_error @error_callback.call end end class Response < ActionDispatch::Response #:nodoc: all class Header < DelegateClass(Hash) # :nodoc: def initialize(response, header) @response = response super(header) end def []=(k,v) if @response.committed? raise ActionDispatch::IllegalStateError, 'header already sent' end super end def merge(other) self.class.new @response, __getobj__.merge(other) end def to_hash __getobj__.dup end end private def before_committed super jar = request.cookie_jar # The response can be committed multiple times jar.write self unless committed? end def before_sending super request.cookie_jar.commit! headers.freeze end def build_buffer(response, body) buf = Live::Buffer.new response body.each { |part| buf.write part } buf end def merge_default_headers(original, default) Header.new self, super end def handle_conditional_get! super unless committed? end end def process(name) t1 = Thread.current locals = t1.keys.map { |key| [key, t1[key]] } error = nil # This processes the action in a child thread. It lets us return the # response code and headers back up the rack stack, and still process # the body in parallel with sending data to the client Thread.new { t2 = Thread.current t2.abort_on_exception = true # Since we're processing the view in a different thread, copy the # thread locals from the main thread to the child thread. :'( locals.each { |k,v| t2[k] = v } begin super(name) rescue => e if @_response.committed? begin @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html @_response.stream.call_on_error rescue => exception log_error(exception) ensure log_error(e) @_response.stream.close end else error = e end ensure @_response.commit! end } @_response.await_commit raise error if error end def log_error(exception) logger = ActionController::Base.logger return unless logger logger.fatal do message = "\n#{exception.class} (#{exception.message}):\n" message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) message << " " << exception.backtrace.join("\n ") "#{message}\n\n" end end def response_body=(body) super response.close if response end def set_response!(request) if request.env["HTTP_VERSION"] == "HTTP/1.0" super else @_response = Live::Response.new @_response.request = request end end end end rails-4.2.6/actionpack/lib/action_controller/metal/mime_responds.rb000066400000000000000000000262541266740050600255510ustar00rootroot00000000000000require 'abstract_controller/collector' module ActionController #:nodoc: module MimeResponds extend ActiveSupport::Concern # :stopdoc: module ClassMethods def respond_to(*) raise NoMethodError, "The controller-level `respond_to' feature has " \ "been extracted to the `responders` gem. Add it to your Gemfile to " \ "continue using this feature:\n" \ " gem 'responders', '~> 2.0'\n" \ "Consult the Rails upgrade guide for details." end end def respond_with(*) raise NoMethodError, "The `respond_with' feature has been extracted " \ "to the `responders` gem. Add it to your Gemfile to continue using " \ "this feature:\n" \ " gem 'responders', '~> 2.0'\n" \ "Consult the Rails upgrade guide for details." end # :startdoc: # Without web-service support, an action which collects the data for displaying a list of people # might look something like this: # # def index # @people = Person.all # end # # Here's the same action, with web-service support baked in: # # def index # @people = Person.all # # respond_to do |format| # format.html # format.xml { render xml: @people } # end # end # # What that says is, "if the client wants HTML in response to this action, just respond as we # would have before, but if the client wants XML, return them the list of people in XML format." # (Rails determines the desired response format from the HTTP Accept header submitted by the client.) # # Supposing you have an action that adds a new person, optionally creating their company # (by name) if it does not already exist, without web-services, it might look like this: # # def create # @company = Company.find_or_create_by(name: params[:company][:name]) # @person = @company.people.create(params[:person]) # # redirect_to(person_list_url) # end # # Here's the same action, with web-service support baked in: # # def create # company = params[:person].delete(:company) # @company = Company.find_or_create_by(name: company[:name]) # @person = @company.people.create(params[:person]) # # respond_to do |format| # format.html { redirect_to(person_list_url) } # format.js # format.xml { render xml: @person.to_xml(include: @company) } # end # end # # If the client wants HTML, we just redirect them back to the person list. If they want JavaScript, # then it is an Ajax request and we render the JavaScript template associated with this action. # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also # include the person's company in the rendered XML, so you get something like this: # # # ... # ... # # ... # ... # ... # # # # Note, however, the extra bit at the top of that action: # # company = params[:person].delete(:company) # @company = Company.find_or_create_by(name: company[:name]) # # This is because the incoming XML document (if a web-service request is in process) can only contain a # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): # # person[name]=...&person[company][name]=...&... # # And, like this (xml-encoded): # # # ... # # ... # # # # In other words, we make the request so that it operates on a single entity's person. Then, in the action, # we extract the company data from the request, find or create the company, and then create the new person # with the remaining data. # # Note that you can define your own XML parameter parser which would allow you to describe multiple entities # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow # and accept Rails' defaults, life will be much easier. # # If you need to use a MIME type which isn't supported by default, you can register your own handlers in # config/initializers/mime_types.rb as follows. # # Mime::Type.register "image/jpg", :jpg # # Respond to also allows you to specify a common block for different formats by using any: # # def index # @people = Person.all # # respond_to do |format| # format.html # format.any(:xml, :json) { render request.format.to_sym => @people } # end # end # # In the example above, if the format is xml, it will render: # # render xml: @people # # Or if the format is json: # # render json: @people # # Formats can have different variants. # # The request variant is a specialization of the request format, like :tablet, # :phone, or :desktop. # # We often want to render different html/json/xml templates for phones, # tablets, and desktop browsers. Variants make it easy. # # You can set the variant in a +before_action+: # # request.variant = :tablet if request.user_agent =~ /iPad/ # # Respond to variants in the action just like you respond to formats: # # respond_to do |format| # format.html do |variant| # variant.tablet # renders app/views/projects/show.html+tablet.erb # variant.phone { extra_setup; render ... } # variant.none { special_setup } # executed only if there is no variant set # end # end # # Provide separate templates for each format and variant: # # app/views/projects/show.html.erb # app/views/projects/show.html+tablet.erb # app/views/projects/show.html+phone.erb # # When you're not sharing any code within the format, you can simplify defining variants # using the inline syntax: # # respond_to do |format| # format.js { render "trash" } # format.html.phone { redirect_to progress_path } # format.html.none { render "trash" } # end # # Variants also support common `any`/`all` block that formats have. # # It works for both inline: # # respond_to do |format| # format.html.any { render text: "any" } # format.html.phone { render text: "phone" } # end # # and block syntax: # # respond_to do |format| # format.html do |variant| # variant.any(:tablet, :phablet){ render text: "any" } # variant.phone { render text: "phone" } # end # end # # You can also set an array of variants: # # request.variant = [:tablet, :phone] # # which will work similarly to formats and MIME types negotiation. If there will be no # :tablet variant declared, :phone variant will be picked: # # respond_to do |format| # format.html.none # format.html.phone # this gets rendered # end # # Be sure to check the documentation of ActionController::MimeResponds.respond_to # for more examples. def respond_to(*mimes) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? collector = Collector.new(mimes, request.variant) yield collector if block_given? if format = collector.negotiate_format(request) _process_format(format) response = collector.response response ? response.call : render({}) else raise ActionController::UnknownFormat end end # A container for responses available from the current controller for # requests for different mime-types sent to a particular action. # # The public controller methods +respond_to+ may be called with a block # that is used to define responses to different mime-types, e.g. # for +respond_to+ : # # respond_to do |format| # format.html # format.xml { render xml: @people } # end # # In this usage, the argument passed to the block (+format+ above) is an # instance of the ActionController::MimeResponds::Collector class. This # object serves as a container in which available responses can be stored by # calling any of the dynamically generated, mime-type-specific methods such # as +html+, +xml+ etc on the Collector. Each response is represented by a # corresponding block if present. # # A subsequent call to #negotiate_format(request) will enable the Collector # to determine which specific mime-type it should respond with for the current # request, with this response then being accessible by calling #response. class Collector include AbstractController::Collector attr_accessor :format def initialize(mimes, variant = nil) @responses = {} @variant = variant mimes.each { |mime| @responses["Mime::#{mime.upcase}".constantize] = nil } end def any(*args, &block) if args.any? args.each { |type| send(type, &block) } else custom(Mime::ALL, &block) end end alias :all :any def custom(mime_type, &block) mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type) @responses[mime_type] ||= if block_given? block else VariantCollector.new(@variant) end end def response response = @responses.fetch(format, @responses[Mime::ALL]) if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax response.variant elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block response else # `format.html{ |variant| variant.phone }` - variant block syntax variant_collector = VariantCollector.new(@variant) response.call(variant_collector) # call format block with variants collector variant_collector.variant end end def negotiate_format(request) @format = request.negotiate_mime(@responses.keys) end class VariantCollector #:nodoc: def initialize(variant = nil) @variant = variant @variants = {} end def any(*args, &block) if block_given? if args.any? && args.none?{ |a| a == @variant } args.each{ |v| @variants[v] = block } else @variants[:any] = block end end end alias :all :any def method_missing(name, *args, &block) @variants[name] = block if block_given? end def variant if @variant.nil? @variants[:none] || @variants[:any] elsif (@variants.keys & @variant).any? @variant.each do |v| return @variants[v] if @variants.key?(v) end else @variants[:any] end end end end end end rails-4.2.6/actionpack/lib/action_controller/metal/params_wrapper.rb000066400000000000000000000231041266740050600257170ustar00rootroot00000000000000require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/struct' require 'action_dispatch/http/mime_type' module ActionController # Wraps the parameters hash into a nested hash. This will allow clients to # submit requests without having to specify any root elements. # # This functionality is enabled in +config/initializers/wrap_parameters.rb+ # and can be customized. If you are upgrading to \Rails 3.1, this file will # need to be created for the functionality to be enabled. # # You could also turn it on per controller by setting the format array to # a non-empty array: # # class UsersController < ApplicationController # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form] # end # # If you enable +ParamsWrapper+ for +:json+ format, instead of having to # send JSON parameters like this: # # {"user": {"name": "Konata"}} # # You can send parameters like this: # # {"name": "Konata"} # # And it will be wrapped into a nested hash with the key name matching the # controller's name. For example, if you're posting to +UsersController+, # your new +params+ hash will look like this: # # {"name" => "Konata", "user" => {"name" => "Konata"}} # # You can also specify the key in which the parameters should be wrapped to, # and also the list of attributes it should wrap by using either +:include+ or # +:exclude+ options like this: # # class UsersController < ApplicationController # wrap_parameters :person, include: [:username, :password] # end # # On ActiveRecord models with no +:include+ or +:exclude+ option set, # it will only wrap the parameters returned by the class method # attribute_names. # # If you're going to pass the parameters to an +ActiveModel+ object (such as # User.new(params[:user])), you might consider passing the model class to # the method instead. The +ParamsWrapper+ will actually try to determine the # list of attribute names from the model and only wrap those attributes: # # class UsersController < ApplicationController # wrap_parameters Person # end # # You still could pass +:include+ and +:exclude+ to set the list of attributes # you want to wrap. # # By default, if you don't specify the key in which the parameters would be # wrapped to, +ParamsWrapper+ will actually try to determine if there's # a model related to it or not. This controller, for example: # # class Admin::UsersController < ApplicationController # end # # will try to check if Admin::User or +User+ model exists, and use it to # determine the wrapper key respectively. If both models don't exist, # it will then fallback to use +user+ as the key. module ParamsWrapper extend ActiveSupport::Concern EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8) require 'mutex_m' class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc: include Mutex_m def self.from_hash(hash) name = hash[:name] format = Array(hash[:format]) include = hash[:include] && Array(hash[:include]).collect(&:to_s) exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s) new name, format, include, exclude, nil, nil end def initialize(name, format, include, exclude, klass, model) # nodoc super @include_set = include @name_set = name end def model super || synchronize { super || self.model = _default_wrap_model } end def include return super if @include_set m = model synchronize do return super if @include_set @include_set = true unless super || exclude if m.respond_to?(:attribute_names) && m.attribute_names.any? self.include = m.attribute_names end end end end def name return super if @name_set m = model synchronize do return super if @name_set @name_set = true unless super || klass.anonymous? self.name = m ? m.to_s.demodulize.underscore : klass.controller_name.singularize end end end private # Determine the wrapper model from the controller's name. By convention, # this could be done by trying to find the defined model that has the # same singularize name as the controller. For example, +UsersController+ # will try to find if the +User+ model exists. # # This method also does namespace lookup. Foo::Bar::UsersController will # try to find Foo::Bar::User, Foo::User and finally User. def _default_wrap_model #:nodoc: return nil if klass.anonymous? model_name = klass.name.sub(/Controller$/, '').classify begin if model_klass = model_name.safe_constantize model_klass else namespaces = model_name.split("::") namespaces.delete_at(-2) break if namespaces.last == model_name model_name = namespaces.join("::") end end until model_klass model_klass end end included do class_attribute :_wrapper_options self._wrapper_options = Options.from_hash(format: []) end module ClassMethods def _set_wrapper_options(options) self._wrapper_options = Options.from_hash(options) end # Sets the name of the wrapper key, or the model which +ParamsWrapper+ # would use to determine the attribute names from. # # ==== Examples # wrap_parameters format: :xml # # enables the parameter wrapper for XML format # # wrap_parameters :person # # wraps parameters into +params[:person]+ hash # # wrap_parameters Person # # wraps parameters by determining the wrapper key from Person class # (+person+, in this case) and the list of attribute names # # wrap_parameters include: [:username, :title] # # wraps only +:username+ and +:title+ attributes from parameters. # # wrap_parameters false # # disables parameters wrapping for this controller altogether. # # ==== Options # * :format - The list of formats in which the parameters wrapper # will be enabled. # * :include - The list of attribute names which parameters wrapper # will wrap into a nested hash. # * :exclude - The list of attribute names which parameters wrapper # will exclude from a nested hash. def wrap_parameters(name_or_model_or_options, options = {}) model = nil case name_or_model_or_options when Hash options = name_or_model_or_options when false options = options.merge(:format => []) when Symbol, String options = options.merge(:name => name_or_model_or_options) else model = name_or_model_or_options end opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options) opts.model = model opts.klass = self self._wrapper_options = opts end # Sets the default wrapper key or model which will be used to determine # wrapper key and attribute names. Will be called automatically when the # module is inherited. def inherited(klass) if klass._wrapper_options.format.any? params = klass._wrapper_options.dup params.klass = klass klass._wrapper_options = params end super end end # Performs parameters wrapping upon the request. Will be called automatically # by the metal call stack. def process_action(*args) if _wrapper_enabled? if request.parameters[_wrapper_key].present? wrapped_hash = _extract_parameters(request.parameters) else wrapped_hash = _wrap_parameters request.request_parameters end wrapped_keys = request.request_parameters.keys wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys) # This will make the wrapped hash accessible from controller and view request.parameters.merge! wrapped_hash request.request_parameters.merge! wrapped_hash # This will display the wrapped hash in the log file request.filtered_parameters.merge! wrapped_filtered_hash end super end private # Returns the wrapper key which will be used to stored wrapped parameters. def _wrapper_key _wrapper_options.name end # Returns the list of enabled formats. def _wrapper_formats _wrapper_options.format end # Returns the list of parameters which will be selected for wrapped. def _wrap_parameters(parameters) { _wrapper_key => _extract_parameters(parameters) } end def _extract_parameters(parameters) if include_only = _wrapper_options.include parameters.slice(*include_only) else exclude = _wrapper_options.exclude || [] parameters.except(*(exclude + EXCLUDE_PARAMETERS)) end end # Checks if we should perform parameters wrapping. def _wrapper_enabled? ref = request.content_mime_type.try(:ref) _wrapper_formats.include?(ref) && _wrapper_key && !request.request_parameters[_wrapper_key] end end end rails-4.2.6/actionpack/lib/action_controller/metal/rack_delegation.rb000066400000000000000000000013111266740050600260030ustar00rootroot00000000000000require 'action_dispatch/http/request' require 'action_dispatch/http/response' module ActionController module RackDelegation extend ActiveSupport::Concern delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :response_code, :to => "@_response" def dispatch(action, request) set_response!(request) super(action, request) end def response_body=(body) response.body = body if response super end def reset_session @_request.reset_session end private def set_response!(request) @_response = ActionDispatch::Response.new @_response.request = request end end end rails-4.2.6/actionpack/lib/action_controller/metal/redirecting.rb000066400000000000000000000130351266740050600251750ustar00rootroot00000000000000module ActionController class RedirectBackError < AbstractController::Error #:nodoc: DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' def initialize(message = nil) super(message || DEFAULT_MESSAGE) end end module Redirecting extend ActiveSupport::Concern include AbstractController::Logger include ActionController::RackDelegation include ActionController::UrlFor # Redirects the browser to the target specified in +options+. This parameter can be any one of: # # * Hash - The URL will be generated by calling url_for with the +options+. # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. # * String starting with protocol:// (like http://) or a protocol relative reference (like //) - Is passed straight through as the target for redirection. # * String not containing a protocol - The current protocol and host is prepended to the string. # * Proc - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+. # * :back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. # Short-hand for redirect_to(request.env["HTTP_REFERER"]) # # === Examples: # # redirect_to action: "show", id: 5 # redirect_to post # redirect_to "http://www.rubyonrails.org" # redirect_to "/images/screenshot.jpg" # redirect_to articles_url # redirect_to :back # redirect_to proc { edit_post_url(@post) } # # The redirection happens as a "302 Found" header unless otherwise specified using the :status option: # # redirect_to post_url(@post), status: :found # redirect_to action: 'atom', status: :moved_permanently # redirect_to post_url(@post), status: 301 # redirect_to action: 'atom', status: 302 # # The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an # integer, or a symbol representing the downcased, underscored and symbolized description. # Note that the status code must be a 3xx HTTP code, or redirection will not occur. # # If you are using XHR requests other than GET or POST and redirecting after the # request then some browsers will follow the redirect using the original request # method. This may lead to undesirable behavior such as a double DELETE. To work # around this you can return a 303 See Other status code which will be # followed using a GET request. # # redirect_to posts_url, status: :see_other # redirect_to action: 'index', status: 303 # # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names # +alert+ and +notice+ as well as a general purpose +flash+ bucket. # # redirect_to post_url(@post), alert: "Watch it, mister!" # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road" # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id } # redirect_to({ action: 'atom' }, alert: "Something serious happened") # # When using redirect_to :back, if there is no referrer, # ActionController::RedirectBackError will be raised. You # may specify some fallback behavior for this case by rescuing # ActionController::RedirectBackError. def redirect_to(options = {}, response_status = {}) #:doc: raise ActionControllerError.new("Cannot redirect to nil!") unless options raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters) raise AbstractController::DoubleRenderError if response_body self.status = _extract_redirect_to_status(options, response_status) self.location = _compute_redirect_to_location(request, options) self.response_body = "You are being
redirected." end def _compute_redirect_to_location(request, options) #:nodoc: case options # The scheme name consist of a letter followed by any combination of # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") # characters; and is terminated by a colon (":"). # See http://tools.ietf.org/html/rfc3986#section-3.1 # The protocol relative scheme starts with a double slash "//". when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i options when String request.protocol + request.host_with_port + options when :back request.headers["Referer"] or raise RedirectBackError when Proc _compute_redirect_to_location request, options.call else url_for(options) end.delete("\0\r\n") end module_function :_compute_redirect_to_location public :_compute_redirect_to_location private def _extract_redirect_to_status(options, response_status) if options.is_a?(Hash) && options.key?(:status) Rack::Utils.status_code(options.delete(:status)) elsif response_status.key?(:status) Rack::Utils.status_code(response_status[:status]) else 302 end end end end rails-4.2.6/actionpack/lib/action_controller/metal/renderers.rb000066400000000000000000000076601266740050600246760ustar00rootroot00000000000000require 'set' module ActionController # See Renderers.add def self.add_renderer(key, &block) Renderers.add(key, &block) end # See Renderers.remove def self.remove_renderer(key) Renderers.remove(key) end class MissingRenderer < LoadError def initialize(format) super "No renderer defined for format: #{format}" end end module Renderers extend ActiveSupport::Concern included do class_attribute :_renderers self._renderers = Set.new.freeze end module ClassMethods def use_renderers(*args) renderers = _renderers + args self._renderers = renderers.freeze end alias use_renderer use_renderers end def render_to_body(options) _render_to_body_with_renderer(options) || super end def _render_to_body_with_renderer(options) _renderers.each do |name| if options.key?(name) _process_options(options) method_name = Renderers._render_with_renderer_method_name(name) return send(method_name, options.delete(name), options) end end nil end # A Set containing renderer names that correspond to available renderer procs. # Default values are :json, :js, :xml. RENDERERS = Set.new def self._render_with_renderer_method_name(key) "_render_with_renderer_#{key}" end # Adds a new renderer to call within controller actions. # A renderer is invoked by passing its name as an option to # AbstractController::Rendering#render. To create a renderer # pass it a name and a block. The block takes two arguments, the first # is the value paired with its key and the second is the remaining # hash of options passed to +render+. # # Create a csv renderer: # # ActionController::Renderers.add :csv do |obj, options| # filename = options[:filename] || 'data' # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s # send_data str, type: Mime::CSV, # disposition: "attachment; filename=#{filename}.csv" # end # # Note that we used Mime::CSV for the csv mime type as it comes with Rails. # For a custom renderer, you'll need to register a mime type with # Mime::Type.register. # # To use the csv renderer in a controller action: # # def show # @csvable = Csvable.find(params[:id]) # respond_to do |format| # format.html # format.csv { render csv: @csvable, filename: @csvable.name } # end # end # To use renderers and their mime types in more concise ways, see # ActionController::MimeResponds::ClassMethods.respond_to def self.add(key, &block) define_method(_render_with_renderer_method_name(key), &block) RENDERERS << key.to_sym end # This method is the opposite of add method. # # Usage: # # ActionController::Renderers.remove(:csv) def self.remove(key) RENDERERS.delete(key.to_sym) method_name = _render_with_renderer_method_name(key) remove_method(method_name) if method_defined?(method_name) end module All extend ActiveSupport::Concern include Renderers included do self._renderers = RENDERERS end end add :json do |json, options| json = json.to_json(options) unless json.kind_of?(String) if options[:callback].present? if content_type.nil? || content_type == Mime::JSON self.content_type = Mime::JS end "/**/#{options[:callback]}(#{json})" else self.content_type ||= Mime::JSON json end end add :js do |js, options| self.content_type ||= Mime::JS js.respond_to?(:to_js) ? js.to_js(options) : js end add :xml do |xml, options| self.content_type ||= Mime::XML xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml end end end rails-4.2.6/actionpack/lib/action_controller/metal/rendering.rb000066400000000000000000000050401266740050600246500ustar00rootroot00000000000000module ActionController module Rendering extend ActiveSupport::Concern RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html] # Before processing, set the request formats in current controller formats. def process_action(*) #:nodoc: self.formats = request.formats.map(&:ref).compact super end # Check for double render errors and set the content_type after rendering. def render(*args) #:nodoc: raise ::AbstractController::DoubleRenderError if self.response_body super end # Overwrite render_to_string because body can now be set to a rack body. def render_to_string(*) result = super if result.respond_to?(:each) string = "" result.each { |r| string << r } string else result end end def render_to_body(options = {}) super || _render_in_priorities(options) || ' ' end private def _render_in_priorities(options) RENDER_FORMATS_IN_PRIORITY.each do |format| return options[format] if options.key?(format) end nil end def _process_format(format, options = {}) super if options[:plain] self.content_type = Mime::TEXT else self.content_type ||= format.to_s end end # Normalize arguments by catching blocks and setting them on :update. def _normalize_args(action=nil, options={}, &blk) #:nodoc: options = super options[:update] = blk if block_given? options end # Normalize both text and status options. def _normalize_options(options) #:nodoc: _normalize_text(options) if options[:html] options[:html] = ERB::Util.html_escape(options[:html]) end if options.delete(:nothing) options[:body] = nil end if options[:status] options[:status] = Rack::Utils.status_code(options[:status]) end super end def _normalize_text(options) RENDER_FORMATS_IN_PRIORITY.each do |format| if options.key?(format) && options[format].respond_to?(:to_text) options[format] = options[format].to_text end end end # Process controller specific options, as status, content-type and location. def _process_options(options) #:nodoc: status, content_type, location = options.values_at(:status, :content_type, :location) self.status = status if status self.content_type = content_type if content_type self.headers["Location"] = url_for(location) if location super end end end rails-4.2.6/actionpack/lib/action_controller/metal/request_forgery_protection.rb000066400000000000000000000317531266740050600304000ustar00rootroot00000000000000require 'rack/session/abstract/id' require 'action_controller/metal/exceptions' require 'active_support/security_utils' module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: end class InvalidCrossOriginRequest < ActionControllerError #:nodoc: end # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks # by including a token in the rendered HTML for your application. This token is # stored as a random string in the session, to which an attacker does not have # access. When a request reaches your application, \Rails verifies the received # token with the token in the session. Only HTML and JavaScript requests are checked, # so this will not protect your XML API (presumably you'll have a different # authentication scheme there anyway). # # GET requests are not protected since they don't have side effects like writing # to the database and don't leak sensitive information. JavaScript requests are # an exception: a third-party site can use a # # The first two characters (">) are required in case the exception happens # while rendering attributes for a given tag. You can check the real cause # for the exception in your logger. # # == Web server support # # Not all web servers support streaming out-of-the-box. You need to check # the instructions for each of them. # # ==== Unicorn # # Unicorn supports streaming but it needs to be configured. For this, you # need to create a config file as follow: # # # unicorn.config.rb # listen 3000, tcp_nopush: false # # And use it on initialization: # # unicorn_rails --config-file unicorn.config.rb # # You may also want to configure other parameters like :tcp_nodelay. # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen # # If you are using Unicorn with NGINX, you may need to tweak NGINX. # Streaming should work out of the box on Rainbows. # # ==== Passenger # # To be described. # module Streaming extend ActiveSupport::Concern protected # Set proper cache control and transfer encoding when streaming def _process_options(options) #:nodoc: super if options[:stream] if env["HTTP_VERSION"] == "HTTP/1.0" options.delete(:stream) else headers["Cache-Control"] ||= "no-cache" headers["Transfer-Encoding"] = "chunked" headers.delete("Content-Length") end end end # Call render_body if we are streaming instead of usual +render+. def _render_template(options) #:nodoc: if options.delete(:stream) Rack::Chunked::Body.new view_renderer.render_body(view_context, options) else super end end end end rails-4.2.6/actionpack/lib/action_controller/metal/strong_parameters.rb000066400000000000000000000562201266740050600264400ustar00rootroot00000000000000require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/filters' require 'active_support/deprecation' require 'active_support/rescuable' require 'action_dispatch/http/upload' require 'stringio' require 'set' module ActionController # Raised when a required parameter is missing. # # params = ActionController::Parameters.new(a: {}) # params.fetch(:b) # # => ActionController::ParameterMissing: param not found: b # params.require(:a) # # => ActionController::ParameterMissing: param not found: a class ParameterMissing < KeyError attr_reader :param # :nodoc: def initialize(param) # :nodoc: @param = param super("param is missing or the value is empty: #{param}") end end # Raised when a supplied parameter is not expected and # ActionController::Parameters.action_on_unpermitted_parameters # is set to :raise. # # params = ActionController::Parameters.new(a: "123", b: "456") # params.permit(:c) # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b class UnpermittedParameters < IndexError attr_reader :params # :nodoc: def initialize(params) # :nodoc: @params = params super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}") end end # == Action Controller \Parameters # # Allows to choose which attributes should be whitelisted for mass updating # and thus prevent accidentally exposing that which shouldn't be exposed. # Provides two methods for this purpose: #require and #permit. The former is # used to mark parameters as required. The latter is used to set the parameter # as permitted and limit which attributes should be allowed for mass updating. # # params = ActionController::Parameters.new({ # person: { # name: 'Francesco', # age: 22, # role: 'admin' # } # }) # # permitted = params.require(:person).permit(:name, :age) # permitted # => {"name"=>"Francesco", "age"=>22} # permitted.class # => ActionController::Parameters # permitted.permitted? # => true # # Person.first.update!(permitted) # # => # # # It provides two options that controls the top-level behavior of new instances: # # * +permit_all_parameters+ - If it's +true+, all the parameters will be # permitted by default. The default is +false+. # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters # that are not explicitly permitted are found. The values can be :log to # write a message on the logger or :raise to raise # ActionController::UnpermittedParameters exception. The default value is :log # in test and development environments, +false+ otherwise. # # Examples: # # params = ActionController::Parameters.new # params.permitted? # => false # # ActionController::Parameters.permit_all_parameters = true # # params = ActionController::Parameters.new # params.permitted? # => true # # params = ActionController::Parameters.new(a: "123", b: "456") # params.permit(:c) # # => {} # # ActionController::Parameters.action_on_unpermitted_parameters = :raise # # params = ActionController::Parameters.new(a: "123", b: "456") # params.permit(:c) # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b # # Please note that these options *are not thread-safe*. In a multi-threaded # environment they should only be set once at boot-time and never mutated at # runtime. # # ActionController::Parameters inherits from # ActiveSupport::HashWithIndifferentAccess, this means # that you can fetch values using either :key or "key". # # params = ActionController::Parameters.new(key: 'value') # params[:key] # => "value" # params["key"] # => "value" class Parameters < ActiveSupport::HashWithIndifferentAccess cattr_accessor :permit_all_parameters, instance_accessor: false cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false # By default, never raise an UnpermittedParameters exception if these # params are present. The default includes both 'controller' and 'action' # because they are added by Rails and should be of no concern. One way # to change these is to specify `always_permitted_parameters` in your # config. For instance: # # config.always_permitted_parameters = %w( controller action format ) cattr_accessor :always_permitted_parameters self.always_permitted_parameters = %w( controller action ) def self.const_missing(const_name) super unless const_name == :NEVER_UNPERMITTED_PARAMS ActiveSupport::Deprecation.warn(<<-MSG.squish) `ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated. Use `ActionController::Parameters.always_permitted_parameters` instead. MSG always_permitted_parameters end # Returns a new instance of ActionController::Parameters. # Also, sets the +permitted+ attribute to the default value of # ActionController::Parameters.permit_all_parameters. # # class Person < ActiveRecord::Base # end # # params = ActionController::Parameters.new(name: 'Francesco') # params.permitted? # => false # Person.new(params) # => ActiveModel::ForbiddenAttributesError # # ActionController::Parameters.permit_all_parameters = true # # params = ActionController::Parameters.new(name: 'Francesco') # params.permitted? # => true # Person.new(params) # => # def initialize(attributes = nil) super(attributes) @permitted = self.class.permit_all_parameters end # Returns a safe +Hash+ representation of this parameter with all # unpermitted keys removed. # # params = ActionController::Parameters.new({ # name: 'Senjougahara Hitagi', # oddity: 'Heavy stone crab' # }) # params.to_h # => {} # # safe_params = params.permit(:name) # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} def to_h if permitted? to_hash else slice(*self.class.always_permitted_parameters).permit!.to_h end end # Returns an unsafe, unfiltered +Hash+ representation of this parameter. def to_unsafe_h to_hash end alias_method :to_unsafe_hash, :to_unsafe_h # Convert all hashes in values into parameters, then yield each pair like # the same way as Hash#each_pair def each_pair(&block) super do |key, value| convert_hashes_to_parameters(key, value) end super end alias_method :each, :each_pair # Attribute that keeps track of converted arrays, if any, to avoid double # looping in the common use case permit + mass-assignment. Defined in a # method to instantiate it only if needed. # # Testing membership still loops, but it's going to be faster than our own # loop that converts values. Also, we are not going to build a new array # object per fetch. def converted_arrays @converted_arrays ||= Set.new end # Returns +true+ if the parameter is permitted, +false+ otherwise. # # params = ActionController::Parameters.new # params.permitted? # => false # params.permit! # params.permitted? # => true def permitted? @permitted end # Sets the +permitted+ attribute to +true+. This can be used to pass # mass assignment. Returns +self+. # # class Person < ActiveRecord::Base # end # # params = ActionController::Parameters.new(name: 'Francesco') # params.permitted? # => false # Person.new(params) # => ActiveModel::ForbiddenAttributesError # params.permit! # params.permitted? # => true # Person.new(params) # => # def permit! each_pair do |key, value| Array.wrap(value).each do |v| v.permit! if v.respond_to? :permit! end end @permitted = true self end # Ensures that a parameter is present. If it's present, returns # the parameter at the given +key+, otherwise raises an # ActionController::ParameterMissing error. # # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person) # # => {"name"=>"Francesco"} # # ActionController::Parameters.new(person: nil).require(:person) # # => ActionController::ParameterMissing: param not found: person # # ActionController::Parameters.new(person: {}).require(:person) # # => ActionController::ParameterMissing: param not found: person def require(key) value = self[key] if value.present? || value == false value else raise ParameterMissing.new(key) end end # Alias of #require. alias :required :require # Returns a new ActionController::Parameters instance that # includes only the given +filters+ and sets the +permitted+ attribute # for the object to +true+. This is useful for limiting which attributes # should be allowed for mass updating. # # params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' }) # permitted = params.require(:user).permit(:name, :age) # permitted.permitted? # => true # permitted.has_key?(:name) # => true # permitted.has_key?(:age) # => true # permitted.has_key?(:role) # => false # # Only permitted scalars pass the filter. For example, given # # params.permit(:name) # # +:name+ passes it is a key of +params+ whose associated value is of type # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+. # Otherwise, the key +:name+ is filtered out. # # You may declare that the parameter should be an array of permitted scalars # by mapping it to an empty array: # # params = ActionController::Parameters.new(tags: ['rails', 'parameters']) # params.permit(tags: []) # # You can also use +permit+ on nested parameters, like: # # params = ActionController::Parameters.new({ # person: { # name: 'Francesco', # age: 22, # pets: [{ # name: 'Purplish', # category: 'dogs' # }] # } # }) # # permitted = params.permit(person: [ :name, { pets: :name } ]) # permitted.permitted? # => true # permitted[:person][:name] # => "Francesco" # permitted[:person][:age] # => nil # permitted[:person][:pets][0][:name] # => "Purplish" # permitted[:person][:pets][0][:category] # => nil # # Note that if you use +permit+ in a key that points to a hash, # it won't allow all the hash. You also need to specify which # attributes inside the hash should be whitelisted. # # params = ActionController::Parameters.new({ # person: { # contact: { # email: 'none@test.com', # phone: '555-1234' # } # } # }) # # params.require(:person).permit(:contact) # # => {} # # params.require(:person).permit(contact: :phone) # # => {"contact"=>{"phone"=>"555-1234"}} # # params.require(:person).permit(contact: [ :email, :phone ]) # # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}} def permit(*filters) params = self.class.new filters.flatten.each do |filter| case filter when Symbol, String permitted_scalar_filter(params, filter) when Hash then hash_filter(params, filter) end end unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters params.permit! end # Returns a parameter for the given +key+. If not found, # returns +nil+. # # params = ActionController::Parameters.new(person: { name: 'Francesco' }) # params[:person] # => {"name"=>"Francesco"} # params[:none] # => nil def [](key) convert_hashes_to_parameters(key, super) end # Returns a parameter for the given +key+. If the +key+ # can't be found, there are several options: With no other arguments, # it will raise an ActionController::ParameterMissing error; # if more arguments are given, then that will be returned; if a block # is given, then that will be run and its result returned. # # params = ActionController::Parameters.new(person: { name: 'Francesco' }) # params.fetch(:person) # => {"name"=>"Francesco"} # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none # params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco" def fetch(key, *args) convert_hashes_to_parameters(key, super, false) rescue KeyError raise ActionController::ParameterMissing.new(key) end # Returns a new ActionController::Parameters instance that # includes only the given +keys+. If the given +keys+ # don't exist, returns an empty hash. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) # params.slice(:a, :b) # => {"a"=>1, "b"=>2} # params.slice(:d) # => {} def slice(*keys) new_instance_with_inherited_permitted_status(super) end # Removes and returns the key/value pairs matching the given keys. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) # params.extract!(:a, :b) # => {"a"=>1, "b"=>2} # params # => {"c"=>3} def extract!(*keys) new_instance_with_inherited_permitted_status(super) end # Returns a new ActionController::Parameters with the results of # running +block+ once for every value. The keys are unchanged. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) # params.transform_values { |x| x * 2 } # # => {"a"=>2, "b"=>4, "c"=>6} def transform_values if block_given? new_instance_with_inherited_permitted_status(super) else super end end # This method is here only to make sure that the returned object has the # correct +permitted+ status. It should not matter since the parent of # this object is +HashWithIndifferentAccess+ def transform_keys # :nodoc: if block_given? new_instance_with_inherited_permitted_status(super) else super end end # Deletes and returns a key-value pair from +Parameters+ whose key is equal # to key. If the key is not found, returns the default value. If the # optional code block is given and the key is not found, pass in the key # and return the result of block. def delete(key, &block) convert_hashes_to_parameters(key, super, false) end # Equivalent to Hash#keep_if, but returns nil if no changes were made. def select!(&block) convert_value_to_parameters(super) end # Returns an exact copy of the ActionController::Parameters # instance. +permitted+ state is kept on the duped object. # # params = ActionController::Parameters.new(a: 1) # params.permit! # params.permitted? # => true # copy_params = params.dup # => {"a"=>1} # copy_params.permitted? # => true def dup super.tap do |duplicate| duplicate.permitted = @permitted end end protected def permitted=(new_permitted) @permitted = new_permitted end private def new_instance_with_inherited_permitted_status(hash) self.class.new(hash).tap do |new_instance| new_instance.permitted = @permitted end end def convert_hashes_to_parameters(key, value, assign_if_converted=true) converted = convert_value_to_parameters(value) self[key] = converted if assign_if_converted && !converted.equal?(value) converted end def convert_value_to_parameters(value) if value.is_a?(Array) && !converted_arrays.member?(value) converted = value.map { |_| convert_value_to_parameters(_) } converted_arrays << converted converted elsif value.is_a?(Parameters) || !value.is_a?(Hash) value else self.class.new(value) end end def each_element(object) if object.is_a?(Array) object.map { |el| yield el }.compact elsif fields_for_style?(object) hash = object.class.new object.each { |k,v| hash[k] = yield v } hash else yield object end end def fields_for_style?(object) object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) } end def unpermitted_parameters!(params) unpermitted_keys = unpermitted_keys(params) if unpermitted_keys.any? case self.class.action_on_unpermitted_parameters when :log name = "unpermitted_parameters.action_controller" ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys) when :raise raise ActionController::UnpermittedParameters.new(unpermitted_keys) end end end def unpermitted_keys(params) self.keys - params.keys - self.always_permitted_parameters end # # --- Filtering ---------------------------------------------------------- # # This is a white list of permitted scalar types that includes the ones # supported in XML and JSON requests. # # This list is in particular used to filter ordinary requests, String goes # as first element to quickly short-circuit the common case. # # If you modify this collection please update the API of +permit+ above. PERMITTED_SCALAR_TYPES = [ String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, # DateTimes are Dates, we document the type but avoid the redundant check. StringIO, IO, ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile, ] def permitted_scalar?(value) PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)} end def permitted_scalar_filter(params, key) if has_key?(key) && permitted_scalar?(self[key]) params[key] = self[key] end keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k| if permitted_scalar?(self[k]) params[k] = self[k] end end end def array_of_permitted_scalars?(value) if value.is_a?(Array) value.all? {|element| permitted_scalar?(element)} end end def array_of_permitted_scalars_filter(params, key) if has_key?(key) && array_of_permitted_scalars?(self[key]) params[key] = self[key] end end EMPTY_ARRAY = [] def hash_filter(params, filter) filter = filter.with_indifferent_access # Slicing filters out non-declared keys. slice(*filter.keys).each do |key, value| next unless value if filter[key] == EMPTY_ARRAY # Declaration { comment_ids: [] }. array_of_permitted_scalars_filter(params, key) else # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }. params[key] = each_element(value) do |element| if element.is_a?(Hash) element = self.class.new(element) unless element.respond_to?(:permit) element.permit(*Array.wrap(filter[key])) end end end end end end # == Strong \Parameters # # It provides an interface for protecting attributes from end-user # assignment. This makes Action Controller parameters forbidden # to be used in Active Model mass assignment until they have been # whitelisted. # # In addition, parameters can be marked as required and flow through a # predefined raise/rescue flow to end up as a 400 Bad Request with no # effort. # # class PeopleController < ActionController::Base # # Using "Person.create(params[:person])" would raise an # # ActiveModel::ForbiddenAttributes exception because it'd # # be using mass assignment without an explicit permit step. # # This is the recommended form: # def create # Person.create(person_params) # end # # # This will pass with flying colors as long as there's a person key in the # # parameters, otherwise it'll raise an ActionController::MissingParameter # # exception, which will get caught by ActionController::Base and turned # # into a 400 Bad Request reply. # def update # redirect_to current_account.people.find(params[:id]).tap { |person| # person.update!(person_params) # } # end # # private # # Using a private method to encapsulate the permissible parameters is # # just a good pattern since you'll be able to reuse the same permit # # list between create and update. Also, you can specialize this method # # with per-user checking of permissible attributes. # def person_params # params.require(:person).permit(:name, :age) # end # end # # In order to use accepts_nested_attributes_for with Strong \Parameters, you # will need to specify which nested attributes should be whitelisted. # # class Person # has_many :pets # accepts_nested_attributes_for :pets # end # # class PeopleController < ActionController::Base # def create # Person.create(person_params) # end # # ... # # private # # def person_params # # It's mandatory to specify the nested attributes that should be whitelisted. # # If you use `permit` with just the key that points to the nested attributes hash, # # it will return an empty hash. # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ]) # end # end # # See ActionController::Parameters.require and ActionController::Parameters.permit # for more information. module StrongParameters extend ActiveSupport::Concern include ActiveSupport::Rescuable # Returns a new ActionController::Parameters object that # has been instantiated with the request.parameters. def params @_params ||= Parameters.new(request.parameters) end # Assigns the given +value+ to the +params+ hash. If +value+ # is a Hash, this will create an ActionController::Parameters # object that has been instantiated with the given +value+ hash. def params=(value) @_params = value.is_a?(Hash) ? Parameters.new(value) : value end end end rails-4.2.6/actionpack/lib/action_controller/metal/testing.rb000066400000000000000000000013021266740050600243450ustar00rootroot00000000000000module ActionController module Testing extend ActiveSupport::Concern include RackDelegation # TODO : Rewrite tests using controller.headers= to use Rack env def headers=(new_headers) @_response ||= ActionDispatch::Response.new @_response.headers.replace(new_headers) end # Behavior specific to functional tests module Functional # :nodoc: def set_response!(request) end def recycle! @_url_options = nil self.formats = nil self.params = nil end end module ClassMethods def before_filters _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name} end end end end rails-4.2.6/actionpack/lib/action_controller/metal/url_for.rb000066400000000000000000000032121266740050600243420ustar00rootroot00000000000000module ActionController # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing # the _routes method. Otherwise, an exception will be raised. # # In addition to AbstractController::UrlFor, this module accesses the HTTP layer to define # url options like the +host+. In order to do so, this module requires the host class # to implement +env+ and +request+, which need to be a Rack-compatible. # # class RootUrl # include ActionController::UrlFor # include Rails.application.routes.url_helpers # # delegate :env, :request, to: :controller # # def initialize(controller) # @controller = controller # @url = root_path # named route from the application. # end # end module UrlFor extend ActiveSupport::Concern include AbstractController::UrlFor def url_options @_url_options ||= { :host => request.host, :port => request.optional_port, :protocol => request.protocol, :_recall => request.path_parameters }.merge!(super).freeze if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) || (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) || (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze]) options = @_url_options.dup if original_script_name options[:original_script_name] = original_script_name else options[:script_name] = same_origin ? request.script_name.dup : script_name end options.freeze else @_url_options end end end end rails-4.2.6/actionpack/lib/action_controller/middleware.rb000066400000000000000000000013351266740050600237110ustar00rootroot00000000000000module ActionController class Middleware < Metal class ActionMiddleware def initialize(controller, app) @controller, @app = controller, app end def call(env) request = ActionDispatch::Request.new(env) @controller.build(@app).dispatch(:index, request) end end class << self alias build new def new(app) ActionMiddleware.new(self, app) end end attr_internal :app def process(action) response = super self.status, self.headers, self.response_body = response if response.is_a?(Array) response end def initialize(app) super() @_app = app end def index call(env) end end endrails-4.2.6/actionpack/lib/action_controller/model_naming.rb000066400000000000000000000005251266740050600242250ustar00rootroot00000000000000module ActionController module ModelNaming # Converts the given object to an ActiveModel compliant one. def convert_to_model(object) object.respond_to?(:to_model) ? object.to_model : object end def model_name_from_record_or_class(record_or_class) convert_to_model(record_or_class).model_name end end end rails-4.2.6/actionpack/lib/action_controller/railtie.rb000066400000000000000000000047551266740050600232360ustar00rootroot00000000000000require "rails" require "action_controller" require "action_dispatch/railtie" require "abstract_controller/railties/routes_helpers" require "action_controller/railties/helpers" require "action_view/railtie" module ActionController class Railtie < Rails::Railtie #:nodoc: config.action_controller = ActiveSupport::OrderedOptions.new config.eager_load_namespaces << ActionController initializer "action_controller.assets_config", :group => :all do |app| app.config.action_controller.assets_dir ||= app.config.paths["public"].first end initializer "action_controller.set_helpers_path" do |app| ActionController::Helpers.helpers_path = app.helpers_paths end initializer "action_controller.parameters_config" do |app| options = app.config.action_controller ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false } if app.config.action_controller[:always_permitted_parameters] ActionController::Parameters.always_permitted_parameters = app.config.action_controller.delete(:always_permitted_parameters) end ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do (Rails.env.test? || Rails.env.development?) ? :log : false end end initializer "action_controller.set_configs" do |app| paths = app.config.paths options = app.config.action_controller options.logger ||= Rails.logger options.cache_store ||= Rails.cache options.javascripts_dir ||= paths["public/javascripts"].first options.stylesheets_dir ||= paths["public/stylesheets"].first # Ensure readers methods get compiled options.asset_host ||= app.config.asset_host options.relative_url_root ||= app.config.relative_url_root ActiveSupport.on_load(:action_controller) do include app.routes.mounted_helpers extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) extend ::ActionController::Railties::Helpers options.each do |k,v| k = "#{k}=" if respond_to?(k) send(k, v) elsif !Base.respond_to?(k) raise "Invalid option key: #{k}" end end end end initializer "action_controller.compile_config_methods" do ActiveSupport.on_load(:action_controller) do config.compile_methods! if config.respond_to?(:compile_methods!) end end end end rails-4.2.6/actionpack/lib/action_controller/railties/000077500000000000000000000000001266740050600230615ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_controller/railties/helpers.rb000066400000000000000000000011121266740050600250430ustar00rootroot00000000000000module ActionController module Railties module Helpers def inherited(klass) super return unless klass.respond_to?(:helpers_path=) if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) } paths = namespace.railtie_helpers_paths else paths = ActionController::Helpers.helpers_path end klass.helpers_path = paths if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers klass.helper :all end end end end end rails-4.2.6/actionpack/lib/action_controller/test_case.rb000066400000000000000000000652321266740050600235540ustar00rootroot00000000000000require 'rack/session/abstract/id' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/hash/keys' require 'active_support/deprecation' require 'rails-dom-testing' module ActionController module TemplateAssertions extend ActiveSupport::Concern included do setup :setup_subscriptions teardown :teardown_subscriptions end RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze def setup_subscriptions RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable| instance_variable_set("@_#{instance_variable}", Hash.new(0)) end @_subscribers = [] @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload| path = payload[:layout] if path @_layouts[path] += 1 if path =~ /^layouts\/(.*)/ @_layouts[$1] += 1 end end end @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| if virtual_path = payload[:virtual_path] partial = virtual_path =~ /^.*\/_[^\/]*$/ if partial @_partials[virtual_path] += 1 @_partials[virtual_path.split("/").last] += 1 end @_templates[virtual_path] += 1 else path = payload[:identifier] if path @_files[path] += 1 @_files[path.split("/").last] += 1 end end end end def teardown_subscriptions return unless defined?(@_subscribers) @_subscribers.each do |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) end end def process(*args) reset_template_assertion super end def reset_template_assertion RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable| ivar_name = "@_#{instance_variable}" if instance_variable_defined?(ivar_name) instance_variable_get(ivar_name).clear end end end # Asserts that the request was rendered with the appropriate template file or partials. # # # assert that the "new" view template was rendered # assert_template "new" # # # assert that the exact template "admin/posts/new" was rendered # assert_template %r{\Aadmin/posts/new\Z} # # # assert that the layout 'admin' was rendered # assert_template layout: 'admin' # assert_template layout: 'layouts/admin' # assert_template layout: :admin # # # assert that no layout was rendered # assert_template layout: nil # assert_template layout: false # # # assert that the "_customer" partial was rendered twice # assert_template partial: '_customer', count: 2 # # # assert that no partials were rendered # assert_template partial: false # # # assert that a file was rendered # assert_template file: "README.rdoc" # # # assert that no file was rendered # assert_template file: nil # assert_template file: false # # In a view test case, you can also assert that specific locals are passed # to partials: # # # assert that the "_customer" partial was rendered with a specific object # assert_template partial: '_customer', locals: { customer: @customer } def assert_template(options = {}, message = nil) # Force body to be read in case the template is being streamed. response.body case options when NilClass, Regexp, String, Symbol options = options.to_s if Symbol === options rendered = @_templates msg = message || sprintf("expecting <%s> but rendering with <%s>", options.inspect, rendered.keys) matches_template = case options when String !options.empty? && rendered.any? do |t, num| options_splited = options.split(File::SEPARATOR) t_splited = t.split(File::SEPARATOR) t_splited.last(options_splited.size) == options_splited end when Regexp rendered.any? { |t,num| t.match(options) } when NilClass rendered.blank? end assert matches_template, msg when Hash options.assert_valid_keys(:layout, :partial, :locals, :count, :file) if options.key?(:layout) expected_layout = options[:layout] msg = message || sprintf("expecting layout <%s> but action rendered <%s>", expected_layout, @_layouts.keys) case expected_layout when String, Symbol assert_includes @_layouts.keys, expected_layout.to_s, msg when Regexp assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg) when nil, false assert(@_layouts.empty?, msg) end end if options[:file] assert_includes @_files.keys, options[:file] elsif options.key?(:file) assert @_files.blank?, "expected no files but #{@_files.keys} was rendered" end if expected_partial = options[:partial] if expected_locals = options[:locals] if defined?(@_rendered_views) view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/') partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial, expected_locals, @_rendered_views.locals_for(view)] assert(@_rendered_views.view_rendered?(view, options[:locals]), msg) else warn "the :locals option to #assert_template is only supported in a ActionView::TestCase" end elsif expected_count = options[:count] actual_count = @_partials[expected_partial] msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)", expected_partial, expected_count, actual_count) assert(actual_count == expected_count.to_i, msg) else msg = message || sprintf("expecting partial <%s> but action rendered <%s>", options[:partial], @_partials.keys) assert_includes @_partials, expected_partial, msg end elsif options.key?(:partial) assert @_partials.empty?, "Expected no partials to be rendered" end else raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil" end end end class TestRequest < ActionDispatch::TestRequest #:nodoc: DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup DEFAULT_ENV.delete 'PATH_INFO' def initialize(env = {}) super self.session = TestSession.new self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16)) end def assign_parameters(routes, controller_path, action, parameters = {}) parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) extra_keys = routes.extra_keys(parameters) non_path_parameters = get? ? query_parameters : request_parameters parameters.each do |key, value| if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?)) value = value.map{ |v| v.duplicable? ? v.dup : v } elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? }) value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }] elsif value.frozen? && value.duplicable? value = value.dup end if extra_keys.include?(key) non_path_parameters[key] = value else if value.is_a?(Array) value = value.map(&:to_param) else value = value.to_param end path_parameters[key] = value end end # Clear the combined params hash in case it was already referenced. @env.delete("action_dispatch.request.parameters") # Clear the filter cache variables so they're not stale @filtered_parameters = @filtered_env = @filtered_path = nil params = self.request_parameters.dup %w(controller action only_path).each do |k| params.delete(k) params.delete(k.to_sym) end data = params.to_query @env['CONTENT_LENGTH'] = data.length.to_s @env['rack.input'] = StringIO.new(data) end def recycle! @formats = nil @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } @method = @request_method = nil @fullpath = @ip = @remote_ip = @protocol = nil @env['action_dispatch.request.query_parameters'] = {} @set_cookies ||= {} @set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }]) deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies") @set_cookies.reject!{ |k,v| deleted_cookies.include?(k) } cookie_jar.update(rack_cookies) cookie_jar.update(cookies) cookie_jar.update(@set_cookies) cookie_jar.recycle! end private def default_env DEFAULT_ENV end end class TestResponse < ActionDispatch::TestResponse def recycle! initialize end end class LiveTestResponse < Live::Response def recycle! @body = nil initialize end def body @body ||= super end # Was the response successful? alias_method :success?, :successful? # Was the URL not found? alias_method :missing?, :not_found? # Were we redirected? alias_method :redirect?, :redirection? # Was there a server-side error? alias_method :error?, :server_error? end # Methods #destroy and #load! are overridden to avoid calling methods on the # @store object, which does not exist for the TestSession class. class TestSession < Rack::Session::Abstract::SessionHash #:nodoc: DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS def initialize(session = {}) super(nil, nil) @id = SecureRandom.hex(16) @data = stringify_keys(session) @loaded = true end def exists? true end def keys @data.keys end def values @data.values end def destroy clear end def fetch(key, *args, &block) @data.fetch(key.to_s, *args, &block) end private def load! @id end end # Superclass for ActionController functional tests. Functional tests allow you to # test a single controller action per test method. This should not be confused with # integration tests (see ActionDispatch::IntegrationTest), which are more like # "stories" that can involve multiple controllers and multiple actions (i.e. multiple # different HTTP requests). # # == Basic example # # Functional tests are written as follows: # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate # an HTTP request. # 2. Then, one asserts whether the current state is as expected. "State" can be anything: # the controller's HTTP response, the database contents, etc. # # For example: # # class BooksControllerTest < ActionController::TestCase # def test_create # # Simulate a POST response with the given HTTP parameters. # post(:create, book: { title: "Love Hina" }) # # # Assert that the controller tried to redirect us to # # the created book's URI. # assert_response :found # # # Assert that the controller really put the book in the database. # assert_not_nil Book.find_by(title: "Love Hina") # end # end # # You can also send a real document in the simulated HTTP request. # # def test_create # json = {book: { title: "Love Hina" }}.to_json # post :create, json # end # # == Special instance variables # # ActionController::TestCase will also automatically provide the following instance # variables for use in the tests: # # @controller:: # The controller instance that will be tested. # @request:: # An ActionController::TestRequest, representing the current HTTP # request. You can modify this object before sending the HTTP request. For example, # you might want to set some session properties before sending a GET request. # @response:: # An ActionController::TestResponse object, representing the response # of the last HTTP response. In the above example, @response becomes valid # after calling +post+. If the various assert methods are not sufficient, then you # may use this object to inspect the HTTP response in detail. # # (Earlier versions of \Rails required each functional test to subclass # Test::Unit::TestCase and define @controller, @request, @response in +setup+.) # # == Controller is automatically inferred # # ActionController::TestCase will automatically infer the controller under test # from the test class name. If the controller cannot be inferred from the test # class name, you can explicitly set it with +tests+. # # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase # tests WidgetController # end # # == \Testing controller internals # # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions # can be used against. These collections are: # # * assigns: Instance variables assigned in the action that are available for the view. # * session: Objects being saved in the session. # * flash: The flash objects currently in the session. # * cookies: \Cookies being sent to the user on this request. # # These collections can be used just like any other hash: # # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave" # assert flash.empty? # makes sure that there's nothing in the flash # # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing. # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work. # # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url. # # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another # action call which can then be asserted against. # # == Manipulating session and cookie variables # # Sometimes you need to set up the session and cookie variables for a test. # To do this just assign a value to the session or cookie collection: # # session[:key] = "value" # cookies[:key] = "value" # # To clear the cookies for a test just clear the cookie collection: # # cookies.clear # # == \Testing named routes # # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case. # # assert_redirected_to page_url(title: 'foo') class TestCase < ActiveSupport::TestCase module Behavior extend ActiveSupport::Concern include ActionDispatch::TestProcess include ActiveSupport::Testing::ConstantLookup include Rails::Dom::Testing::Assertions attr_reader :response, :request module ClassMethods # Sets the controller class name. Useful if the name can't be inferred from test class. # Normalizes +controller_class+ before using. # # tests WidgetController # tests :widget # tests 'widget' def tests(controller_class) case controller_class when String, Symbol self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize when Class self.controller_class = controller_class else raise ArgumentError, "controller class must be a String, Symbol, or Class" end end def controller_class=(new_class) self._controller_class = new_class end def controller_class if current_controller_class = self._controller_class current_controller_class else self.controller_class = determine_default_controller_class(name) end end def determine_default_controller_class(name) determine_constant_from_test_name(name) do |constant| Class === constant && constant < ActionController::Metal end end end # Simulate a GET request with the given parameters. # # - +action+: The controller action to call. # - +parameters+: The HTTP parameters that you want to pass. This may # be +nil+, a hash, or a string that is appropriately encoded # (application/x-www-form-urlencoded or multipart/form-data). # - +session+: A hash of parameters to store in the session. This may be +nil+. # - +flash+: A hash of parameters to store in the flash. This may be +nil+. # # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with # +post+, +patch+, +put+, +delete+, and +head+. # # Note that the request method is not verified. The different methods are # available to make the tests more expressive. def get(action, *args) process(action, "GET", *args) end # Simulate a POST request with the given parameters and set/volley the response. # See +get+ for more details. def post(action, *args) process(action, "POST", *args) end # Simulate a PATCH request with the given parameters and set/volley the response. # See +get+ for more details. def patch(action, *args) process(action, "PATCH", *args) end # Simulate a PUT request with the given parameters and set/volley the response. # See +get+ for more details. def put(action, *args) process(action, "PUT", *args) end # Simulate a DELETE request with the given parameters and set/volley the response. # See +get+ for more details. def delete(action, *args) process(action, "DELETE", *args) end # Simulate a HEAD request with the given parameters and set/volley the response. # See +get+ for more details. def head(action, *args) process(action, "HEAD", *args) end def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') __send__(request_method, action, parameters, session, flash).tap do @request.env.delete 'HTTP_X_REQUESTED_WITH' @request.env.delete 'HTTP_ACCEPT' end end alias xhr :xml_http_request def paramify_values(hash_or_array_or_value) case hash_or_array_or_value when Hash Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }] when Array hash_or_array_or_value.map {|i| paramify_values(i)} when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile hash_or_array_or_value else hash_or_array_or_value.to_param end end # Simulate a HTTP request to +action+ by specifying request method, # parameters and set/volley the response. # # - +action+: The controller action to call. # - +http_method+: Request method used to send the http request. Possible values # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. # - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a # string that is appropriately encoded (+application/x-www-form-urlencoded+ # or +multipart/form-data+). # - +session+: A hash of parameters to store in the session. This may be +nil+. # - +flash+: A hash of parameters to store in the flash. This may be +nil+. # # Example calling +create+ action and sending two params: # # process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' } # # Example sending parameters, +nil+ session and setting a flash message: # # process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' } # # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests # prefer using #get, #post, #patch, #put, #delete and #head methods # respectively which will make tests more expressive. # # Note that the request method is not verified. def process(action, http_method = 'GET', *args) check_required_ivars if args.first.is_a?(String) && http_method != 'HEAD' @request.env['RAW_POST_DATA'] = args.shift end parameters, session, flash = args parameters ||= {} # Ensure that numbers and symbols passed as params are converted to # proper params, as is the case when engaging rack. parameters = paramify_values(parameters) if html_format?(parameters) @html_document = nil @html_scanner_document = nil unless @controller.respond_to?(:recycle!) @controller.extend(Testing::Functional) end @request.recycle! @response.recycle! @controller.recycle! @request.env['REQUEST_METHOD'] = http_method controller_class_name = @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) @request.session.update(session) if session @request.flash.update(flash || {}) @controller.request = @request @controller.response = @response build_request_uri(action, parameters) name = @request.parameters[:action] @controller.recycle! @controller.process(name) if cookies = @request.env['action_dispatch.cookies'] unless @response.committed? cookies.write(@response) end end @response.prepare! @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {} if flash_value = @request.flash.to_session_value @request.session['flash'] = flash_value end @response end def setup_controller_request_and_response @controller = nil unless defined? @controller response_klass = TestResponse if klass = self.class.controller_class if klass < ActionController::Live response_klass = LiveTestResponse end unless @controller begin @controller = klass.new rescue warn "could not construct controller #{klass}" if $VERBOSE end end end @request = build_request @response = build_response response_klass @response.request = @request if @controller @controller.request = @request @controller.params = {} end end def build_request TestRequest.new end def build_response(klass) klass.new end included do include ActionController::TemplateAssertions include ActionDispatch::Assertions class_attribute :_controller_class setup :setup_controller_request_and_response end private def document_root_element html_document.root end def check_required_ivars # Sanity check for required instance variables so we can give an # understandable error message. [:@routes, :@controller, :@request, :@response].each do |iv_name| if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil? raise "#{iv_name} is nil: make sure you set it in your test's setup method." end end end def build_request_uri(action, parameters) unless @request.env["PATH_INFO"] options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters options.update( :action => action, :relative_url_root => nil, :_recall => @request.path_parameters) if route_name = options.delete(:use_route) ActiveSupport::Deprecation.warn <<-MSG.squish Passing the `use_route` option in functional tests are deprecated. Support for this option in the `process` method (and the related `get`, `head`, `post`, `patch`, `put` and `delete` helpers) will be removed in the next version without replacement. Functional tests are essentially unit tests for controllers and they should not require knowledge to how the application's routes are configured. Instead, you should explicitly pass the appropiate params to the `process` method. Previously the engines guide also contained an incorrect example that recommended using this option to test an engine's controllers within the dummy application. That recommendation was incorrect and has since been corrected. Instead, you should override the `@routes` variable in the test case with `Foo::Engine.routes`. See the updated engines guide for details. MSG end url, query_string = @routes.path_for(options, route_name).split("?", 2) @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root @request.env["PATH_INFO"] = url @request.env["QUERY_STRING"] = query_string || "" end end def html_format?(parameters) return true unless parameters.key?(:format) Mime.fetch(parameters[:format]) { Mime['html'] }.html? end end include Behavior end end rails-4.2.6/actionpack/lib/action_dispatch.rb000066400000000000000000000060521266740050600212110ustar00rootroot00000000000000#-- # Copyright (c) 2004-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ require 'active_support' require 'active_support/rails' require 'active_support/core_ext/module/attribute_accessors' require 'action_pack' require 'rack' module Rack autoload :Test, 'rack/test' end module ActionDispatch extend ActiveSupport::Autoload class IllegalStateError < StandardError end eager_autoload do autoload_under 'http' do autoload :Request autoload :Response end end autoload_under 'middleware' do autoload :RequestId autoload :Callbacks autoload :Cookies autoload :DebugExceptions autoload :ExceptionWrapper autoload :Flash autoload :ParamsParser autoload :PublicExceptions autoload :Reloader autoload :RemoteIp autoload :ShowExceptions autoload :SSL autoload :Static end autoload :Journey autoload :MiddlewareStack, 'action_dispatch/middleware/stack' autoload :Routing module Http extend ActiveSupport::Autoload autoload :Cache autoload :Headers autoload :MimeNegotiation autoload :Parameters autoload :ParameterFilter autoload :Upload autoload :UploadedFile, 'action_dispatch/http/upload' autoload :URL end module Session autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' end mattr_accessor :test_app autoload_under 'testing' do autoload :Assertions autoload :Integration autoload :IntegrationTest, 'action_dispatch/testing/integration' autoload :TestProcess autoload :TestRequest autoload :TestResponse end end autoload :Mime, 'action_dispatch/http/mime_type' ActiveSupport.on_load(:action_view) do ActionView::Base.default_formats ||= Mime::SET.symbols ActionView::Template::Types.delegate_to Mime end rails-4.2.6/actionpack/lib/action_dispatch/000077500000000000000000000000001266740050600206615ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/http/000077500000000000000000000000001266740050600216405ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/http/cache.rb000066400000000000000000000116311266740050600232320ustar00rootroot00000000000000 module ActionDispatch module Http module Cache module Request HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze def if_modified_since if since = env[HTTP_IF_MODIFIED_SINCE] Time.rfc2822(since) rescue nil end end def if_none_match env[HTTP_IF_NONE_MATCH] end def if_none_match_etags (if_none_match ? if_none_match.split(/\s*,\s*/) : []).collect do |etag| etag.gsub(/^\"|\"$/, "") end end def not_modified?(modified_at) if_modified_since && modified_at && if_modified_since >= modified_at end def etag_matches?(etag) if etag etag = etag.gsub(/^\"|\"$/, "") if_none_match_etags.include?(etag) end end # Check response freshness (Last-Modified and ETag) against request # If-Modified-Since and If-None-Match conditions. If both headers are # supplied, both must match, or the request is not considered fresh. def fresh?(response) last_modified = if_modified_since etag = if_none_match return false unless last_modified || etag success = true success &&= not_modified?(response.last_modified) if last_modified success &&= etag_matches?(response.etag) if etag success end end module Response attr_reader :cache_control, :etag alias :etag? :etag def last_modified if last = headers[LAST_MODIFIED] Time.httpdate(last) end end def last_modified? headers.include?(LAST_MODIFIED) end def last_modified=(utc_time) headers[LAST_MODIFIED] = utc_time.httpdate end def date if date_header = headers[DATE] Time.httpdate(date_header) end end def date? headers.include?(DATE) end def date=(utc_time) headers[DATE] = utc_time.httpdate end def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}") end private DATE = 'Date'.freeze LAST_MODIFIED = "Last-Modified".freeze ETAG = "ETag".freeze CACHE_CONTROL = "Cache-Control".freeze SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate]) def cache_control_segments if cache_control = self[CACHE_CONTROL] cache_control.delete(' ').split(',') else [] end end def cache_control_headers cache_control = {} cache_control_segments.each do |segment| directive, argument = segment.split('=', 2) if SPECIAL_KEYS.include? directive key = directive.tr('-', '_') cache_control[key.to_sym] = argument || true else cache_control[:extras] ||= [] cache_control[:extras] << segment end end cache_control end def prepare_cache_control! @cache_control = cache_control_headers @etag = self[ETAG] end def handle_conditional_get! if etag? || last_modified? || !@cache_control.empty? set_conditional_cache_control! end end DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze NO_CACHE = "no-cache".freeze PUBLIC = "public".freeze PRIVATE = "private".freeze MUST_REVALIDATE = "must-revalidate".freeze def set_conditional_cache_control! control = {} cc_headers = cache_control_headers if extras = cc_headers.delete(:extras) @cache_control[:extras] ||= [] @cache_control[:extras] += extras @cache_control[:extras].uniq! end control.merge! cc_headers control.merge! @cache_control if control.empty? headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL elsif control[:no_cache] headers[CACHE_CONTROL] = NO_CACHE if control[:extras] headers[CACHE_CONTROL] += ", #{control[:extras].join(', ')}" end else extras = control[:extras] max_age = control[:max_age] options = [] options << "max-age=#{max_age.to_i}" if max_age options << (control[:public] ? PUBLIC : PRIVATE) options << MUST_REVALIDATE if control[:must_revalidate] options.concat(extras) if extras headers[CACHE_CONTROL] = options.join(", ") end end end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/filter_parameters.rb000066400000000000000000000051521266740050600257000ustar00rootroot00000000000000require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/object/duplicable' require 'action_dispatch/http/parameter_filter' module ActionDispatch module Http # Allows you to specify sensitive parameters which will be replaced from # the request log by looking in the query string of the request and all # sub-hashes of the params hash to filter. If a block is given, each key and # value of the params hash and all sub-hashes is passed to it, the value # or key can be replaced using String#replace or similar method. # # env["action_dispatch.parameter_filter"] = [:password] # => replaces the value to all keys matching /password/i with "[FILTERED]" # # env["action_dispatch.parameter_filter"] = [:foo, "bar"] # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" # # env["action_dispatch.parameter_filter"] = lambda do |k,v| # v.reverse! if k =~ /secret/i # end # => reverses the value to all keys matching /secret/i module FilterParameters ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc: NULL_PARAM_FILTER = ParameterFilter.new # :nodoc: NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc: def initialize(env) super @filtered_parameters = nil @filtered_env = nil @filtered_path = nil end # Return a hash of parameters with all sensitive data replaced. def filtered_parameters @filtered_parameters ||= parameter_filter.filter(parameters) end # Return a hash of request.env with all sensitive data replaced. def filtered_env @filtered_env ||= env_filter.filter(@env) end # Reconstructed a path with all sensitive GET parameters replaced. def filtered_path @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}" end protected def parameter_filter parameter_filter_for @env.fetch("action_dispatch.parameter_filter") { return NULL_PARAM_FILTER } end def env_filter user_key = @env.fetch("action_dispatch.parameter_filter") { return NULL_ENV_FILTER } parameter_filter_for(Array(user_key) + ENV_MATCH) end def parameter_filter_for(filters) ParameterFilter.new(filters) end KV_RE = '[^&;=]+' PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})} def filtered_query_string query_string.gsub(PAIR_RE) do |_| parameter_filter.filter([[$1, $2]]).first.join("=") end end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/filter_redirect.rb000066400000000000000000000013531266740050600253350ustar00rootroot00000000000000module ActionDispatch module Http module FilterRedirect FILTERED = '[FILTERED]'.freeze # :nodoc: def filtered_location filters = location_filter if !filters.empty? && location_filter_match?(filters) FILTERED else location end end private def location_filter if request request.env['action_dispatch.redirect_filter'] || [] else [] end end def location_filter_match?(filters) filters.any? do |filter| if String === filter location.include?(filter) elsif Regexp === filter location.match(filter) end end end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/headers.rb000066400000000000000000000051021266740050600235760ustar00rootroot00000000000000module ActionDispatch module Http # Provides access to the request's HTTP headers from the environment. # # env = { "CONTENT_TYPE" => "text/plain" } # headers = ActionDispatch::Http::Headers.new(env) # headers["Content-Type"] # => "text/plain" class Headers CGI_VARIABLES = Set.new(%W[ AUTH_TYPE CONTENT_LENGTH CONTENT_TYPE GATEWAY_INTERFACE HTTPS PATH_INFO PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE ]).freeze HTTP_HEADER = /\A[A-Za-z0-9-]+\z/ include Enumerable attr_reader :env def initialize(env = {}) # :nodoc: @env = env end # Returns the value for the given key mapped to @env. def [](key) @env[env_name(key)] end # Sets the given value for the key mapped to @env. def []=(key, value) @env[env_name(key)] = value end def key?(key) @env.key? env_name(key) end alias :include? :key? # Returns the value for the given key mapped to @env. # # If the key is not found and an optional code block is not provided, # raises a KeyError exception. # # If the code block is provided, then it will be run and # its result returned. def fetch(key, *args, &block) @env.fetch env_name(key), *args, &block end def each(&block) @env.each(&block) end # Returns a new Http::Headers instance containing the contents of # headers_or_env and the original instance. def merge(headers_or_env) headers = Http::Headers.new(env.dup) headers.merge!(headers_or_env) headers end # Adds the contents of headers_or_env to original instance # entries; duplicate keys are overwritten with the values from # headers_or_env. def merge!(headers_or_env) headers_or_env.each do |key, value| self[env_name(key)] = value end end private # Converts a HTTP header name to an environment variable name if it is # not contained within the headers hash. def env_name(key) key = key.to_s if key =~ HTTP_HEADER key = key.upcase.tr('-', '_') key = "HTTP_" + key unless CGI_VARIABLES.include?(key) end key end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/mime_negotiation.rb000066400000000000000000000120371266740050600255170ustar00rootroot00000000000000require 'active_support/core_ext/module/attribute_accessors' module ActionDispatch module Http module MimeNegotiation extend ActiveSupport::Concern included do mattr_accessor :ignore_accept_header self.ignore_accept_header = false end attr_reader :variant # The MIME type of the HTTP request, such as Mime::XML. # # For backward compatibility, the post \format is extracted from the # X-Post-Data-Format HTTP header if present. def content_mime_type @env["action_dispatch.request.content_type"] ||= begin if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ Mime::Type.lookup($1.strip.downcase) else nil end end end def content_type content_mime_type && content_mime_type.to_s end # Returns the accepted MIME type for the request. def accepts @env["action_dispatch.request.accepts"] ||= begin header = @env['HTTP_ACCEPT'].to_s.strip if header.empty? [content_mime_type] else Mime::Type.parse(header) end end end # Returns the MIME type for the \format used in the request. # # GET /posts/5.xml | request.format => Mime::XML # GET /posts/5.xhtml | request.format => Mime::HTML # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first # def format(view_path = []) formats.first || Mime::NullType.instance end def formats @env["action_dispatch.request.formats"] ||= begin params_readable = begin parameters[:format] rescue ActionController::BadRequest false end if params_readable Array(Mime[parameters[:format]]) elsif use_accept_header && valid_accept_header accepts elsif xhr? [Mime::JS] else [Mime::HTML] end end end # Sets the \variant for template. def variant=(variant) if variant.is_a?(Symbol) @variant = [variant] elsif variant.nil? || variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) } @variant = variant else raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \ "For security reasons, never directly set the variant to a user-provided value, " \ "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \ "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'" end end # Sets the \format by string extension, which can be used to force custom formats # that are not controlled by the extension. # # class ApplicationController < ActionController::Base # before_action :adjust_format_for_iphone # # private # def adjust_format_for_iphone # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] # end # end def format=(extension) parameters[:format] = extension.to_s @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] end # Sets the \formats by string extensions. This differs from #format= by allowing you # to set multiple, ordered formats, which is useful when you want to have a fallback. # # In this example, the :iphone format will be used if it's available, otherwise it'll fallback # to the :html format. # # class ApplicationController < ActionController::Base # before_action :adjust_format_for_iphone_with_html_fallback # # private # def adjust_format_for_iphone_with_html_fallback # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/] # end # end def formats=(extensions) parameters[:format] = extensions.first.to_s @env["action_dispatch.request.formats"] = extensions.collect do |extension| Mime::Type.lookup_by_extension(extension) end end # Receives an array of mimes and return the first user sent mime that # matches the order array. # def negotiate_mime(order) formats.each do |priority| if priority == Mime::ALL return order.first elsif order.include?(priority) return priority end end order.include?(Mime::ALL) ? format : nil end protected BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ def valid_accept_header (xhr? && (accept.present? || content_mime_type)) || (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) end def use_accept_header !self.class.ignore_accept_header end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/mime_type.rb000066400000000000000000000207411266740050600241610ustar00rootroot00000000000000require 'set' require 'singleton' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/starts_ends_with' module Mime class Mimes < Array def symbols @symbols ||= map { |m| m.to_sym } end %w(<< concat shift unshift push pop []= clear compact! collect! delete delete_at delete_if flatten! map! insert reject! reverse! replace slice! sort! uniq!).each do |method| module_eval <<-CODE, __FILE__, __LINE__ + 1 def #{method}(*) @symbols = nil super end CODE end end SET = Mimes.new EXTENSION_LOOKUP = {} LOOKUP = {} class << self def [](type) return type if type.is_a?(Type) Type.lookup_by_extension(type) end def fetch(type) return type if type.is_a?(Type) EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } end end # Encapsulates the notion of a mime type. Can be used at render time, for example, with: # # class PostsController < ActionController::Base # def show # @post = Post.find(params[:id]) # # respond_to do |format| # format.html # format.ics { render text: @post.to_ics, mime_type: Mime::Type["text/calendar"] } # format.xml { render xml: @post } # end # end # end class Type @@html_types = Set.new [:html, :all] cattr_reader :html_types attr_reader :symbol @register_callbacks = [] # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: attr_accessor :index, :name, :q alias :to_s :name def initialize(index, name, q = nil) @index = index @name = name q ||= 0.0 if @name == Mime::ALL.to_s # default wildcard match to end of list @q = ((q || 1.0).to_f * 100).to_i end def <=>(item) result = item.q <=> @q result = @index <=> item.index if result == 0 result end def ==(item) @name == item.to_s end end class AcceptList < Array #:nodoc: def assort! sort! # Take care of the broken text/xml entry by renaming or deleting it if text_xml_idx && app_xml_idx app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two exchange_xml_items if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list delete_at(text_xml_idx) # delete text_xml from the list elsif text_xml_idx text_xml.name = Mime::XML.to_s end # Look for more specific XML-based types and sort them ahead of app/xml if app_xml_idx idx = app_xml_idx while idx < length type = self[idx] break if type.q < app_xml.q if type.name.ends_with? '+xml' self[app_xml_idx], self[idx] = self[idx], app_xml @app_xml_idx = idx end idx += 1 end end map! { |i| Mime::Type.lookup(i.name) }.uniq! to_a end private def text_xml_idx @text_xml_idx ||= index('text/xml') end def app_xml_idx @app_xml_idx ||= index(Mime::XML.to_s) end def text_xml self[text_xml_idx] end def app_xml self[app_xml_idx] end def exchange_xml_items self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml @app_xml_idx, @text_xml_idx = text_xml_idx, app_xml_idx end end class << self TRAILING_STAR_REGEXP = /(text|application)\/\*/ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/ def register_callback(&block) @register_callbacks << block end def lookup(string) LOOKUP[string] || Type.new(string) end def lookup_by_extension(extension) EXTENSION_LOOKUP[extension.to_s] end # Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for # rendering different HTML versions depending on the user agent, like an iPhone. def register_alias(string, symbol, extension_synonyms = []) register(string, symbol, [], extension_synonyms, true) end def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false) Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms)) new_mime = Mime.const_get(symbol.upcase) SET << new_mime ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last } @register_callbacks.each do |callback| callback.call(new_mime) end end def parse(accept_header) if !accept_header.include?(',') accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact else list, index = AcceptList.new, 0 accept_header.split(',').each do |header| params, q = header.split(PARAMETER_SEPARATOR_REGEXP) if params.present? params.strip! params = parse_trailing_star(params) || [params] params.each do |m| list << AcceptItem.new(index, m.to_s, q) index += 1 end end end list.assort! end end def parse_trailing_star(accept_header) parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP end # For an input of 'text', returns [Mime::JSON, Mime::XML, Mime::ICS, # Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]. # # For an input of 'application', returns [Mime::HTML, Mime::JS, # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]. def parse_data_with_trailing_star(input) Mime::SET.select { |m| m =~ input } end # This method is opposite of register method. # # Usage: # # Mime::Type.unregister(:mobile) def unregister(symbol) symbol = symbol.upcase mime = Mime.const_get(symbol) Mime.instance_eval { remove_const(symbol) } SET.delete_if { |v| v.eql?(mime) } LOOKUP.delete_if { |_,v| v.eql?(mime) } EXTENSION_LOOKUP.delete_if { |_,v| v.eql?(mime) } end end attr_reader :hash def initialize(string, symbol = nil, synonyms = []) @symbol, @synonyms = symbol, synonyms @string = string @hash = [@string, @synonyms, @symbol].hash end def to_s @string end def to_str to_s end def to_sym @symbol end def ref to_sym || to_s end def ===(list) if list.is_a?(Array) (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) } else super end end def ==(mime_type) return false if mime_type.blank? (@synonyms + [ self ]).any? do |synonym| synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym end end def eql?(other) super || (self.class == other.class && @string == other.string && @synonyms == other.synonyms && @symbol == other.symbol) end def =~(mime_type) return false if mime_type.blank? regexp = Regexp.new(Regexp.quote(mime_type.to_s)) (@synonyms + [ self ]).any? do |synonym| synonym.to_s =~ regexp end end def html? @@html_types.include?(to_sym) || @string =~ /html/ end protected attr_reader :string, :synonyms private def to_ary; end def to_a; end def method_missing(method, *args) if method.to_s.ends_with? '?' method[0..-2].downcase.to_sym == to_sym else super end end def respond_to_missing?(method, include_private = false) #:nodoc: method.to_s.ends_with? '?' end end class NullType include Singleton def nil? true end def ref; end def respond_to_missing?(method, include_private = false) method.to_s.ends_with? '?' end private def method_missing(method, *args) false if method.to_s.ends_with? '?' end end end require 'action_dispatch/http/mime_types' rails-4.2.6/actionpack/lib/action_dispatch/http/mime_types.rb000066400000000000000000000030511266740050600243370ustar00rootroot00000000000000# Build list of Mime types for HTTP responses # http://www.iana.org/assignments/media-types/ Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml ) Mime::Type.register "text/plain", :text, [], %w(txt) Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript ) Mime::Type.register "text/css", :css Mime::Type.register "text/calendar", :ics Mime::Type.register "text/csv", :csv Mime::Type.register "text/vcard", :vcf Mime::Type.register "image/png", :png, [], %w(png) Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg) Mime::Type.register "image/gif", :gif, [], %w(gif) Mime::Type.register "image/bmp", :bmp, [], %w(bmp) Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml ) Mime::Type.register "application/rss+xml", :rss Mime::Type.register "application/atom+xml", :atom Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ) Mime::Type.register "multipart/form-data", :multipart_form Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form # http://www.ietf.org/rfc/rfc4627.txt # http://www.json.org/JSONRequest.html Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) Mime::Type.register "application/pdf", :pdf, [], %w(pdf) Mime::Type.register "application/zip", :zip, [], %w(zip) # Create Mime::ALL but do not add it to the SET. Mime::ALL = Mime::Type.new("*/*", :all, []) rails-4.2.6/actionpack/lib/action_dispatch/http/parameter_filter.rb000066400000000000000000000033331266740050600255140ustar00rootroot00000000000000module ActionDispatch module Http class ParameterFilter FILTERED = '[FILTERED]'.freeze # :nodoc: def initialize(filters = []) @filters = filters end def filter(params) compiled_filter.call(params) end private def compiled_filter @compiled_filter ||= CompiledFilter.compile(@filters) end class CompiledFilter # :nodoc: def self.compile(filters) return lambda { |params| params.dup } if filters.empty? strings, regexps, blocks = [], [], [] filters.each do |item| case item when Proc blocks << item when Regexp regexps << item else strings << item.to_s end end regexps << Regexp.new(strings.join('|'), true) unless strings.empty? new regexps, blocks end attr_reader :regexps, :blocks def initialize(regexps, blocks) @regexps = regexps @blocks = blocks end def call(original_params) filtered_params = {} original_params.each do |key, value| if regexps.any? { |r| key =~ r } value = FILTERED elsif value.is_a?(Hash) value = call(value) elsif value.is_a?(Array) value = value.map { |v| v.is_a?(Hash) ? call(v) : v } elsif blocks.any? key = key.dup if key.duplicable? value = value.dup if value.duplicable? blocks.each { |b| b.call(key, value) } end filtered_params[key] = value end filtered_params end end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/parameters.rb000066400000000000000000000036171266740050600243370ustar00rootroot00000000000000require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/deprecation' module ActionDispatch module Http module Parameters PARAMETERS_KEY = 'action_dispatch.request.path_parameters' # Returns both GET and POST \parameters in a single hash. def parameters @env["action_dispatch.request.parameters"] ||= begin params = begin request_parameters.merge(query_parameters) rescue EOFError query_parameters.dup end params.merge!(path_parameters) end end alias :params :parameters def path_parameters=(parameters) #:nodoc: @env.delete('action_dispatch.request.parameters') @env[PARAMETERS_KEY] = parameters end def symbolized_path_parameters ActiveSupport::Deprecation.warn( '`symbolized_path_parameters` is deprecated. Please use `path_parameters`.' ) path_parameters end # Returns a hash with the \parameters used to form the \path of the request. # Returned hash keys are strings: # # {'action' => 'my_action', 'controller' => 'my_controller'} def path_parameters @env[PARAMETERS_KEY] ||= {} end private # Convert nested Hash to HashWithIndifferentAccess. # def normalize_encode_params(params) case params when Hash if params.has_key?(:tempfile) UploadedFile.new(params) else params.each_with_object({}) do |(key, val), new_hash| new_hash[key] = if val.is_a?(Array) val.map! { |el| normalize_encode_params(el) } else normalize_encode_params(val) end end.with_indifferent_access end else params end end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/rack_cache.rb000066400000000000000000000020441266740050600242300ustar00rootroot00000000000000require "rack/cache" require "rack/cache/context" require "active_support/cache" module ActionDispatch class RailsMetaStore < Rack::Cache::MetaStore def self.resolve(uri) new end def initialize(store = Rails.cache) @store = store end def read(key) if data = @store.read(key) Marshal.load(data) else [] end end def write(key, value) @store.write(key, Marshal.dump(value)) end ::Rack::Cache::MetaStore::RAILS = self end class RailsEntityStore < Rack::Cache::EntityStore def self.resolve(uri) new end def initialize(store = Rails.cache) @store = store end def exist?(key) @store.exist?(key) end def open(key) @store.read(key) end def read(key) body = open(key) body.join if body end def write(body) buf = [] key, size = slurp(body) { |part| buf << part } @store.write(key, buf) [key, size] end ::Rack::Cache::EntityStore::RAILS = self end end rails-4.2.6/actionpack/lib/action_dispatch/http/request.rb000066400000000000000000000270531266740050600236640ustar00rootroot00000000000000require 'stringio' require 'active_support/inflector' require 'action_dispatch/http/headers' require 'action_controller/metal/exceptions' require 'rack/request' require 'action_dispatch/http/cache' require 'action_dispatch/http/mime_negotiation' require 'action_dispatch/http/parameters' require 'action_dispatch/http/filter_parameters' require 'action_dispatch/http/upload' require 'action_dispatch/http/url' require 'active_support/core_ext/array/conversions' module ActionDispatch class Request < Rack::Request include ActionDispatch::Http::Cache::Request include ActionDispatch::Http::MimeNegotiation include ActionDispatch::Http::Parameters include ActionDispatch::Http::FilterParameters include ActionDispatch::Http::URL autoload :Session, 'action_dispatch/request/session' autoload :Utils, 'action_dispatch/request/utils' LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/] ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR SERVER_NAME SERVER_PROTOCOL HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_NEGOTIATE HTTP_PRAGMA ].freeze ENV_METHODS.each do |env| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset @env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"] end # end METHOD end def initialize(env) super @method = nil @request_method = nil @remote_ip = nil @original_fullpath = nil @fullpath = nil @ip = nil @uuid = nil end def check_path_parameters! # If any of the path parameters has an invalid encoding then # raise since it's likely to trigger errors further on. path_parameters.each do |key, value| next unless value.respond_to?(:valid_encoding?) unless value.valid_encoding? raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" end end end def key?(key) @env.key?(key) end # List of HTTP request methods from the following RFCs: # Hypertext Transfer Protocol -- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt) # HTTP Extensions for Distributed Authoring -- WEBDAV (http://www.ietf.org/rfc/rfc2518.txt) # Versioning Extensions to WebDAV (http://www.ietf.org/rfc/rfc3253.txt) # Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt) # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt) # Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt) # Calendar Extensions to WebDAV (http://www.ietf.org/rfc/rfc4791.txt) # PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt) RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT) RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK) RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY) RFC3648 = %w(ORDERPATCH) RFC3744 = %w(ACL) RFC5323 = %w(SEARCH) RFC4791 = %w(MKCALENDAR) RFC5789 = %w(PATCH) HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789 HTTP_METHOD_LOOKUP = {} # Populate the HTTP method lookup cache HTTP_METHODS.each { |method| HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym } # Returns the HTTP \method that the application should see. # In the case where the \method was overridden by a middleware # (for instance, if a HEAD request was converted to a GET, # or if a _method parameter was used to determine the \method # the application should use), this \method returns the overridden # value, not the original. def request_method @request_method ||= check_method(env["REQUEST_METHOD"]) end def request_method=(request_method) #:nodoc: if check_method(request_method) @request_method = env["REQUEST_METHOD"] = request_method end end # Returns a symbol form of the #request_method def request_method_symbol HTTP_METHOD_LOOKUP[request_method] end # Returns the original value of the environment's REQUEST_METHOD, # even if it was overridden by middleware. See #request_method for # more information. def method @method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']) end # Returns a symbol form of the #method def method_symbol HTTP_METHOD_LOOKUP[method] end # Is this a GET (or HEAD) request? # Equivalent to request.request_method_symbol == :get. def get? HTTP_METHOD_LOOKUP[request_method] == :get end # Is this a POST request? # Equivalent to request.request_method_symbol == :post. def post? HTTP_METHOD_LOOKUP[request_method] == :post end # Is this a PATCH request? # Equivalent to request.request_method == :patch. def patch? HTTP_METHOD_LOOKUP[request_method] == :patch end # Is this a PUT request? # Equivalent to request.request_method_symbol == :put. def put? HTTP_METHOD_LOOKUP[request_method] == :put end # Is this a DELETE request? # Equivalent to request.request_method_symbol == :delete. def delete? HTTP_METHOD_LOOKUP[request_method] == :delete end # Is this a HEAD request? # Equivalent to request.request_method_symbol == :head. def head? HTTP_METHOD_LOOKUP[request_method] == :head end # Provides access to the request's HTTP headers, for example: # # request.headers["Content-Type"] # => "text/plain" def headers Http::Headers.new(@env) end # Returns a +String+ with the last requested path including their params. # # # get '/foo' # request.original_fullpath # => '/foo' # # # get '/foo?bar' # request.original_fullpath # => '/foo?bar' def original_fullpath @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath) end # Returns the +String+ full path including params of the last URL requested. # # # get "/articles" # request.fullpath # => "/articles" # # # get "/articles?page=2" # request.fullpath # => "/articles?page=2" def fullpath @fullpath ||= super end # Returns the original request URL as a +String+. # # # get "/articles?page=2" # request.original_url # => "http://www.example.com/articles?page=2" def original_url base_url + original_fullpath end # The +String+ MIME type of the request. # # # get "/articles" # request.media_type # => "application/x-www-form-urlencoded" def media_type content_mime_type.to_s end # Returns the content length of the request as an integer. def content_length super.to_i end # Returns true if the "X-Requested-With" header contains "XMLHttpRequest" # (case-insensitive), which may need to be manually added depending on the # choice of JavaScript libraries and frameworks. def xml_http_request? @env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i end alias :xhr? :xml_http_request? def ip @ip ||= super end # Originating IP address, usually set by the RemoteIp middleware. def remote_ip @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s end # Returns the unique request id, which is based on either the X-Request-Id header that can # be generated by a firewall, load balancer, or web server or by the RequestId middleware # (which sets the action_dispatch.request_id environment variable). # # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. # This relies on the rack variable set by the ActionDispatch::RequestId middleware. def uuid @uuid ||= env["action_dispatch.request_id"] end # Returns the lowercase name of the HTTP server software. def server_software (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil end # Read the request \body. This is useful for web services that need to # work with raw requests directly. def raw_post unless @env.include? 'RAW_POST_DATA' raw_post_body = body @env['RAW_POST_DATA'] = raw_post_body.read(content_length) raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end @env['RAW_POST_DATA'] end # The request body is an IO input stream. If the RAW_POST_DATA environment # variable is already set, wrap it in a StringIO. def body if raw_post = @env['RAW_POST_DATA'] raw_post.force_encoding(Encoding::BINARY) StringIO.new(raw_post) else @env['rack.input'] end end def form_data? FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s) end def body_stream #:nodoc: @env['rack.input'] end # TODO This should be broken apart into AD::Request::Session and probably # be included by the session middleware. def reset_session if session && session.respond_to?(:destroy) session.destroy else self.session = {} end @env['action_dispatch.request.flash_hash'] = nil end def session=(session) #:nodoc: Session.set @env, session end def session_options=(options) Session::Options.set @env, options end # Override Rack's GET method to support indifferent access def GET @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {})) rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:query, e) end alias :query_parameters :GET # Override Rack's POST method to support indifferent access def POST @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {})) rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:request, e) end alias :request_parameters :POST # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. def authorization @env['HTTP_AUTHORIZATION'] || @env['X-HTTP_AUTHORIZATION'] || @env['X_HTTP_AUTHORIZATION'] || @env['REDIRECT_X_HTTP_AUTHORIZATION'] end # True if the request came from localhost, 127.0.0.1. def local? LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end # Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility. def deep_munge(hash) ActiveSupport::Deprecation.warn( 'This method has been extracted into `ActionDispatch::Request::Utils.deep_munge`. Please start using that instead.' ) Utils.deep_munge(hash) end protected def parse_query(qs) Utils.deep_munge(super) end private def check_method(name) HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}") name end end end rails-4.2.6/actionpack/lib/action_dispatch/http/response.rb000066400000000000000000000246461266740050600240370ustar00rootroot00000000000000require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/filters' require 'active_support/deprecation' require 'action_dispatch/http/filter_redirect' require 'monitor' module ActionDispatch # :nodoc: # Represents an HTTP response generated by a controller action. Use it to # retrieve the current state of the response, or customize the response. It can # either represent a real HTTP response (i.e. one that is meant to be sent # back to the web browser) or a TestResponse (i.e. one that is generated # from integration tests). # # \Response is mostly a Ruby on \Rails framework implementation detail, and # should never be used directly in controllers. Controllers should use the # methods defined in ActionController::Base instead. For example, if you want # to set the HTTP response's content MIME type, then use # ActionControllerBase#headers instead of Response#headers. # # Nevertheless, integration tests may want to inspect controller responses in # more detail, and that's when \Response can be useful for application # developers. Integration test methods such as # ActionDispatch::Integration::Session#get and # ActionDispatch::Integration::Session#post return objects of type # TestResponse (which are of course also of type \Response). # # For example, the following demo integration test prints the body of the # controller response to the console: # # class DemoControllerTest < ActionDispatch::IntegrationTest # def test_print_root_path_to_console # get('/') # puts response.body # end # end class Response # The request that the response is responding to. attr_accessor :request # The HTTP status code. attr_reader :status attr_writer :sending_file # Get and set headers for this response. attr_accessor :header alias_method :headers=, :header= alias_method :headers, :header delegate :[], :[]=, :to => :@header delegate :each, :to => :@stream # Sets the HTTP response's content MIME type. For example, in the controller # you could write this: # # response.content_type = "text/plain" # # If a character set has been defined for this response (see charset=) then # the character set information will also be included in the content type # information. attr_reader :content_type # The charset of the response. HTML wants to know the encoding of the # content you're giving them, so we need to send that along. attr_accessor :charset CONTENT_TYPE = "Content-Type".freeze SET_COOKIE = "Set-Cookie".freeze LOCATION = "Location".freeze NO_CONTENT_CODES = [204, 304] cattr_accessor(:default_charset) { "utf-8" } cattr_accessor(:default_headers) include Rack::Response::Helpers include ActionDispatch::Http::FilterRedirect include ActionDispatch::Http::Cache::Response include MonitorMixin class Buffer # :nodoc: def initialize(response, buf) @response = response @buf = buf @closed = false end def write(string) raise IOError, "closed stream" if closed? @response.commit! @buf.push string end def each(&block) @response.sending! x = @buf.each(&block) @response.sent! x end def abort end def close @response.commit! @closed = true end def closed? @closed end end # The underlying body, as a streamable object. attr_reader :stream def initialize(status = 200, header = {}, body = [], options = {}) super() default_headers = options.fetch(:default_headers, self.class.default_headers) header = merge_default_headers(header, default_headers) self.body, self.header, self.status = body, header, status @sending_file = false @blank = false @cv = new_cond @committed = false @sending = false @sent = false @content_type = nil @charset = nil if content_type = self[CONTENT_TYPE] type, charset = content_type.split(/;\s*charset=/) @content_type = Mime::Type.lookup(type) @charset = charset || self.class.default_charset end prepare_cache_control! yield self if block_given? end def await_commit synchronize do @cv.wait_until { @committed } end end def await_sent synchronize { @cv.wait_until { @sent } } end def commit! synchronize do before_committed @committed = true @cv.broadcast end end def sending! synchronize do before_sending @sending = true @cv.broadcast end end def sent! synchronize do @sent = true @cv.broadcast end end def sending?; synchronize { @sending }; end def committed?; synchronize { @committed }; end def sent?; synchronize { @sent }; end # Sets the HTTP status code. def status=(status) @status = Rack::Utils.status_code(status) end # Sets the HTTP content type. def content_type=(content_type) @content_type = content_type.to_s end # The response code of the request. def response_code @status end # Returns a string to ensure compatibility with Net::HTTPResponse. def code @status.to_s end # Returns the corresponding message for the current HTTP status code: # # response.status = 200 # response.message # => "OK" # # response.status = 404 # response.message # => "Not Found" # def message Rack::Utils::HTTP_STATUS_CODES[@status] end alias_method :status_message, :message # Returns the content of the response as a string. This contains the contents # of any calls to render. def body strings = [] each { |part| strings << part.to_s } strings.join end EMPTY = " " # Allows you to manually set or override the response body. def body=(body) @blank = true if body == EMPTY if body.respond_to?(:to_path) @stream = body else synchronize do @stream = build_buffer self, munge_body_object(body) end end end def body_parts parts = [] @stream.each { |x| parts << x } parts end def set_cookie(key, value) ::Rack::Utils.set_cookie_header!(header, key, value) end def delete_cookie(key, value={}) ::Rack::Utils.delete_cookie_header!(header, key, value) end # The location header we'll be responding with. def location headers[LOCATION] end alias_method :redirect_url, :location # Sets the location header we'll be responding with. def location=(url) headers[LOCATION] = url end def close stream.close if stream.respond_to?(:close) end def abort if stream.respond_to?(:abort) stream.abort elsif stream.respond_to?(:close) # `stream.close` should really be reserved for a close from the # other direction, but we must fall back to it for # compatibility. stream.close end end # Turns the Response into a Rack-compatible array of the status, headers, # and body. Allows explict splatting: # # status, headers, body = *response def to_a rack_response @status, @header.to_hash end alias prepare! to_a # Be super clear that a response object is not an Array. Defining this # would make implicit splatting work, but it also makes adding responses # as arrays work, and "flattening" responses, cascading to the rack body! # Not sensible behavior. def to_ary ActiveSupport::Deprecation.warn(<<-MSG.squish) `ActionDispatch::Response#to_ary` no longer performs implicit conversion to an array. Please use `response.to_a` instead, or a splat like `status, headers, body = *response`. MSG to_a end # Returns the response cookies, converted to a Hash of (name => value) pairs # # assert_equal 'AuthorOfNewPage', r.cookies['author'] def cookies cookies = {} if header = self[SET_COOKIE] header = header.split("\n") if header.respond_to?(:to_str) header.each do |cookie| if pair = cookie.split(';').first key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) } cookies[key] = value end end end cookies end private def before_committed end def before_sending end def merge_default_headers(original, default) default.respond_to?(:merge) ? default.merge(original) : original end def build_buffer(response, body) Buffer.new response, body end def munge_body_object(body) body.respond_to?(:each) ? body : [body] end def assign_default_content_type_and_charset!(headers) return if headers[CONTENT_TYPE].present? @content_type ||= Mime::HTML @charset ||= self.class.default_charset unless @charset == false type = @content_type.to_s.dup type << "; charset=#{@charset}" if append_charset? headers[CONTENT_TYPE] = type end def append_charset? !@sending_file && @charset != false end class RackBody def initialize(response) @response = response end def each(*args, &block) @response.each(*args, &block) end def close # Rack "close" maps to Response#abort, and *not* Response#close # (which is used when the controller's finished writing) @response.abort end def body @response.body end def respond_to?(method, include_private = false) if method.to_s == 'to_path' @response.stream.respond_to?(method) else super end end def to_path @response.stream.to_path end def to_ary nil end end def rack_response(status, header) assign_default_content_type_and_charset!(header) handle_conditional_get! header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join) if NO_CONTENT_CODES.include?(@status) header.delete CONTENT_TYPE [status, header, []] else [status, header, RackBody.new(self)] end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/upload.rb000066400000000000000000000040721266740050600234540ustar00rootroot00000000000000module ActionDispatch module Http # Models uploaded files. # # The actual file is accessible via the +tempfile+ accessor, though some # of its interface is available directly for convenience. # # Uploaded files are temporary files whose lifespan is one request. When # the object is finalized Ruby unlinks the file, so there is no need to # clean them with a separate maintenance task. class UploadedFile # The basename of the file in the client. attr_accessor :original_filename # A string with the MIME type of the file. attr_accessor :content_type # A +Tempfile+ object with the actual uploaded file. Note that some of # its interface is available directly. attr_accessor :tempfile alias :to_io :tempfile # A string with the headers of the multipart request. attr_accessor :headers def initialize(hash) # :nodoc: @tempfile = hash[:tempfile] raise(ArgumentError, ':tempfile is required') unless @tempfile @original_filename = hash[:filename] if @original_filename begin @original_filename.encode!(Encoding::UTF_8) rescue EncodingError @original_filename.force_encoding(Encoding::UTF_8) end end @content_type = hash[:type] @headers = hash[:head] end # Shortcut for +tempfile.read+. def read(length=nil, buffer=nil) @tempfile.read(length, buffer) end # Shortcut for +tempfile.open+. def open @tempfile.open end # Shortcut for +tempfile.close+. def close(unlink_now=false) @tempfile.close(unlink_now) end # Shortcut for +tempfile.path+. def path @tempfile.path end # Shortcut for +tempfile.rewind+. def rewind @tempfile.rewind end # Shortcut for +tempfile.size+. def size @tempfile.size end # Shortcut for +tempfile.eof?+. def eof? @tempfile.eof? end end end end rails-4.2.6/actionpack/lib/action_dispatch/http/url.rb000066400000000000000000000200121266740050600227620ustar00rootroot00000000000000require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/hash/slice' module ActionDispatch module Http module URL IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/ PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/ mattr_accessor :tld_length self.tld_length = 1 class << self def extract_domain(host, tld_length) extract_domain_from(host, tld_length) if named_host?(host) end def extract_subdomains(host, tld_length) if named_host?(host) extract_subdomains_from(host, tld_length) else [] end end def extract_subdomain(host, tld_length) extract_subdomains(host, tld_length).join('.') end def url_for(options) if options[:only_path] path_for options else full_url_for options end end def full_url_for(options) host = options[:host] protocol = options[:protocol] port = options[:port] unless host raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' end build_host_url(host, port, protocol, options, path_for(options)) end def path_for(options) path = options[:script_name].to_s.chomp("/") path << options[:path] if options.key?(:path) add_trailing_slash(path) if options[:trailing_slash] add_params(path, options[:params]) if options.key?(:params) add_anchor(path, options[:anchor]) if options.key?(:anchor) path end private def add_params(path, params) params = { params: params } unless params.is_a?(Hash) params.reject! { |_,v| v.to_param.nil? } path << "?#{params.to_query}" unless params.empty? end def add_anchor(path, anchor) if anchor path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}" end end def extract_domain_from(host, tld_length) host.split('.').last(1 + tld_length).join('.') end def extract_subdomains_from(host, tld_length) parts = host.split('.') parts[0..-(tld_length + 2)] end def add_trailing_slash(path) # includes querysting if path.include?('?') path.sub!(/\?/, '/\&') # does not have a .format elsif !path.include?(".") path.sub!(/[^\/]\z|\A\z/, '\&/') end end def build_host_url(host, port, protocol, options, path) if match = host.match(HOST_REGEXP) protocol ||= match[1] unless protocol == false host = match[2] port = match[3] unless options.key? :port end protocol = normalize_protocol protocol host = normalize_host(host, options) result = protocol.dup if options[:user] && options[:password] result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@" end result << host normalize_port(port, protocol) { |normalized_port| result << ":#{normalized_port}" } result.concat path end def named_host?(host) IP_HOST_REGEXP !~ host end def normalize_protocol(protocol) case protocol when nil "http://" when false, "//" "//" when PROTOCOL_REGEXP "#{$1}://" else raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}" end end def normalize_host(_host, options) return _host unless named_host?(_host) tld_length = options[:tld_length] || @@tld_length subdomain = options.fetch :subdomain, true domain = options[:domain] host = "" if subdomain == true return _host if domain.nil? host << extract_subdomains_from(_host, tld_length).join('.') elsif subdomain host << subdomain.to_param end host << "." unless host.empty? host << (domain || extract_domain_from(_host, tld_length)) host end def normalize_port(port, protocol) return unless port case protocol when "//" then yield port when "https://" yield port unless port.to_i == 443 else yield port unless port.to_i == 80 end end end def initialize(env) super @protocol = nil @port = nil end # Returns the complete URL used for this request. def url protocol + host_with_port + fullpath end # Returns 'https://' if this is an SSL request and 'http://' otherwise. def protocol @protocol ||= ssl? ? 'https://' : 'http://' end # Returns the \host for this request, such as "example.com". def raw_host_with_port if forwarded = env["HTTP_X_FORWARDED_HOST"].presence forwarded.split(/,\s?/).last else env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}" end end # Returns the host for this request, such as example.com. def host raw_host_with_port.sub(/:\d+$/, '') end # Returns a \host:\port string for this request, such as "example.com" or # "example.com:8080". def host_with_port "#{host}#{port_string}" end # Returns the port number of this request as an integer. def port @port ||= begin if raw_host_with_port =~ /:(\d+)$/ $1.to_i else standard_port end end end # Returns the standard \port number for this request's protocol. def standard_port case protocol when 'https://' then 443 else 80 end end # Returns whether this request is using the standard port def standard_port? port == standard_port end # Returns a number \port suffix like 8080 if the \port number of this request # is not the default HTTP \port 80 or HTTPS \port 443. def optional_port standard_port? ? nil : port end # Returns a string \port suffix, including colon, like ":8080" if the \port # number of this request is not the default HTTP \port 80 or HTTPS \port 443. def port_string standard_port? ? '' : ":#{port}" end def server_port @env['SERVER_PORT'].to_i end # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify # a different tld_length, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". def domain(tld_length = @@tld_length) ActionDispatch::Http::URL.extract_domain(host, tld_length) end # Returns all the \subdomains as an array, so ["dev", "www"] would be # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, # such as 2 to catch ["www"] instead of ["www", "rubyonrails"] # in "www.rubyonrails.co.uk". def subdomains(tld_length = @@tld_length) ActionDispatch::Http::URL.extract_subdomains(host, tld_length) end # Returns all the \subdomains as a string, so "dev.www" would be # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, # such as 2 to catch "www" instead of "www.rubyonrails" # in "www.rubyonrails.co.uk". def subdomain(tld_length = @@tld_length) ActionDispatch::Http::URL.extract_subdomain(host, tld_length) end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey.rb000066400000000000000000000003451266740050600227030ustar00rootroot00000000000000require 'action_dispatch/journey/router' require 'action_dispatch/journey/gtg/builder' require 'action_dispatch/journey/gtg/simulator' require 'action_dispatch/journey/nfa/builder' require 'action_dispatch/journey/nfa/simulator' rails-4.2.6/actionpack/lib/action_dispatch/journey/000077500000000000000000000000001266740050600223545ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/journey/backwards.rb000066400000000000000000000003001266740050600246330ustar00rootroot00000000000000module Rack # :nodoc: Mount = ActionDispatch::Journey::Router Mount::RouteSet = ActionDispatch::Journey::Router Mount::RegexpWithNamedGroups = ActionDispatch::Journey::Path::Pattern end rails-4.2.6/actionpack/lib/action_dispatch/journey/formatter.rb000066400000000000000000000120661266740050600247110ustar00rootroot00000000000000require 'action_controller/metal/exceptions' require 'active_support/deprecation' module ActionDispatch module Journey # The Formatter class is used for formatting URLs. For example, parameters # passed to +url_for+ in Rails will eventually call Formatter#generate. class Formatter # :nodoc: attr_reader :routes def initialize(routes) @routes = routes @cache = nil end def generate(name, options, path_parameters, parameterize = nil) constraints = path_parameters.merge(options) missing_keys = [] match_route(name, constraints) do |route| parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize) # Skip this route unless a name has been provided or it is a # standard Rails route since we can't determine whether an options # hash passed to url_for matches a Rack application or a redirect. next unless name || route.dispatcher? missing_keys = missing_keys(route, parameterized_parts) next unless missing_keys.empty? params = options.dup.delete_if do |key, _| parameterized_parts.key?(key) || route.defaults.key?(key) end defaults = route.defaults required_parts = route.required_parts parameterized_parts.delete_if do |key, value| value.to_s == defaults[key].to_s && !required_parts.include?(key) end return [route.format(parameterized_parts), params] end message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}" message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty? raise ActionController::UrlGenerationError, message end def clear @cache = nil end private def extract_parameterized_parts(route, options, recall, parameterize = nil) parameterized_parts = recall.merge(options) keys_to_keep = route.parts.reverse.drop_while { |part| !options.key?(part) || (options[part] || recall[part]).nil? } | route.required_parts (parameterized_parts.keys - keys_to_keep).each do |bad_key| parameterized_parts.delete(bad_key) end if parameterize parameterized_parts.each do |k, v| parameterized_parts[k] = parameterize.call(k, v) end end parameterized_parts.keep_if { |_, v| v } parameterized_parts end def named_routes routes.named_routes end def match_route(name, options) if named_routes.key?(name) yield named_routes[name] else # Make sure we don't show the deprecation warning more than once warned = false routes = non_recursive(cache, options) hash = routes.group_by { |_, r| r.score(options) } hash.keys.sort.reverse_each do |score| break if score < 0 hash[score].sort_by { |i, _| i }.each do |_, route| if name && !warned ActiveSupport::Deprecation.warn <<-MSG.squish You are trying to generate the URL for a named route called #{name.inspect} but no such route was found. In the future, this will result in an `ActionController::UrlGenerationError` exception. MSG warned = true end yield route end end end end def non_recursive(cache, options) routes = [] queue = [cache] while queue.any? c = queue.shift routes.concat(c[:___routes]) if c.key?(:___routes) options.each do |pair| queue << c[pair] if c.key?(pair) end end routes end # Returns an array populated with missing keys if any are present. def missing_keys(route, parts) missing_keys = [] tests = route.path.requirements route.required_parts.each { |key| if tests.key?(key) missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key] else missing_keys << key unless parts[key] end } missing_keys end def possibles(cache, options, depth = 0) cache.fetch(:___routes) { [] } + options.find_all { |pair| cache.key?(pair) }.flat_map { |pair| possibles(cache[pair], options, depth + 1) } end def build_cache root = { ___routes: [] } routes.each_with_index do |route, i| leaf = route.required_defaults.inject(root) do |h, tuple| h[tuple] ||= {} end (leaf[:___routes] ||= []) << [i, route] end root end def cache @cache ||= build_cache end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/gtg/000077500000000000000000000000001266740050600231355ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/journey/gtg/builder.rb000066400000000000000000000103451266740050600251130ustar00rootroot00000000000000require 'action_dispatch/journey/gtg/transition_table' module ActionDispatch module Journey # :nodoc: module GTG # :nodoc: class Builder # :nodoc: DUMMY = Nodes::Dummy.new attr_reader :root, :ast, :endpoints def initialize(root) @root = root @ast = Nodes::Cat.new root, DUMMY @followpos = nil end def transition_table dtrans = TransitionTable.new marked = {} state_id = Hash.new { |h,k| h[k] = h.length } start = firstpos(root) dstates = [start] until dstates.empty? s = dstates.shift next if marked[s] marked[s] = true # mark s s.group_by { |state| symbol(state) }.each do |sym, ps| u = ps.flat_map { |l| followpos(l) } next if u.empty? if u.uniq == [DUMMY] from = state_id[s] to = state_id[Object.new] dtrans[from, to] = sym dtrans.add_accepting(to) ps.each { |state| dtrans.add_memo(to, state.memo) } else dtrans[state_id[s], state_id[u]] = sym if u.include?(DUMMY) to = state_id[u] accepting = ps.find_all { |l| followpos(l).include?(DUMMY) } accepting.each { |accepting_state| dtrans.add_memo(to, accepting_state.memo) } dtrans.add_accepting(state_id[u]) end end dstates << u end end dtrans end def nullable?(node) case node when Nodes::Group true when Nodes::Star true when Nodes::Or node.children.any? { |c| nullable?(c) } when Nodes::Cat nullable?(node.left) && nullable?(node.right) when Nodes::Terminal !node.left when Nodes::Unary nullable?(node.left) else raise ArgumentError, 'unknown nullable: %s' % node.class.name end end def firstpos(node) case node when Nodes::Star firstpos(node.left) when Nodes::Cat if nullable?(node.left) firstpos(node.left) | firstpos(node.right) else firstpos(node.left) end when Nodes::Or node.children.flat_map { |c| firstpos(c) }.uniq when Nodes::Unary firstpos(node.left) when Nodes::Terminal nullable?(node) ? [] : [node] else raise ArgumentError, 'unknown firstpos: %s' % node.class.name end end def lastpos(node) case node when Nodes::Star firstpos(node.left) when Nodes::Or node.children.flat_map { |c| lastpos(c) }.uniq when Nodes::Cat if nullable?(node.right) lastpos(node.left) | lastpos(node.right) else lastpos(node.right) end when Nodes::Terminal nullable?(node) ? [] : [node] when Nodes::Unary lastpos(node.left) else raise ArgumentError, 'unknown lastpos: %s' % node.class.name end end def followpos(node) followpos_table[node] end private def followpos_table @followpos ||= build_followpos end def build_followpos table = Hash.new { |h, k| h[k] = [] } @ast.each do |n| case n when Nodes::Cat lastpos(n.left).each do |i| table[i] += firstpos(n.right) end when Nodes::Star lastpos(n).each do |i| table[i] += firstpos(n) end end end table end def symbol(edge) case edge when Journey::Nodes::Symbol edge.regexp else edge.left end end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/gtg/simulator.rb000066400000000000000000000017431266740050600255060ustar00rootroot00000000000000require 'strscan' module ActionDispatch module Journey # :nodoc: module GTG # :nodoc: class MatchData # :nodoc: attr_reader :memos def initialize(memos) @memos = memos end end class Simulator # :nodoc: attr_reader :tt def initialize(transition_table) @tt = transition_table end def simulate(string) ms = memos(string) { return } MatchData.new(ms) end alias :=~ :simulate alias :match :simulate def memos(string) input = StringScanner.new(string) state = [0] while sym = input.scan(%r([/.?]|[^/.?]+)) state = tt.move(state, sym) end acceptance_states = state.find_all { |s| tt.accepting? s } return yield if acceptance_states.empty? acceptance_states.flat_map { |x| tt.memo(x) }.compact end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb000066400000000000000000000076551266740050600270400ustar00rootroot00000000000000require 'action_dispatch/journey/nfa/dot' module ActionDispatch module Journey # :nodoc: module GTG # :nodoc: class TransitionTable # :nodoc: include Journey::NFA::Dot attr_reader :memos def initialize @regexp_states = {} @string_states = {} @accepting = {} @memos = Hash.new { |h,k| h[k] = [] } end def add_accepting(state) @accepting[state] = true end def accepting_states @accepting.keys end def accepting?(state) @accepting[state] end def add_memo(idx, memo) @memos[idx] << memo end def memo(idx) @memos[idx] end def eclosure(t) Array(t) end def move(t, a) return [] if t.empty? regexps = [] t.map { |s| if states = @regexp_states[s] regexps.concat states.map { |re, v| re === a ? v : nil } end if states = @string_states[s] states[a] end }.compact.concat regexps end def as_json(options = nil) simple_regexp = Hash.new { |h,k| h[k] = {} } @regexp_states.each do |from, hash| hash.each do |re, to| simple_regexp[from][re.source] = to end end { regexp_states: simple_regexp, string_states: @string_states, accepting: @accepting } end def to_svg svg = IO.popen('dot -Tsvg', 'w+') { |f| f.write(to_dot) f.close_write f.readlines } 3.times { svg.shift } svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '') end def visualizer(paths, title = 'FSM') viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer' fsm_js = File.read File.join(viz_dir, 'fsm.js') fsm_css = File.read File.join(viz_dir, 'fsm.css') erb = File.read File.join(viz_dir, 'index.html.erb') states = "function tt() { return #{to_json}; }" fun_routes = paths.sample(3).map do |ast| ast.map { |n| case n when Nodes::Symbol case n.left when ':id' then rand(100).to_s when ':format' then %w{ xml json }.sample else 'omg' end when Nodes::Terminal then n.symbol else nil end }.compact.join end stylesheets = [fsm_css] svg = to_svg javascripts = [states, fsm_js] # Annoying hack for 1.9 warnings fun_routes = fun_routes stylesheets = stylesheets svg = svg javascripts = javascripts require 'erb' template = ERB.new erb template.result(binding) end def []=(from, to, sym) to_mappings = states_hash_for(sym)[from] ||= {} to_mappings[sym] = to end def states ss = @string_states.keys + @string_states.values.flat_map(&:values) rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values) (ss + rs).uniq end def transitions @string_states.flat_map { |from, hash| hash.map { |s, to| [from, s, to] } } + @regexp_states.flat_map { |from, hash| hash.map { |s, to| [from, s, to] } } end private def states_hash_for(sym) case sym when String @string_states when Regexp @regexp_states else raise ArgumentError, 'unknown symbol: %s' % sym.class end end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/nfa/000077500000000000000000000000001266740050600231205ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/journey/nfa/builder.rb000066400000000000000000000031371266740050600250770ustar00rootroot00000000000000require 'action_dispatch/journey/nfa/transition_table' require 'action_dispatch/journey/gtg/transition_table' module ActionDispatch module Journey # :nodoc: module NFA # :nodoc: class Visitor < Visitors::Visitor # :nodoc: def initialize(tt) @tt = tt @i = -1 end def visit_CAT(node) left = visit(node.left) right = visit(node.right) @tt.merge(left.last, right.first) [left.first, right.last] end def visit_GROUP(node) from = @i += 1 left = visit(node.left) to = @i += 1 @tt.accepting = to @tt[from, left.first] = nil @tt[left.last, to] = nil @tt[from, to] = nil [from, to] end def visit_OR(node) from = @i += 1 children = node.children.map { |c| visit(c) } to = @i += 1 children.each do |child| @tt[from, child.first] = nil @tt[child.last, to] = nil end @tt.accepting = to [from, to] end def terminal(node) from_i = @i += 1 # new state to_i = @i += 1 # new state @tt[from_i, to_i] = node @tt.accepting = to_i @tt.add_memo(to_i, node.memo) [from_i, to_i] end end class Builder # :nodoc: def initialize(ast) @ast = ast end def transition_table tt = TransitionTable.new Visitor.new(tt).accept(@ast) tt end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/nfa/dot.rb000066400000000000000000000015561266740050600242420ustar00rootroot00000000000000# encoding: utf-8 module ActionDispatch module Journey # :nodoc: module NFA # :nodoc: module Dot # :nodoc: def to_dot edges = transitions.map { |from, sym, to| " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];" } #memo_nodes = memos.values.flatten.map { |n| # label = n # if Journey::Route === n # label = "#{n.verb.source} #{n.path.spec}" # end # " #{n.object_id} [label=\"#{label}\", shape=box];" #} #memo_edges = memos.flat_map { |k, memos| # (memos || []).map { |v| " #{k} -> #{v.object_id};" } #}.uniq <<-eodot digraph nfa { rankdir=LR; node [shape = doublecircle]; #{accepting_states.join ' '}; node [shape = circle]; #{edges.join "\n"} } eodot end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/nfa/simulator.rb000066400000000000000000000020371266740050600254660ustar00rootroot00000000000000require 'strscan' module ActionDispatch module Journey # :nodoc: module NFA # :nodoc: class MatchData # :nodoc: attr_reader :memos def initialize(memos) @memos = memos end end class Simulator # :nodoc: attr_reader :tt def initialize(transition_table) @tt = transition_table end def simulate(string) input = StringScanner.new(string) state = tt.eclosure(0) until input.eos? sym = input.scan(%r([/.?]|[^/.?]+)) # FIXME: tt.eclosure is not needed for the GTG state = tt.eclosure(tt.move(state, sym)) end acceptance_states = state.find_all { |s| tt.accepting?(tt.eclosure(s).sort.last) } return if acceptance_states.empty? memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact MatchData.new(memos) end alias :=~ :simulate alias :match :simulate end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb000066400000000000000000000077211266740050600270150ustar00rootroot00000000000000require 'action_dispatch/journey/nfa/dot' module ActionDispatch module Journey # :nodoc: module NFA # :nodoc: class TransitionTable # :nodoc: include Journey::NFA::Dot attr_accessor :accepting attr_reader :memos def initialize @table = Hash.new { |h,f| h[f] = {} } @memos = {} @accepting = nil @inverted = nil end def accepting?(state) accepting == state end def accepting_states [accepting] end def add_memo(idx, memo) @memos[idx] = memo end def memo(idx) @memos[idx] end def []=(i, f, s) @table[f][i] = s end def merge(left, right) @memos[right] = @memos.delete(left) @table[right] = @table.delete(left) end def states (@table.keys + @table.values.flat_map(&:keys)).uniq end # Returns a generalized transition graph with reduced states. The states # are reduced like a DFA, but the table must be simulated like an NFA. # # Edges of the GTG are regular expressions. def generalized_table gt = GTG::TransitionTable.new marked = {} state_id = Hash.new { |h,k| h[k] = h.length } alphabet = self.alphabet stack = [eclosure(0)] until stack.empty? state = stack.pop next if marked[state] || state.empty? marked[state] = true alphabet.each do |alpha| next_state = eclosure(following_states(state, alpha)) next if next_state.empty? gt[state_id[state], state_id[next_state]] = alpha stack << next_state end end final_groups = state_id.keys.find_all { |s| s.sort.last == accepting } final_groups.each do |states| id = state_id[states] gt.add_accepting(id) save = states.find { |s| @memos.key?(s) && eclosure(s).sort.last == accepting } gt.add_memo(id, memo(save)) end gt end # Returns set of NFA states to which there is a transition on ast symbol # +a+ from some state +s+ in +t+. def following_states(t, a) Array(t).flat_map { |s| inverted[s][a] }.uniq end # Returns set of NFA states to which there is a transition on ast symbol # +a+ from some state +s+ in +t+. def move(t, a) Array(t).map { |s| inverted[s].keys.compact.find_all { |sym| sym === a }.map { |sym| inverted[s][sym] } }.flatten.uniq end def alphabet inverted.values.flat_map(&:keys).compact.uniq.sort_by { |x| x.to_s } end # Returns a set of NFA states reachable from some NFA state +s+ in set # +t+ on nil-transitions alone. def eclosure(t) stack = Array(t) seen = {} children = [] until stack.empty? s = stack.pop next if seen[s] seen[s] = true children << s stack.concat(inverted[s][nil]) end children.uniq end def transitions @table.flat_map { |to, hash| hash.map { |from, sym| [from, sym, to] } } end private def inverted return @inverted if @inverted @inverted = Hash.new { |h, from| h[from] = Hash.new { |j, s| j[s] = [] } } @table.each { |to, hash| hash.each { |from, sym| if sym sym = Nodes::Symbol === sym ? sym.regexp : sym.left end @inverted[from][sym] << to } } @inverted end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/nodes/000077500000000000000000000000001266740050600234645ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/journey/nodes/node.rb000066400000000000000000000046231266740050600247430ustar00rootroot00000000000000require 'action_dispatch/journey/visitors' module ActionDispatch module Journey # :nodoc: module Nodes # :nodoc: class Node # :nodoc: include Enumerable attr_accessor :left, :memo def initialize(left) @left = left @memo = nil end def each(&block) Visitors::Each.new(block).accept(self) end def to_s Visitors::String.new.accept(self) end def to_dot Visitors::Dot.new.accept(self) end def to_sym name.to_sym end def name left.tr '*:', '' end def type raise NotImplementedError end def symbol?; false; end def literal?; false; end end class Terminal < Node # :nodoc: alias :symbol :left end class Literal < Terminal # :nodoc: def literal?; true; end def type; :LITERAL; end end class Dummy < Literal # :nodoc: def initialize(x = Object.new) super end def literal?; false; end end %w{ Symbol Slash Dot }.each do |t| class_eval <<-eoruby, __FILE__, __LINE__ + 1 class #{t} < Terminal; def type; :#{t.upcase}; end end eoruby end class Symbol < Terminal # :nodoc: attr_accessor :regexp alias :symbol :regexp DEFAULT_EXP = /[^\.\/\?]+/ def initialize(left) super @regexp = DEFAULT_EXP end def default_regexp? regexp == DEFAULT_EXP end def symbol?; true; end end class Unary < Node # :nodoc: def children; [left] end end class Group < Unary # :nodoc: def type; :GROUP; end end class Star < Unary # :nodoc: def type; :STAR; end def name left.name.tr '*:', '' end end class Binary < Node # :nodoc: attr_accessor :right def initialize(left, right) super(left) @right = right end def children; [left, right] end end class Cat < Binary # :nodoc: def type; :CAT; end end class Or < Node # :nodoc: attr_reader :children def initialize(children) @children = children end def type; :OR; end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/parser.rb000066400000000000000000000072531266740050600242040ustar00rootroot00000000000000# # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.11 # from Racc grammer file "". # require 'racc/parser.rb' require 'action_dispatch/journey/parser_extras' module ActionDispatch module Journey class Parser < Racc::Parser ##### State transition tables begin ### racc_action_table = [ 13, 15, 14, 7, 21, 16, 8, 19, 13, 15, 14, 7, 17, 16, 8, 13, 15, 14, 7, 24, 16, 8, 13, 15, 14, 7, 19, 16, 8 ] racc_action_check = [ 2, 2, 2, 2, 17, 2, 2, 2, 0, 0, 0, 0, 1, 0, 0, 19, 19, 19, 19, 20, 19, 19, 7, 7, 7, 7, 22, 7, 7 ] racc_action_pointer = [ 6, 12, -2, nil, nil, nil, nil, 20, nil, nil, nil, nil, nil, nil, nil, nil, nil, 4, nil, 13, 13, nil, 17, nil, nil ] racc_action_default = [ -19, -19, -2, -3, -4, -5, -6, -19, -10, -11, -12, -13, -14, -15, -16, -17, -18, -19, -1, -19, -19, 25, -8, -9, -7 ] racc_goto_table = [ 1, 22, 18, 23, nil, nil, nil, 20 ] racc_goto_check = [ 1, 2, 1, 3, nil, nil, nil, 1 ] racc_goto_pointer = [ nil, 0, -18, -16, nil, nil, nil, nil, nil, nil, nil ] racc_goto_default = [ nil, nil, 2, 3, 4, 5, 6, 9, 10, 11, 12 ] racc_reduce_table = [ 0, 0, :racc_error, 2, 11, :_reduce_1, 1, 11, :_reduce_2, 1, 11, :_reduce_none, 1, 12, :_reduce_none, 1, 12, :_reduce_none, 1, 12, :_reduce_none, 3, 15, :_reduce_7, 3, 13, :_reduce_8, 3, 13, :_reduce_9, 1, 16, :_reduce_10, 1, 14, :_reduce_none, 1, 14, :_reduce_none, 1, 14, :_reduce_none, 1, 14, :_reduce_none, 1, 19, :_reduce_15, 1, 17, :_reduce_16, 1, 18, :_reduce_17, 1, 20, :_reduce_18 ] racc_reduce_n = 19 racc_shift_n = 25 racc_token_table = { false => 0, :error => 1, :SLASH => 2, :LITERAL => 3, :SYMBOL => 4, :LPAREN => 5, :RPAREN => 6, :DOT => 7, :STAR => 8, :OR => 9 } racc_nt_base = 10 racc_use_result_var = false Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "SLASH", "LITERAL", "SYMBOL", "LPAREN", "RPAREN", "DOT", "STAR", "OR", "$start", "expressions", "expression", "or", "terminal", "group", "star", "symbol", "literal", "slash", "dot" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted def _reduce_1(val, _values) Cat.new(val.first, val.last) end def _reduce_2(val, _values) val.first end # reduce 3 omitted # reduce 4 omitted # reduce 5 omitted # reduce 6 omitted def _reduce_7(val, _values) Group.new(val[1]) end def _reduce_8(val, _values) Or.new([val.first, val.last]) end def _reduce_9(val, _values) Or.new([val.first, val.last]) end def _reduce_10(val, _values) Star.new(Symbol.new(val.last)) end # reduce 11 omitted # reduce 12 omitted # reduce 13 omitted # reduce 14 omitted def _reduce_15(val, _values) Slash.new('/') end def _reduce_16(val, _values) Symbol.new(val.first) end def _reduce_17(val, _values) Literal.new(val.first) end def _reduce_18(val, _values) Dot.new(val.first) end def _reduce_none(val, _values) val[0] end end # class Parser end # module Journey end # module ActionDispatch rails-4.2.6/actionpack/lib/action_dispatch/journey/parser.y000066400000000000000000000017051266740050600240450ustar00rootroot00000000000000class ActionDispatch::Journey::Parser options no_result_var token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR rule expressions : expression expressions { Cat.new(val.first, val.last) } | expression { val.first } | or ; expression : terminal | group | star ; group : LPAREN expressions RPAREN { Group.new(val[1]) } ; or : expression OR expression { Or.new([val.first, val.last]) } | expression OR or { Or.new([val.first, val.last]) } ; star : STAR { Star.new(Symbol.new(val.last)) } ; terminal : symbol | literal | slash | dot ; slash : SLASH { Slash.new('/') } ; symbol : SYMBOL { Symbol.new(val.first) } ; literal : LITERAL { Literal.new(val.first) } ; dot : DOT { Dot.new(val.first) } ; end ---- header require 'action_dispatch/journey/parser_extras' rails-4.2.6/actionpack/lib/action_dispatch/journey/parser_extras.rb000066400000000000000000000006651266740050600255720ustar00rootroot00000000000000require 'action_dispatch/journey/scanner' require 'action_dispatch/journey/nodes/node' module ActionDispatch module Journey # :nodoc: class Parser < Racc::Parser # :nodoc: include Journey::Nodes def initialize @scanner = Scanner.new end def parse(string) @scanner.scan_setup(string) do_parse end def next_token @scanner.next_token end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/path/000077500000000000000000000000001266740050600233105ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/journey/path/pattern.rb000066400000000000000000000110331266740050600253100ustar00rootroot00000000000000require 'action_dispatch/journey/router/strexp' module ActionDispatch module Journey # :nodoc: module Path # :nodoc: class Pattern # :nodoc: attr_reader :spec, :requirements, :anchored def self.from_string string new Journey::Router::Strexp.build(string, {}, ["/.?"], true) end def initialize(strexp) @spec = strexp.ast @requirements = strexp.requirements @separators = strexp.separators.join @anchored = strexp.anchor @names = nil @optional_names = nil @required_names = nil @re = nil @offsets = nil end def build_formatter Visitors::FormatBuilder.new.accept(spec) end def ast @spec.grep(Nodes::Symbol).each do |node| re = @requirements[node.to_sym] node.regexp = re if re end @spec.grep(Nodes::Star).each do |node| node = node.left node.regexp = @requirements[node.to_sym] || /(.+)/ end @spec end def names @names ||= spec.grep(Nodes::Symbol).map { |n| n.name } end def required_names @required_names ||= names - optional_names end def optional_names @optional_names ||= spec.grep(Nodes::Group).flat_map { |group| group.grep(Nodes::Symbol) }.map { |n| n.name }.uniq end class RegexpOffsets < Journey::Visitors::Visitor # :nodoc: attr_reader :offsets def initialize(matchers) @matchers = matchers @capture_count = [0] end def visit(node) super @capture_count end def visit_SYMBOL(node) node = node.to_sym if @matchers.key?(node) re = /#{@matchers[node]}|/ @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0)) else @capture_count << (@capture_count.last || 0) end end end class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc: def initialize(separator, matchers) @separator = separator @matchers = matchers @separator_re = "([^#{separator}]+)" super() end def accept(node) %r{\A#{visit node}\Z} end def visit_CAT(node) [visit(node.left), visit(node.right)].join end def visit_SYMBOL(node) node = node.to_sym return @separator_re unless @matchers.key?(node) re = @matchers[node] "(#{re})" end def visit_GROUP(node) "(?:#{visit node.left})?" end def visit_LITERAL(node) Regexp.escape(node.left) end alias :visit_DOT :visit_LITERAL def visit_SLASH(node) node.left end def visit_STAR(node) re = @matchers[node.left.to_sym] || '.+' "(#{re})" end end class UnanchoredRegexp < AnchoredRegexp # :nodoc: def accept(node) %r{\A#{visit node}} end end class MatchData # :nodoc: attr_reader :names def initialize(names, offsets, match) @names = names @offsets = offsets @match = match end def captures (length - 1).times.map { |i| self[i + 1] } end def [](x) idx = @offsets[x - 1] + x @match[idx] end def length @offsets.length end def post_match @match.post_match end def to_s @match.to_s end end def match(other) return unless match = to_regexp.match(other) MatchData.new(names, offsets, match) end alias :=~ :match def source to_regexp.source end def to_regexp @re ||= regexp_visitor.new(@separators, @requirements).accept spec end private def regexp_visitor @anchored ? AnchoredRegexp : UnanchoredRegexp end def offsets return @offsets if @offsets viz = RegexpOffsets.new(@requirements) @offsets = viz.accept(spec) end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/route.rb000066400000000000000000000060041266740050600240370ustar00rootroot00000000000000module ActionDispatch module Journey # :nodoc: class Route # :nodoc: attr_reader :app, :path, :defaults, :name attr_reader :constraints alias :conditions :constraints attr_accessor :precedence ## # +path+ is a path constraint. # +constraints+ is a hash of constraints to be applied to this route. def initialize(name, app, path, constraints, defaults = {}) @name = name @app = app @path = path @constraints = constraints @defaults = defaults @required_defaults = nil @required_parts = nil @parts = nil @decorated_ast = nil @precedence = 0 @path_formatter = @path.build_formatter end def ast @decorated_ast ||= begin decorated_ast = path.ast decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self } decorated_ast end end def requirements # :nodoc: # needed for rails `rake routes` @defaults.merge(path.requirements).delete_if { |_,v| /.+?/ == v } end def segments path.names end def required_keys required_parts + required_defaults.keys end def score(constraints) required_keys = path.required_names supplied_keys = constraints.map { |k,v| v && k.to_s }.compact return -1 unless (required_keys - supplied_keys).empty? score = (supplied_keys & path.names).length score + (required_defaults.length * 2) end def parts @parts ||= segments.map { |n| n.to_sym } end alias :segment_keys :parts def format(path_options) @path_formatter.evaluate path_options end def optional_parts path.optional_names.map { |n| n.to_sym } end def required_parts @required_parts ||= path.required_names.map { |n| n.to_sym } end def required_default?(key) (constraints[:required_defaults] || []).include?(key) end def required_defaults @required_defaults ||= @defaults.dup.delete_if do |k,_| parts.include?(k) || !required_default?(k) end end def glob? !path.spec.grep(Nodes::Star).empty? end def dispatcher? @app.dispatcher? end def matches?(request) constraints.all? do |method, value| next true unless request.respond_to?(method) case value when Regexp, String value === request.send(method).to_s when Array value.include?(request.send(method)) when TrueClass request.send(method).present? when FalseClass request.send(method).blank? else value === request.send(method) end end end def ip constraints[:ip] || // end def verb constraints[:request_method] || // end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/router.rb000066400000000000000000000073561266740050600242340ustar00rootroot00000000000000require 'action_dispatch/journey/router/utils' require 'action_dispatch/journey/router/strexp' require 'action_dispatch/journey/routes' require 'action_dispatch/journey/formatter' before = $-w $-w = false require 'action_dispatch/journey/parser' $-w = before require 'action_dispatch/journey/route' require 'action_dispatch/journey/path/pattern' module ActionDispatch module Journey # :nodoc: class Router # :nodoc: class RoutingError < ::StandardError # :nodoc: end # :nodoc: VERSION = '2.0.0' attr_accessor :routes def initialize(routes) @routes = routes end def serve(req) find_routes(req).each do |match, parameters, route| set_params = req.path_parameters path_info = req.path_info script_name = req.script_name unless route.path.anchored req.script_name = (script_name.to_s + match.to_s).chomp('/') req.path_info = match.post_match req.path_info = "/" + req.path_info unless req.path_info.start_with? "/" end req.path_parameters = set_params.merge parameters status, headers, body = route.app.serve(req) if 'pass' == headers['X-Cascade'] req.script_name = script_name req.path_info = path_info req.path_parameters = set_params next end return [status, headers, body] end return [404, {'X-Cascade' => 'pass'}, ['Not Found']] end def recognize(rails_req) find_routes(rails_req).each do |match, parameters, route| unless route.path.anchored rails_req.script_name = match.to_s rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1') end yield(route, parameters) end end def visualizer tt = GTG::Builder.new(ast).transition_table groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s } asts = groups.values.map { |v| v.first } tt.visualizer(asts) end private def partitioned_routes routes.partitioned_routes end def ast routes.ast end def simulator routes.simulator end def custom_routes partitioned_routes.last end def filter_routes(path) return [] unless ast simulator.memos(path) { [] } end def find_routes req routes = filter_routes(req.path_info).concat custom_routes.find_all { |r| r.path.match(req.path_info) } routes = if req.request_method == "HEAD" match_head_routes(routes, req) else match_routes(routes, req) end routes.sort_by!(&:precedence) routes.map! { |r| match_data = r.path.match(req.path_info) path_parameters = r.defaults.dup match_data.names.zip(match_data.captures) { |name,val| path_parameters[name.to_sym] = Utils.unescape_uri(val) if val } [match_data, path_parameters, r] } end def match_head_routes(routes, req) verb_specific_routes = routes.reject { |route| route.verb == // } head_routes = match_routes(verb_specific_routes, req) if head_routes.empty? begin req.request_method = "GET" match_routes(routes, req) ensure req.request_method = "HEAD" end else head_routes end end def match_routes(routes, req) routes.select { |r| r.matches?(req) } end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/router/000077500000000000000000000000001266740050600236745ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/journey/router/strexp.rb000066400000000000000000000013431266740050600255470ustar00rootroot00000000000000module ActionDispatch module Journey # :nodoc: class Router # :nodoc: class Strexp # :nodoc: class << self alias :compile :new end attr_reader :path, :requirements, :separators, :anchor, :ast def self.build(path, requirements, separators, anchor = true) parser = Journey::Parser.new ast = parser.parse path new ast, path, requirements, separators, anchor end def initialize(ast, path, requirements, separators, anchor = true) @ast = ast @path = path @requirements = requirements @separators = separators @anchor = anchor end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/router/utils.rb000066400000000000000000000054271266740050600253710ustar00rootroot00000000000000module ActionDispatch module Journey # :nodoc: class Router # :nodoc: class Utils # :nodoc: # Normalizes URI path. # # Strips off trailing slash and ensures there is a leading slash. # Also converts downcase url encoded string to uppercase. # # normalize_path("/foo") # => "/foo" # normalize_path("/foo/") # => "/foo" # normalize_path("foo") # => "/foo" # normalize_path("") # => "/" # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) path = "/#{path}" path.squeeze!('/') path.sub!(%r{/+\Z}, '') path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } path = '/' if path == '' path end # URI path and fragment escaping # http://tools.ietf.org/html/rfc3986 class UriEncoder # :nodoc: ENCODE = "%%%02X".freeze US_ASCII = Encoding::US_ASCII UTF_8 = Encoding::UTF_8 EMPTY = "".force_encoding(US_ASCII).freeze DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(US_ASCII) } ALPHA = "a-zA-Z".freeze DIGIT = "0-9".freeze UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze ESCAPED = /%[a-zA-Z0-9]{2}/.freeze FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze def escape_fragment(fragment) escape(fragment, FRAGMENT) end def escape_path(path) escape(path, PATH) end def escape_segment(segment) escape(segment, SEGMENT) end def unescape_uri(uri) encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(encoding) end protected def escape(component, pattern) component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) end def percent_encode(unsafe) safe = EMPTY.dup unsafe.each_byte { |b| safe << DEC2HEX[b] } safe end end ENCODER = UriEncoder.new def self.escape_path(path) ENCODER.escape_path(path.to_s) end def self.escape_segment(segment) ENCODER.escape_segment(segment.to_s) end def self.escape_fragment(fragment) ENCODER.escape_fragment(fragment.to_s) end def self.unescape_uri(uri) ENCODER.unescape_uri(uri) end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/routes.rb000066400000000000000000000034321266740050600242240ustar00rootroot00000000000000module ActionDispatch module Journey # :nodoc: # The Routing table. Contains all routes for a system. Routes can be # added to the table by calling Routes#add_route. class Routes # :nodoc: include Enumerable attr_reader :routes, :named_routes def initialize @routes = [] @named_routes = {} @ast = nil @partitioned_routes = nil @simulator = nil end def empty? routes.empty? end def length routes.length end alias :size :length def last routes.last end def each(&block) routes.each(&block) end def clear routes.clear named_routes.clear end def partitioned_routes @partitioned_routes ||= routes.partition do |r| r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?) end end def ast @ast ||= begin asts = partitioned_routes.first.map(&:ast) Nodes::Or.new(asts) unless asts.empty? end end def simulator @simulator ||= begin gtg = GTG::Builder.new(ast).transition_table GTG::Simulator.new(gtg) end end # Add a route to the routing table. def add_route(app, path, conditions, defaults, name = nil) route = Route.new(name, app, path, conditions, defaults) route.precedence = routes.length routes << route named_routes[name] = route if name && !named_routes[name] clear_cache! route end private def clear_cache! @ast = nil @partitioned_routes = nil @simulator = nil end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/scanner.rb000066400000000000000000000023471266740050600243400ustar00rootroot00000000000000require 'strscan' module ActionDispatch module Journey # :nodoc: class Scanner # :nodoc: def initialize @ss = nil end def scan_setup(str) @ss = StringScanner.new(str) end def eos? @ss.eos? end def pos @ss.pos end def pre_match @ss.pre_match end def next_token return if @ss.eos? until token = scan || @ss.eos?; end token end private def scan case # / when text = @ss.scan(/\//) [:SLASH, text] when text = @ss.scan(/\*\w+/) [:STAR, text] when text = @ss.scan(/(?(value) { Router::Utils.escape_path(value) } ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) } class Parameter < Struct.new(:name, :escaper) def escape(value); escaper.call value; end end def self.required_path(symbol) Parameter.new symbol, ESCAPE_PATH end def self.required_segment(symbol) Parameter.new symbol, ESCAPE_SEGMENT end def initialize(parts) @parts = parts @children = [] @parameters = [] parts.each_with_index do |object,i| case object when Journey::Format @children << i when Parameter @parameters << i end end end def evaluate(hash) parts = @parts.dup @parameters.each do |index| param = parts[index] value = hash[param.name] return ''.freeze unless value parts[index] = param.escape value end @children.each { |index| parts[index] = parts[index].evaluate(hash) } parts.join end end module Visitors # :nodoc: class Visitor # :nodoc: DISPATCH_CACHE = {} def accept(node) visit(node) end private def visit node send(DISPATCH_CACHE[node.type], node) end def binary(node) visit(node.left) visit(node.right) end def visit_CAT(n); binary(n); end def nary(node) node.children.each { |c| visit(c) } end def visit_OR(n); nary(n); end def unary(node) visit(node.left) end def visit_GROUP(n); unary(n); end def visit_STAR(n); unary(n); end def terminal(node); end def visit_LITERAL(n); terminal(n); end def visit_SYMBOL(n); terminal(n); end def visit_SLASH(n); terminal(n); end def visit_DOT(n); terminal(n); end private_instance_methods(false).each do |pim| next unless pim =~ /^visit_(.*)$/ DISPATCH_CACHE[$1.to_sym] = pim end end class FormatBuilder < Visitor # :nodoc: def accept(node); Journey::Format.new(super); end def terminal(node); [node.left]; end def binary(node) visit(node.left) + visit(node.right) end def visit_GROUP(n); [Journey::Format.new(unary(n))]; end def visit_STAR(n) [Journey::Format.required_path(n.left.to_sym)] end def visit_SYMBOL(n) symbol = n.to_sym if symbol == :controller [Journey::Format.required_path(symbol)] else [Journey::Format.required_segment(symbol)] end end end # Loop through the requirements AST class Each < Visitor # :nodoc: attr_reader :block def initialize(block) @block = block end def visit(node) block.call(node) super end end class String < Visitor # :nodoc: private def binary(node) [visit(node.left), visit(node.right)].join end def nary(node) node.children.map { |c| visit(c) }.join '|' end def terminal(node) node.left end def visit_GROUP(node) "(#{visit(node.left)})" end end class Dot < Visitor # :nodoc: def initialize @nodes = [] @edges = [] end def accept(node) super <<-eodot digraph parse_tree { size="8,5" node [shape = none]; edge [dir = none]; #{@nodes.join "\n"} #{@edges.join("\n")} } eodot end private def binary(node) node.children.each do |c| @edges << "#{node.object_id} -> #{c.object_id};" end super end def nary(node) node.children.each do |c| @edges << "#{node.object_id} -> #{c.object_id};" end super end def unary(node) @edges << "#{node.object_id} -> #{node.left.object_id};" super end def visit_GROUP(node) @nodes << "#{node.object_id} [label=\"()\"];" super end def visit_CAT(node) @nodes << "#{node.object_id} [label=\"○\"];" super end def visit_STAR(node) @nodes << "#{node.object_id} [label=\"*\"];" super end def visit_OR(node) @nodes << "#{node.object_id} [label=\"|\"];" super end def terminal(node) value = node.left @nodes << "#{node.object_id} [label=\"#{value}\"];" end end end end end rails-4.2.6/actionpack/lib/action_dispatch/journey/visualizer/000077500000000000000000000000001266740050600245515ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/journey/visualizer/fsm.css000066400000000000000000000014511266740050600260510ustar00rootroot00000000000000body { font-family: "Helvetica Neue", Helvetica, Arial, Sans-Serif; margin: 0; } h1 { font-size: 2.0em; font-weight: bold; text-align: center; color: white; background-color: black; padding: 5px 0; margin: 0 0 20px; } h2 { text-align: center; display: none; font-size: 0.5em; } .clearfix {display: inline-block; } .input { overflow: show;} .instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em} .instruction p { padding: 0 0 5px; } .instruction li { padding: 0 10px 5px; } .form { background: #EEE; padding: 20px 30px; border-radius: 5px; margin-left: auto; margin-right: auto; width: 500px; margin-bottom: 20px} .form p, .form form { text-align: center } .form form {padding: 0 10px 5px; } .form .fun_routes { font-size: 0.9em;} .form .fun_routes a { margin: 0 5px 0 0; } rails-4.2.6/actionpack/lib/action_dispatch/journey/visualizer/fsm.js000066400000000000000000000063731266740050600257050ustar00rootroot00000000000000function tokenize(input, callback) { while(input.length > 0) { callback(input.match(/^[\/\.\?]|[^\/\.\?]+/)[0]); input = input.replace(/^[\/\.\?]|[^\/\.\?]+/, ''); } } var graph = d3.select("#chart-2 svg"); var svg_edges = {}; var svg_nodes = {}; graph.selectAll("g.edge").each(function() { var node = d3.select(this); var index = node.select("title").text().split("->"); var left = parseInt(index[0]); var right = parseInt(index[1]); if(!svg_edges[left]) { svg_edges[left] = {} } svg_edges[left][right] = node; }); graph.selectAll("g.node").each(function() { var node = d3.select(this); var index = parseInt(node.select("title").text()); svg_nodes[index] = node; }); function reset_graph() { for(var key in svg_edges) { for(var mkey in svg_edges[key]) { var node = svg_edges[key][mkey]; var path = node.select("path"); var arrow = node.select("polygon"); path.style("stroke", "black"); arrow.style("stroke", "black").style("fill", "black"); } } for(var key in svg_nodes) { var node = svg_nodes[key]; node.select('ellipse').style("fill", "white"); node.select('polygon').style("fill", "white"); } return false; } function highlight_edge(from, to) { var node = svg_edges[from][to]; var path = node.select("path"); var arrow = node.select("polygon"); path .transition().duration(500) .style("stroke", "green"); arrow .transition().duration(500) .style("stroke", "green").style("fill", "green"); } function highlight_state(index, color) { if(!color) { color = "green"; } svg_nodes[index].select('ellipse') .style("fill", "white") .transition().duration(500) .style("fill", color); } function highlight_finish(index) { svg_nodes[index].select('polygon') .style("fill", "while") .transition().duration(500) .style("fill", "blue"); } function match(input) { reset_graph(); var table = tt(); var states = [0]; var regexp_states = table['regexp_states']; var string_states = table['string_states']; var accepting = table['accepting']; highlight_state(0); tokenize(input, function(token) { var new_states = []; for(var key in states) { var state = states[key]; if(string_states[state] && string_states[state][token]) { var new_state = string_states[state][token]; highlight_edge(state, new_state); highlight_state(new_state); new_states.push(new_state); } if(regexp_states[state]) { for(var key in regexp_states[state]) { var re = new RegExp("^" + key + "$"); if(re.test(token)) { var new_state = regexp_states[state][key]; highlight_edge(state, new_state); highlight_state(new_state); new_states.push(new_state); } } } } if(new_states.length == 0) { return; } states = new_states; }); for(var key in states) { var state = states[key]; if(accepting[state]) { for(var mkey in svg_edges[state]) { if(!regexp_states[mkey] && !string_states[mkey]) { highlight_edge(state, mkey); highlight_finish(mkey); } } } else { highlight_state(state, "red"); } } return false; } rails-4.2.6/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb000066400000000000000000000031701266740050600273160ustar00rootroot00000000000000 <%= title %>

Routes FSM with NFA simulation

Type a route in to the box and click "simulate".

Some fun routes to try: <% fun_routes.each do |path| %> <%= path %> <% end %>

<%= svg %>

This is a FSM for a system that has the following routes:

    <% paths.each do |route| %>
  • <%= route %>
  • <% end %>
<% javascripts.each do |js| %> <% end %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/000077500000000000000000000000001266740050600227765ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/middleware/callbacks.rb000066400000000000000000000013501266740050600252410ustar00rootroot00000000000000 module ActionDispatch # Provides callbacks to be executed before and after dispatching the request. class Callbacks include ActiveSupport::Callbacks define_callbacks :call class << self delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader" def before(*args, &block) set_callback(:call, :before, *args, &block) end def after(*args, &block) set_callback(:call, :after, *args, &block) end end def initialize(app) @app = app end def call(env) error = nil result = run_callbacks :call do begin @app.call(env) rescue => error end end raise error if error result end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/cookies.rb000066400000000000000000000477611266740050600247760ustar00rootroot00000000000000require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/object/blank' require 'active_support/key_generator' require 'active_support/message_verifier' require 'active_support/json' module ActionDispatch class Request < Rack::Request def cookie_jar env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self) end end # \Cookies are read and written through ActionController#cookies. # # The cookies being read are the ones received along with the request, the cookies # being written will be sent out with the response. Reading a cookie does not get # the cookie object itself back, just the value it holds. # # Examples of writing: # # # Sets a simple session cookie. # # This cookie will be deleted when the user's browser is closed. # cookies[:user_name] = "david" # # # Cookie values are String based. Other data types need to be serialized. # cookies[:lat_lon] = JSON.generate([47.68, -122.37]) # # # Sets a cookie that expires in 1 hour. # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now } # # # Sets a signed cookie, which prevents users from tampering with its value. # # The cookie is signed by your app's `secrets.secret_key_base` value. # # It can be read using the signed method `cookies.signed[:name]` # cookies.signed[:user_id] = current_user.id # # # Sets a "permanent" cookie (which expires in 20 years from now). # cookies.permanent[:login] = "XJ-122" # # # You can also chain these methods: # cookies.permanent.signed[:login] = "XJ-122" # # Examples of reading: # # cookies[:user_name] # => "david" # cookies.size # => 2 # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37] # cookies.signed[:login] # => "XJ-122" # # Example for deleting: # # cookies.delete :user_name # # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: # # cookies[:name] = { # value: 'a yummy cookie', # expires: 1.year.from_now, # domain: 'domain.com' # } # # cookies.delete(:name, domain: 'domain.com') # # The option symbols for setting cookies are: # # * :value - The cookie's value. # * :path - The path for which this cookie applies. Defaults to the root # of the application. # * :domain - The domain for which this cookie applies so you can # restrict to the domain level. If you use a schema like www.example.com # and want to share session with user.example.com set :domain # to :all. Make sure to specify the :domain option with # :all or Array again when deleting cookies. # # domain: nil # Does not sets cookie domain. (default) # domain: :all # Allow the cookie for the top most level # # domain and subdomains. # domain: %w(.example.com .example.org) # Allow the cookie # # for concrete domain names. # # * :expires - The time at which this cookie expires, as a \Time object. # * :secure - Whether this cookie is only transmitted to HTTPS servers. # Default is +false+. # * :httponly - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. class Cookies HTTP_HEADER = "Set-Cookie".freeze GENERATOR_KEY = "action_dispatch.key_generator".freeze SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze SECRET_TOKEN = "action_dispatch.secret_token".freeze SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze # Cookies can typically store 4096 bytes. MAX_COOKIE_SIZE = 4096 # Raised when storing more than 4K of session data. CookieOverflow = Class.new StandardError # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed module ChainedCookieJars # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: # # cookies.permanent[:prefers_open_id] = true # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT # # This jar is only meant for writing. You'll read permanent cookies through the regular accessor. # # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: # # cookies.permanent.signed[:remember_me] = current_user.id # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT def permanent @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) end # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed # cookie was tampered with by the user (or a 3rd party), nil will be returned. # # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, # legacy cookies signed with the old key generator will be transparently upgraded. # # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+. # # Example: # # cookies.signed[:discount] = 45 # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ # # cookies.signed[:discount] # => 45 def signed @signed ||= if @options[:upgrade_legacy_signed_cookies] UpgradeLegacySignedCookieJar.new(self, @key_generator, @options) else SignedCookieJar.new(self, @key_generator, @options) end end # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read. # If the cookie was tampered with by the user (or a 3rd party), nil will be returned. # # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, # legacy cookies signed with the old key generator will be transparently upgraded. # # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+. # # Example: # # cookies.encrypted[:discount] = 45 # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/ # # cookies.encrypted[:discount] # => 45 def encrypted @encrypted ||= if @options[:upgrade_legacy_signed_cookies] UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options) else EncryptedCookieJar.new(self, @key_generator, @options) end end # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set. # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores. def signed_or_encrypted @signed_or_encrypted ||= if @options[:secret_key_base].present? encrypted else signed end end end # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream # to the Message{Encryptor,Verifier} allows us to handle the # (de)serialization step within the cookie jar, which gives us the # opportunity to detect and migrate legacy cookies. module VerifyAndUpgradeLegacySignedMessage # :nodoc: def initialize(*args) super @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer) end def verify_and_upgrade_legacy_signed_message(name, signed_message) deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value| self[name] = { value: value } end rescue ActiveSupport::MessageVerifier::InvalidSignature nil end end class CookieJar #:nodoc: include Enumerable, ChainedCookieJars # This regular expression is used to split the levels of a domain. # The top level domain can be any string without a period or # **.**, ***.** style TLDs like co.uk or com.au # # www.example.co.uk gives: # $& => example.co.uk # # example.com gives: # $& => example.com # # lots.of.subdomains.example.local gives: # $& => example.local DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ def self.options_for_env(env) #:nodoc: { signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '', encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '', encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '', secret_token: env[SECRET_TOKEN], secret_key_base: env[SECRET_KEY_BASE], upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?, serializer: env[COOKIES_SERIALIZER], digest: env[COOKIES_DIGEST] } end def self.build(request) env = request.env key_generator = env[GENERATOR_KEY] options = options_for_env env host = request.host secure = request.ssl? new(key_generator, host, secure, options).tap do |hash| hash.update(request.cookies) end end def initialize(key_generator, host = nil, secure = false, options = {}) @key_generator = key_generator @set_cookies = {} @delete_cookies = {} @host = host @secure = secure @options = options @cookies = {} @committed = false end def committed?; @committed; end def commit! @committed = true @set_cookies.freeze @delete_cookies.freeze end def each(&block) @cookies.each(&block) end # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. def [](name) @cookies[name.to_s] end def fetch(name, *args, &block) @cookies.fetch(name.to_s, *args, &block) end def key?(name) @cookies.key?(name.to_s) end alias :has_key? :key? def update(other_hash) @cookies.update other_hash.stringify_keys self end def handle_options(options) #:nodoc: options[:path] ||= "/" if options[:domain] == :all # if there is a provided tld length then we use it otherwise default domain regexp domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP # if host is not ip and matches domain regexp # (ip confirms to domain regexp so we explicitly check for ip) options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp) ".#{$&}" end elsif options[:domain].is_a? Array # if host matches one of the supplied domains without a dot in front of it options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') } end end # Sets the cookie named +name+. The second argument may be the cookie's # value or a hash of options as documented above. def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! value = options[:value] else value = options options = { :value => value } end handle_options(options) if @cookies[name.to_s] != value or options[:expires] @cookies[name.to_s] = value @set_cookies[name.to_s] = options @delete_cookies.delete(name.to_s) end value end # Removes the cookie on the client machine by setting the value to an empty string # and the expiration date in the past. Like []=, you can pass in # an options hash to delete cookies with extra data such as a :path. def delete(name, options = {}) return unless @cookies.has_key? name.to_s options.symbolize_keys! handle_options(options) value = @cookies.delete(name.to_s) @delete_cookies[name.to_s] = options value end # Whether the given cookie is to be deleted by this CookieJar. # Like []=, you can pass in an options hash to test if a # deletion applies to a specific :path, :domain etc. def deleted?(name, options = {}) options.symbolize_keys! handle_options(options) @delete_cookies[name.to_s] == options end # Removes all cookies on the client machine by calling delete for each cookie def clear(options = {}) @cookies.each_key{ |k| delete(k, options) } end def write(headers) @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) } @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) } end def recycle! #:nodoc: @set_cookies = {} @delete_cookies = {} end mattr_accessor :always_write_cookie self.always_write_cookie = false private def write_cookie?(cookie) @secure || !cookie[:secure] || always_write_cookie end end class PermanentCookieJar #:nodoc: include ChainedCookieJars def initialize(parent_jar, key_generator, options = {}) @parent_jar = parent_jar @key_generator = key_generator @options = options end def [](name) @parent_jar[name.to_s] end def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! else options = { :value => options } end options[:expires] = 20.years.from_now @parent_jar[name] = options end end class JsonSerializer # :nodoc: def self.load(value) ActiveSupport::JSON.decode(value) end def self.dump(value) ActiveSupport::JSON.encode(value) end end module SerializedCookieJars # :nodoc: MARSHAL_SIGNATURE = "\x04\x08".freeze protected def needs_migration?(value) @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE) end def serialize(name, value) serializer.dump(value) end def deserialize(name, value) if value if needs_migration?(value) Marshal.load(value).tap do |v| self[name] = { value: v } end else serializer.load(value) end end end def serializer serializer = @options[:serializer] || :marshal case serializer when :marshal Marshal when :json, :hybrid JsonSerializer else serializer end end def digest @options[:digest] || 'SHA1' end end class SignedCookieJar #:nodoc: include ChainedCookieJars include SerializedCookieJars def initialize(parent_jar, key_generator, options = {}) @parent_jar = parent_jar @options = options secret = key_generator.generate_key(@options[:signed_cookie_salt]) @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end def [](name) if signed_message = @parent_jar[name] deserialize name, verify(signed_message) end end def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! options[:value] = @verifier.generate(serialize(name, options[:value])) else options = { :value => @verifier.generate(serialize(name, options)) } end raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE @parent_jar[name] = options end private def verify(signed_message) @verifier.verify(signed_message) rescue ActiveSupport::MessageVerifier::InvalidSignature nil end end # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if # secrets.secret_token and secrets.secret_key_base are both set. It reads # legacy cookies signed with the old dummy key generator and re-saves # them using the new key generator to provide a smooth upgrade path. class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc: include VerifyAndUpgradeLegacySignedMessage def [](name) if signed_message = @parent_jar[name] deserialize(name, verify(signed_message)) || verify_and_upgrade_legacy_signed_message(name, signed_message) end end end class EncryptedCookieJar #:nodoc: include ChainedCookieJars include SerializedCookieJars def initialize(parent_jar, key_generator, options = {}) if ActiveSupport::LegacyKeyGenerator === key_generator raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " + "Read the upgrade documentation to learn more about this new config option." end @parent_jar = parent_jar @options = options secret = key_generator.generate_key(@options[:encrypted_cookie_salt]) sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt]) @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end def [](name) if encrypted_message = @parent_jar[name] deserialize name, decrypt_and_verify(encrypted_message) end end def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! else options = { :value => options } end options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value])) raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE @parent_jar[name] = options end private def decrypt_and_verify(encrypted_message) @encryptor.decrypt_and_verify(encrypted_message) rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage nil end end # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore # instead of EncryptedCookieJar if secrets.secret_token and secrets.secret_key_base # are both set. It reads legacy cookies signed with the old dummy key generator and # encrypts and re-saves them using the new key generator to provide a smooth upgrade path. class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc: include VerifyAndUpgradeLegacySignedMessage def [](name) if encrypted_or_signed_message = @parent_jar[name] deserialize(name, decrypt_and_verify(encrypted_or_signed_message)) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message) end end end def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) if cookie_jar = env['action_dispatch.cookies'] unless cookie_jar.committed? cookie_jar.write(headers) if headers[HTTP_HEADER].respond_to?(:join) headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n") end end end [status, headers, body] end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb000066400000000000000000000067271266740050600266660ustar00rootroot00000000000000require 'action_dispatch/http/request' require 'action_dispatch/middleware/exception_wrapper' require 'action_dispatch/routing/inspector' module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. class DebugExceptions RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__) def initialize(app, routes_app = nil) @app = app @routes_app = routes_app end def call(env) _, headers, body = response = @app.call(env) if headers['X-Cascade'] == 'pass' body.close if body.respond_to?(:close) raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end response rescue Exception => exception raise exception if env['action_dispatch.show_exceptions'] == false render_exception(env, exception) end private def render_exception(env, exception) wrapper = ExceptionWrapper.new(env, exception) log_error(env, wrapper) if env['action_dispatch.show_detailed_exceptions'] request = Request.new(env) traces = wrapper.traces trace_to_show = 'Application Trace' if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error' trace_to_show = 'Full Trace' end if source_to_show = traces[trace_to_show].first source_to_show_id = source_to_show[:id] end template = ActionView::Base.new([RESCUES_TEMPLATE_PATH], request: request, exception: wrapper.exception, traces: traces, show_source_idx: source_to_show_id, trace_to_show: trace_to_show, routes_inspector: routes_inspector(exception), source_extracts: wrapper.source_extracts, line_number: wrapper.line_number, file: wrapper.file ) file = "rescues/#{wrapper.rescue_template}" if request.xhr? body = template.render(template: file, layout: false, formats: [:text]) format = "text/plain" else body = template.render(template: file, layout: 'rescues/layout') format = "text/html" end render(wrapper.status_code, body, format) else raise exception end end def render(status, body, format) [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] end def log_error(env, wrapper) logger = logger(env) return unless logger exception = wrapper.exception trace = wrapper.application_trace trace = wrapper.framework_trace if trace.empty? ActiveSupport::Deprecation.silence do message = "\n#{exception.class} (#{exception.message}):\n" message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) message << " " << trace.join("\n ") logger.fatal("#{message}\n\n") end end def logger(env) env['action_dispatch.logger'] || stderr_logger end def stderr_logger @stderr_logger ||= ActiveSupport::Logger.new($stderr) end def routes_inspector(exception) if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)) ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb000066400000000000000000000104151266740050600270620ustar00rootroot00000000000000require 'action_controller/metal/exceptions' require 'active_support/core_ext/module/attribute_accessors' module ActionDispatch class ExceptionWrapper cattr_accessor :rescue_responses @@rescue_responses = Hash.new(:internal_server_error) @@rescue_responses.merge!( 'ActionController::RoutingError' => :not_found, 'AbstractController::ActionNotFound' => :not_found, 'ActionController::MethodNotAllowed' => :method_not_allowed, 'ActionController::UnknownHttpMethod' => :method_not_allowed, 'ActionController::NotImplemented' => :not_implemented, 'ActionController::UnknownFormat' => :not_acceptable, 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, 'ActionDispatch::ParamsParser::ParseError' => :bad_request, 'ActionController::BadRequest' => :bad_request, 'ActionController::ParameterMissing' => :bad_request ) cattr_accessor :rescue_templates @@rescue_templates = Hash.new('diagnostics') @@rescue_templates.merge!( 'ActionView::MissingTemplate' => 'missing_template', 'ActionController::RoutingError' => 'routing_error', 'AbstractController::ActionNotFound' => 'unknown_action', 'ActionView::Template::Error' => 'template_error' ) attr_reader :env, :exception, :line_number, :file def initialize(env, exception) @env = env @exception = original_exception(exception) expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError) end def rescue_template @@rescue_templates[@exception.class.name] end def status_code self.class.status_code_for_exception(@exception.class.name) end def application_trace clean_backtrace(:silent) end def framework_trace clean_backtrace(:noise) end def full_trace clean_backtrace(:all) end def traces appplication_trace_with_ids = [] framework_trace_with_ids = [] full_trace_with_ids = [] full_trace.each_with_index do |trace, idx| trace_with_id = { id: idx, trace: trace } if application_trace.include?(trace) appplication_trace_with_ids << trace_with_id else framework_trace_with_ids << trace_with_id end full_trace_with_ids << trace_with_id end { "Application Trace" => appplication_trace_with_ids, "Framework Trace" => framework_trace_with_ids, "Full Trace" => full_trace_with_ids } end def self.status_code_for_exception(class_name) Rack::Utils.status_code(@@rescue_responses[class_name]) end def source_extracts backtrace.map do |trace| file, line = trace.split(":") line_number = line.to_i { code: source_fragment(file, line_number), line_number: line_number } end end private def backtrace Array(@exception.backtrace) end def original_exception(exception) if registered_original_exception?(exception) exception.original_exception else exception end end def registered_original_exception?(exception) exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name) end def clean_backtrace(*args) if backtrace_cleaner backtrace_cleaner.clean(backtrace, *args) else backtrace end end def backtrace_cleaner @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner'] end def source_fragment(path, line) return unless Rails.respond_to?(:root) && Rails.root full_path = Rails.root.join(path) if File.exist?(full_path) File.open(full_path, "r") do |file| start = [line - 3, 0].max lines = file.each_line.drop(start).take(6) Hash[*(start+1..(lines.count+start)).zip(lines).flatten] end end end def expand_backtrace @exception.backtrace.unshift( @exception.to_s.split("\n") ).flatten! end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/flash.rb000066400000000000000000000175241266740050600244310ustar00rootroot00000000000000require 'active_support/core_ext/hash/keys' module ActionDispatch class Request < Rack::Request # Access the contents of the flash. Use flash["notice"] to # read a notice you put there or flash["notice"] = "hello" # to put a new one. def flash @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"]) end end # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create # action that sets flash[:notice] = "Post successfully created" before redirecting to a display action that can # then expose the flash to its template. Actually, that exposure is automatically done. # # class PostsController < ActionController::Base # def create # # save post # flash[:notice] = "Post successfully created" # redirect_to @post # end # # def show # # doesn't need to assign the flash notice to the template, that's done automatically # end # end # # show.html.erb # <% if flash[:notice] %> #
<%= flash[:notice] %>
# <% end %> # # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available: # # flash.alert = "You must be logged in" # flash.notice = "Post successfully created" # # This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to # use sanitize helper. # # Just remember: They'll be gone by the time the next action has been performed. # # See docs on the FlashHash class for more details about the flash. class Flash KEY = 'action_dispatch.request.flash_hash'.freeze class FlashNow #:nodoc: attr_accessor :flash def initialize(flash) @flash = flash end def []=(k, v) k = k.to_s @flash[k] = v @flash.discard(k) v end def [](k) @flash[k.to_s] end # Convenience accessor for flash.now[:alert]=. def alert=(message) self[:alert] = message end # Convenience accessor for flash.now[:notice]=. def notice=(message) self[:notice] = message end end class FlashHash include Enumerable def self.from_session_value(value) #:nodoc: flash = case value when FlashHash # Rails 3.1, 3.2 new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used)) when Hash # Rails 4.0 new(value['flashes'], value['discard']) else new end flash.tap(&:sweep) end # Builds a hash containing the discarded values and the hashes # representing the flashes. # If there are no values in @flashes, returns nil. def to_session_value #:nodoc: return nil if empty? {'discard' => @discard.to_a, 'flashes' => @flashes} end def initialize(flashes = {}, discard = []) #:nodoc: @discard = Set.new(stringify_array(discard)) @flashes = flashes.stringify_keys @now = nil end def initialize_copy(other) if other.now_is_loaded? @now = other.now.dup @now.flash = self end super end def []=(k, v) k = k.to_s @discard.delete k @flashes[k] = v end def [](k) @flashes[k.to_s] end def update(h) #:nodoc: @discard.subtract stringify_array(h.keys) @flashes.update h.stringify_keys self end def keys @flashes.keys end def key?(name) @flashes.key? name.to_s end def delete(key) key = key.to_s @discard.delete key @flashes.delete key self end def to_hash @flashes.dup end def empty? @flashes.empty? end def clear @discard.clear @flashes.clear end def each(&block) @flashes.each(&block) end alias :merge! :update def replace(h) #:nodoc: @discard.clear @flashes.replace h.stringify_keys self end # Sets a flash that will not be available to the next action, only to the current. # # flash.now[:message] = "Hello current action" # # This method enables you to use the flash as a central messaging system in your app. # When you need to pass an object to the next action, you use the standard flash assign ([]=). # When you need to pass an object to the current action, you use now, and your object will # vanish when the current action is done. # # Entries set via now are accessed the same way as standard entries: flash['my-key']. # # Also, brings two convenience accessors: # # flash.now.alert = "Beware now!" # # Equivalent to flash.now[:alert] = "Beware now!" # # flash.now.notice = "Good luck now!" # # Equivalent to flash.now[:notice] = "Good luck now!" def now @now ||= FlashNow.new(self) end # Keeps either the entire current flash or a specific flash entry available for the next action: # # flash.keep # keeps the entire flash # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded def keep(k = nil) k = k.to_s if k @discard.subtract Array(k || keys) k ? self[k] : self end # Marks the entire flash or a single flash entry to be discarded by the end of the current action: # # flash.discard # discard the entire flash at the end of the current action # flash.discard(:warning) # discard only the "warning" entry at the end of the current action def discard(k = nil) k = k.to_s if k @discard.merge Array(k || keys) k ? self[k] : self end # Mark for removal entries that were kept, and delete unkept ones. # # This method is called automatically by filters, so you generally don't need to care about it. def sweep #:nodoc: @discard.each { |k| @flashes.delete k } @discard.replace @flashes.keys end # Convenience accessor for flash[:alert]. def alert self[:alert] end # Convenience accessor for flash[:alert]=. def alert=(message) self[:alert] = message end # Convenience accessor for flash[:notice]. def notice self[:notice] end # Convenience accessor for flash[:notice]=. def notice=(message) self[:notice] = message end protected def now_is_loaded? @now end def stringify_array(array) array.map do |item| item.kind_of?(Symbol) ? item.to_s : item end end end def initialize(app) @app = app end def call(env) @app.call(env) ensure session = Request::Session.find(env) || {} flash_hash = env[KEY] if flash_hash && (flash_hash.present? || session.key?('flash')) session["flash"] = flash_hash.to_session_value env[KEY] = flash_hash.dup end if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) session.key?('flash') && session['flash'].nil? session.delete('flash') end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/params_parser.rb000066400000000000000000000031471266740050600261670ustar00rootroot00000000000000require 'active_support/core_ext/hash/conversions' require 'action_dispatch/http/request' require 'active_support/core_ext/hash/indifferent_access' module ActionDispatch class ParamsParser class ParseError < StandardError attr_reader :original_exception def initialize(message, original_exception) super(message) @original_exception = original_exception end end DEFAULT_PARSERS = { Mime::JSON => :json } def initialize(app, parsers = {}) @app, @parsers = app, DEFAULT_PARSERS.merge(parsers) end def call(env) if params = parse_formatted_parameters(env) env["action_dispatch.request.request_parameters"] = params end @app.call(env) end private def parse_formatted_parameters(env) request = Request.new(env) return false if request.content_length.zero? strategy = @parsers[request.content_mime_type] return false unless strategy case strategy when Proc strategy.call(request.raw_post) when :json data = ActiveSupport::JSON.decode(request.raw_post) data = {:_json => data} unless data.is_a?(Hash) Request::Utils.deep_munge(data).with_indifferent_access else false end rescue => e # JSON or Ruby code block errors logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" raise ParseError.new(e.message, e) end def logger(env) env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr) end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/public_exceptions.rb000066400000000000000000000040001266740050600270340ustar00rootroot00000000000000module ActionDispatch # When called, this middleware renders an error page. By default if an HTML # response is expected it will render static error pages from the `/public` # directory. For example when this middleware receives a 500 response it will # render the template found in `/public/500.html`. # If an internationalized locale is set, this middleware will attempt to render # the template in `/public/500..html`. If an internationalized template # is not found it will fall back on `/public/500.html`. # # When a request with a content type other than HTML is made, this middleware # will attempt to convert error information into the appropriate response type. class PublicExceptions attr_accessor :public_path def initialize(public_path) @public_path = public_path end def call(env) status = env["PATH_INFO"][1..-1] request = ActionDispatch::Request.new(env) content_type = request.formats.first body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) } render(status, content_type, body) end private def render(status, content_type, body) format = "to_#{content_type.to_sym}" if content_type if format && body.respond_to?(format) render_format(status, content_type, body.public_send(format)) else render_html(status) end end def render_format(status, content_type, body) [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] end def render_html(status) path = "#{public_path}/#{status}.#{I18n.locale}.html" path = "#{public_path}/#{status}.html" unless (found = File.exist?(path)) if found || File.exist?(path) render_format(status, 'text/html', File.read(path)) else [404, { "X-Cascade" => "pass" }, []] end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/reloader.rb000066400000000000000000000057271266740050600251330ustar00rootroot00000000000000require 'active_support/deprecation/reporting' module ActionDispatch # ActionDispatch::Reloader provides prepare and cleanup callbacks, # intended to assist with code reloading during development. # # Prepare callbacks are run before each request, and cleanup callbacks # after each request. In this respect they are analogs of ActionDispatch::Callback's # before and after callbacks. However, cleanup callbacks are not called until the # request is fully complete -- that is, after #close has been called on # the response body. This is important for streaming responses such as the # following: # # self.response_body = lambda { |response, output| # # code here which refers to application models # } # # Cleanup callbacks will not be called until after the response_body lambda # is evaluated, ensuring that it can refer to application models and other # classes before they are unloaded. # # By default, ActionDispatch::Reloader is included in the middleware stack # only in the development environment; specifically, when +config.cache_classes+ # is false. Callbacks may be registered even when it is not included in the # middleware stack, but are executed only when ActionDispatch::Reloader.prepare! # or ActionDispatch::Reloader.cleanup! are called manually. # class Reloader include ActiveSupport::Callbacks include ActiveSupport::Deprecation::Reporting define_callbacks :prepare define_callbacks :cleanup # Add a prepare callback. Prepare callbacks are run before each request, prior # to ActionDispatch::Callback's before callbacks. def self.to_prepare(*args, &block) unless block_given? warn "to_prepare without a block is deprecated. Please use a block" end set_callback(:prepare, *args, &block) end # Add a cleanup callback. Cleanup callbacks are run after each request is # complete (after #close is called on the response body). def self.to_cleanup(*args, &block) unless block_given? warn "to_cleanup without a block is deprecated. Please use a block" end set_callback(:cleanup, *args, &block) end # Execute all prepare callbacks. def self.prepare! new(nil).prepare! end # Execute all cleanup callbacks. def self.cleanup! new(nil).cleanup! end def initialize(app, condition=nil) @app = app @condition = condition || lambda { true } @validated = true end def call(env) @validated = @condition.call prepare! response = @app.call(env) response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! } response rescue Exception cleanup! raise end def prepare! #:nodoc: run_callbacks :prepare if validated? end def cleanup! #:nodoc: run_callbacks :cleanup if validated? ensure @validated = true end private def validated? #:nodoc: @validated end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/remote_ip.rb000066400000000000000000000201061266740050600253050ustar00rootroot00000000000000require 'ipaddr' module ActionDispatch # This middleware calculates the IP address of the remote client that is # making the request. It does this by checking various headers that could # contain the address, and then picking the last-set address that is not # on the list of trusted IPs. This follows the precedent set by e.g. # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453], # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection] # by @gingerlime. A more detailed explanation of the algorithm is given # at GetIp#calculate_ip. # # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] # requires. Some Rack servers simply drop preceding headers, and only report # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers]. # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn) # then you should test your Rack server to make sure your data is good. # # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. # This middleware assumes that there is at least one proxy sitting around # and setting headers with the client's remote IP address. If you don't use # a proxy, because you are hosted on e.g. Heroku without SSL, any client can # claim to have any IP address by setting the X-Forwarded-For header. If you # care about that, then you need to explicitly drop or ignore those headers # sometime before this middleware runs. class RemoteIp class IpSpoofAttackError < StandardError; end # The default trusted IPs list simply includes IP addresses that are # guaranteed by the IP specification to be private addresses. Those will # not be the ultimate client IP in production, and so are discarded. See # http://en.wikipedia.org/wiki/Private_network for details. TRUSTED_PROXIES = [ "127.0.0.1", # localhost IPv4 "::1", # localhost IPv6 "fc00::/7", # private IPv6 range fc00::/7 "10.0.0.0/8", # private IPv4 range 10.x.x.x "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255 "192.168.0.0/16", # private IPv4 range 192.168.x.x ].map { |proxy| IPAddr.new(proxy) } attr_reader :check_ip, :proxies # Create a new +RemoteIp+ middleware instance. # # The +check_ip_spoofing+ option is on by default. When on, an exception # is raised if it looks like the client is trying to lie about its own IP # address. It makes sense to turn off this check on sites aimed at non-IP # clients (like WAP devices), or behind proxies that set headers in an # incorrect or confusing way (like AWS ELB). # # The +custom_proxies+ argument can take an Array of string, IPAddr, or # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a # single string, IPAddr, or Regexp object is provided, it will be used in # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you # want in the middle (or at the beginning) of the X-Forwarded-For list, # with your proxy servers after it. If your proxies aren't removed, pass # them in via the +custom_proxies+ parameter. That way, the middleware will # ignore those IP addresses, and return the one that you want. def initialize(app, check_ip_spoofing = true, custom_proxies = nil) @app = app @check_ip = check_ip_spoofing @proxies = if custom_proxies.blank? TRUSTED_PROXIES elsif custom_proxies.respond_to?(:any?) custom_proxies else Array(custom_proxies) + TRUSTED_PROXIES end end # Since the IP address may not be needed, we store the object here # without calculating the IP to keep from slowing down the majority of # requests. For those requests that do need to know the IP, the # GetIp#calculate_ip method will calculate the memoized client IP address. def call(env) env["action_dispatch.remote_ip"] = GetIp.new(env, self) @app.call(env) end # The GetIp class exists as a way to defer processing of the request data # into an actual IP address. If the ActionDispatch::Request#remote_ip method # is called, this class will calculate the value and then memoize it. class GetIp def initialize(env, middleware) @env = env @check_ip = middleware.check_ip @proxies = middleware.proxies end # Sort through the various IP address headers, looking for the IP most # likely to be the address of the actual remote client making this # request. # # REMOTE_ADDR will be correct if the request is made directly against the # Ruby process, on e.g. Heroku. When the request is proxied by another # server like HAProxy or NGINX, the IP address that made the original # request will be put in an X-Forwarded-For header. If there are multiple # proxies, that header may contain a list of IPs. Other proxy services # set the Client-Ip header instead, so we check that too. # # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/], # while the first IP in the list is likely to be the "originating" IP, # it could also have been set by the client maliciously. # # In order to find the first address that is (probably) accurate, we # take the list of IPs, remove known and trusted proxies, and then take # the last address left, which was presumably set by one of those proxies. def calculate_ip # Set by the Rack web server, this is a single value. remote_addr = ips_from('REMOTE_ADDR').last # Could be a CSV list and/or repeated headers that were concatenated. client_ips = ips_from('HTTP_CLIENT_IP').reverse forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. # If they are both set, it means that this request passed through two # proxies with incompatible IP header conventions, and there is no way # for us to determine which header is the right one after the fact. # Since we have no idea, we give up and explode. should_check_ip = @check_ip && client_ips.last && forwarded_ips.last if should_check_ip && !forwarded_ips.include?(client_ips.last) # We don't know which came from the proxy, and which from the user raise IpSpoofAttackError, "IP spoofing attack?! " + "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " + "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end # We assume these things about the IP headers: # # - X-Forwarded-For will be a list of IPs, one per proxy, or blank # - Client-Ip is propagated from the outermost proxy, or is blank # - REMOTE_ADDR will be the IP that made the request to Rack ips = [forwarded_ips, client_ips, remote_addr].flatten.compact # If every single IP option is in the trusted list, just return REMOTE_ADDR filter_proxies(ips).first || remote_addr end # Memoizes the value returned by #calculate_ip and returns it for # ActionDispatch::Request to use. def to_s @ip ||= calculate_ip end protected def ips_from(header) # Split the comma-separated list into an array of strings ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] ips.select do |ip| begin # Only return IPs that are valid according to the IPAddr#new method range = IPAddr.new(ip).to_range # we want to make sure nobody is sneaking a netmask in range.begin == range.end rescue ArgumentError nil end end end def filter_proxies(ips) ips.reject do |ip| @proxies.any? { |proxy| proxy === ip } end end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/request_id.rb000066400000000000000000000025521266740050600254730ustar00rootroot00000000000000require 'securerandom' require 'active_support/core_ext/string/access' module ActionDispatch # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header. # # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only. # # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files # from multiple pieces of the stack. class RequestId def initialize(app) @app = app end def call(env) env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id @app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["action_dispatch.request_id"] } end private def external_request_id(env) if request_id = env["HTTP_X_REQUEST_ID"].presence request_id.gsub(/[^\w\-]/, "").first(255) end end def internal_request_id SecureRandom.uuid end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/session/000077500000000000000000000000001266740050600244615ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb000066400000000000000000000044001266740050600300230ustar00rootroot00000000000000require 'rack/utils' require 'rack/request' require 'rack/session/abstract/id' require 'action_dispatch/middleware/cookies' require 'action_dispatch/request/session' module ActionDispatch module Session class SessionRestoreError < StandardError #:nodoc: attr_reader :original_exception def initialize(const_error) @original_exception = const_error super("Session contains objects whose class definition isn't available.\n" + "Remember to require the classes for all objects kept in the session.\n" + "(Original exception: #{const_error.message} [#{const_error.class}])\n") end end module Compatibility def initialize(app, options = {}) options[:key] ||= '_session_id' super end def generate_sid sid = SecureRandom.hex(16) sid.encode!(Encoding::UTF_8) sid end protected def initialize_sid @default_options.delete(:sidbits) @default_options.delete(:secure_random) end end module StaleSessionCheck def load_session(env) stale_session_check! { super } end def extract_session_id(env) stale_session_check! { super } end def stale_session_check! yield rescue ArgumentError => argument_error if argument_error.message =~ %r{undefined class/module ([\w:]*\w)} begin # Note that the regexp does not allow $1 to end with a ':' $1.constantize rescue LoadError, NameError => e raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace end retry else raise end end end module SessionObject # :nodoc: def prepare_session(env) Request::Session.create(self, env, @default_options) end def loaded_session?(session) !session.is_a?(Request::Session) || session.loaded? end end class AbstractStore < Rack::Session::Abstract::ID include Compatibility include StaleSessionCheck include SessionObject private def set_cookie(env, session_id, cookie) request = ActionDispatch::Request.new(env) request.cookie_jar[key] = cookie end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/session/cache_store.rb000066400000000000000000000027541266740050600272750ustar00rootroot00000000000000require 'action_dispatch/middleware/session/abstract_store' module ActionDispatch module Session # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful # if you don't store critical data in your sessions and you don't need them to live for extended periods # of time. class CacheStore < AbstractStore # Create a new store. The cache to use can be passed in the :cache option. If it is # not specified, Rails.cache will be used. def initialize(app, options = {}) @cache = options[:cache] || Rails.cache options[:expire_after] ||= @cache.options[:expires_in] super end # Get a session from the cache. def get_session(env, sid) unless sid and session = @cache.read(cache_key(sid)) sid, session = generate_sid, {} end [sid, session] end # Set a session in the cache. def set_session(env, sid, session, options) key = cache_key(sid) if session @cache.write(key, session, :expires_in => options[:expire_after]) else @cache.delete(key) end sid end # Remove a session from the cache. def destroy_session(env, sid, options) @cache.delete(cache_key(sid)) generate_sid end private # Turn the session id into a cache key. def cache_key(sid) "_session_id:#{sid}" end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb000066400000000000000000000106551266740050600275020ustar00rootroot00000000000000require 'active_support/core_ext/hash/keys' require 'action_dispatch/middleware/session/abstract_store' require 'rack/session/cookie' module ActionDispatch module Session # This cookie-based session store is the Rails default. It is # dramatically faster than the alternatives. # # Sessions typically contain at most a user_id and flash message; both fit # within the 4K cookie size limit. A CookieOverflow exception is raised if # you attempt to store more than 4K of data. # # The cookie jar used for storage is automatically configured to be the # best possible option given your application's configuration. # # If you only have secret_token set, your cookies will be signed, but # not encrypted. This means a user cannot alter their +user_id+ without # knowing your app's secret key, but can easily read their +user_id+. This # was the default for Rails 3 apps. # # If you have secret_key_base set, your cookies will be encrypted. This # goes a step further than signed cookies in that encrypted cookies cannot # be altered or read by users. This is the default starting in Rails 4. # # If you have both secret_token and secret_key base set, your cookies will # be encrypted, and signed cookies generated by Rails 3 will be # transparently read and encrypted to provide a smooth upgrade path. # # Configure your session store in config/initializers/session_store.rb: # # Rails.application.config.session_store :cookie_store, key: '_your_app_session' # # Configure your secret key in config/secrets.yml: # # development: # secret_key_base: 'secret key' # # To generate a secret key for an existing application, run `rake secret`. # # If you are upgrading an existing Rails 3 app, you should leave your # existing secret_token in place and simply add the new secret_key_base. # Note that you should wait to set secret_key_base until you have 100% of # your userbase on Rails 4 and are reasonably sure you will not need to # rollback to Rails 3. This is because cookies signed based on the new # secret_key_base in Rails 4 are not backwards compatible with Rails 3. # You are free to leave your existing secret_token in place, not set the # new secret_key_base, and ignore the deprecation warnings until you are # reasonably sure that your upgrade is otherwise complete. Additionally, # you should take care to make sure you are not relying on the ability to # decode signed cookies generated by your app in external applications or # JavaScript before upgrading. # # Note that changing the secret key will invalidate all existing sessions! class CookieStore < Rack::Session::Abstract::ID include Compatibility include StaleSessionCheck include SessionObject def initialize(app, options={}) super(app, options.merge!(:cookie_only => true)) end def destroy_session(env, session_id, options) new_sid = generate_sid unless options[:drop] # Reset hash and Assign the new session id env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {} new_sid end def load_session(env) stale_session_check! do data = unpacked_cookie_data(env) data = persistent_session_id!(data) [data["session_id"], data] end end private def extract_session_id(env) stale_session_check! do unpacked_cookie_data(env)["session_id"] end end def unpacked_cookie_data(env) env["action_dispatch.request.unsigned_session_cookie"] ||= begin stale_session_check! do if data = get_cookie(env) data.stringify_keys! end data || {} end end end def persistent_session_id!(data, sid=nil) data ||= {} data["session_id"] ||= sid || generate_sid data end def set_session(env, sid, session_data, options) session_data["session_id"] = sid session_data end def set_cookie(env, session_id, cookie) cookie_jar(env)[@key] = cookie end def get_cookie(env) cookie_jar(env)[@key] end def cookie_jar(env) request = ActionDispatch::Request.new(env) request.cookie_jar.signed_or_encrypted end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb000066400000000000000000000010661266740050600301260ustar00rootroot00000000000000require 'action_dispatch/middleware/session/abstract_store' begin require 'rack/session/dalli' rescue LoadError => e $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" raise e end module ActionDispatch module Session class MemCacheStore < Rack::Session::Dalli include Compatibility include StaleSessionCheck include SessionObject def initialize(app, options = {}) options[:expire_after] ||= options[:expires] super end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/show_exceptions.rb000066400000000000000000000043231266740050600265460ustar00rootroot00000000000000require 'action_dispatch/http/request' require 'action_dispatch/middleware/exception_wrapper' module ActionDispatch # This middleware rescues any exception returned by the application # and calls an exceptions app that will wrap it in a format for the end user. # # The exceptions app should be passed as parameter on initialization # of ShowExceptions. Every time there is an exception, ShowExceptions will # store the exception in env["action_dispatch.exception"], rewrite the # PATH_INFO to the exception status code and call the rack app. # # If the application returns a "X-Cascade" pass response, this middleware # will send an empty response as result with the correct status code. # If any exception happens inside the exceptions app, this middleware # catches the exceptions and returns a FAILSAFE_RESPONSE. class ShowExceptions FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' }, ["500 Internal Server Error\n" \ "If you are the administrator of this website, then please read this web " \ "application's log file and/or the web server's log file to find out what " \ "went wrong."]] def initialize(app, exceptions_app) @app = app @exceptions_app = exceptions_app end def call(env) @app.call(env) rescue Exception => exception if env['action_dispatch.show_exceptions'] == false raise exception else render_exception(env, exception) end end private def render_exception(env, exception) wrapper = ExceptionWrapper.new(env, exception) status = wrapper.status_code env["action_dispatch.exception"] = wrapper.exception env["action_dispatch.original_path"] = env["PATH_INFO"] env["PATH_INFO"] = "/#{status}" response = @exceptions_app.call(env) response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response rescue Exception => failsafe_error $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" FAILSAFE_RESPONSE end def pass_response(status) [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []] end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/ssl.rb000066400000000000000000000033661266740050600241340ustar00rootroot00000000000000module ActionDispatch class SSL YEAR = 31536000 def self.default_hsts_options { :expires => YEAR, :subdomains => false } end def initialize(app, options = {}) @app = app @hsts = options.fetch(:hsts, {}) @hsts = {} if @hsts == true @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts @host = options[:host] @port = options[:port] end def call(env) request = Request.new(env) if request.ssl? status, headers, body = @app.call(env) headers.reverse_merge!(hsts_headers) flag_cookies_as_secure!(headers) [status, headers, body] else redirect_to_https(request) end end private def redirect_to_https(request) host = @host || request.host port = @port || request.port location = "https://#{host}" location << ":#{port}" if port != 80 location << request.fullpath headers = { 'Content-Type' => 'text/html', 'Location' => location } [301, headers, []] end # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02 def hsts_headers if @hsts value = "max-age=#{@hsts[:expires].to_i}" value += "; includeSubDomains" if @hsts[:subdomains] { 'Strict-Transport-Security' => value } else {} end end def flag_cookies_as_secure!(headers) if cookies = headers['Set-Cookie'] cookies = cookies.split("\n") headers['Set-Cookie'] = cookies.map { |cookie| if cookie !~ /;\s*secure\s*(;|$)/i "#{cookie}; secure" else cookie end }.join("\n") end end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/stack.rb000066400000000000000000000053511266740050600244340ustar00rootroot00000000000000require "active_support/inflector/methods" require "active_support/dependencies" module ActionDispatch class MiddlewareStack class Middleware attr_reader :args, :block, :name, :classcache def initialize(klass_or_name, *args, &block) @klass = nil if klass_or_name.respond_to?(:name) @klass = klass_or_name @name = @klass.name else @name = klass_or_name.to_s end @classcache = ActiveSupport::Dependencies::Reference @args, @block = args, block end def klass @klass || classcache[@name] end def ==(middleware) case middleware when Middleware klass == middleware.klass when Class klass == middleware else normalize(@name) == normalize(middleware) end end def inspect klass.to_s end def build(app) klass.new(app, *args, &block) end private def normalize(object) object.to_s.strip.sub(/^::/, '') end end include Enumerable attr_accessor :middlewares def initialize(*args) @middlewares = [] yield(self) if block_given? end def each @middlewares.each { |x| yield x } end def size middlewares.size end def last middlewares.last end def [](i) middlewares[i] end def unshift(*args, &block) middleware = self.class::Middleware.new(*args, &block) middlewares.unshift(middleware) end def initialize_copy(other) self.middlewares = other.middlewares.dup end def insert(index, *args, &block) index = assert_index(index, :before) middleware = self.class::Middleware.new(*args, &block) middlewares.insert(index, middleware) end alias_method :insert_before, :insert def insert_after(index, *args, &block) index = assert_index(index, :after) insert(index + 1, *args, &block) end def swap(target, *args, &block) index = assert_index(target, :before) insert(index, *args, &block) middlewares.delete_at(index + 1) end def delete(target) middlewares.delete target end def use(*args, &block) middleware = self.class::Middleware.new(*args, &block) middlewares.push(middleware) end def build(app = nil, &block) app ||= block raise "MiddlewareStack#build requires an app" unless app middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) } end protected def assert_index(index, where) i = index.is_a?(Integer) ? index : middlewares.index(index) raise "No such middleware to insert #{where}: #{index.inspect}" unless i i end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/static.rb000066400000000000000000000075421266740050600246220ustar00rootroot00000000000000require 'rack/utils' require 'active_support/core_ext/uri' module ActionDispatch # This middleware returns a file's contents from disk in the body response. # When initialized it can accept an optional 'Cache-Control' header which # will be set when a response containing a file's contents is delivered. # # This middleware will render the file specified in `env["PATH_INFO"]` # where the base path is in the +root+ directory. For example if the +root+ # is set to `public/` then a request with `env["PATH_INFO"]` of # `assets/application.js` will return a response with contents of a file # located at `public/assets/application.js` if the file exists. If the file # does not exist a 404 "File not Found" response will be returned. class FileHandler def initialize(root, cache_control) @root = root.chomp('/') @compiled_root = /^#{Regexp.escape(root)}/ headers = cache_control && { 'Cache-Control' => cache_control } @file_server = ::Rack::File.new(@root, headers) end def match?(path) path = URI.parser.unescape(path) return false unless valid_path?(path) paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"].map { |v| Rack::Utils.clean_path_info v } if match = paths.detect { |p| path = File.join(@root, p.force_encoding('UTF-8')) begin File.file?(path) && File.readable?(path) rescue SystemCallError false end } return ::Rack::Utils.escape(match) end end def call(env) path = env['PATH_INFO'] gzip_path = gzip_file_path(path) if gzip_path && gzip_encoding_accepted?(env) env['PATH_INFO'] = gzip_path status, headers, body = @file_server.call(env) if status == 304 return [status, headers, body] end headers['Content-Encoding'] = 'gzip' headers['Content-Type'] = content_type(path) else status, headers, body = @file_server.call(env) end headers['Vary'] = 'Accept-Encoding' if gzip_path return [status, headers, body] ensure env['PATH_INFO'] = path end private def ext ::ActionController::Base.default_static_extension end def content_type(path) ::Rack::Mime.mime_type(::File.extname(path), 'text/plain') end def gzip_encoding_accepted?(env) env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i end def gzip_file_path(path) can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/ gzip_path = "#{path}.gz" if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape(gzip_path))) gzip_path else false end end def valid_path?(path) path.valid_encoding? && !path.include?("\0") end end # This middleware will attempt to return the contents of a file's body from # disk in the response. If a file is not found on disk, the request will be # delegated to the application stack. This middleware is commonly initialized # to serve assets from a server's `public/` directory. # # This middleware verifies the path to ensure that only files # living in the root directory can be rendered. A request cannot # produce a directory traversal using this middleware. Only 'GET' and 'HEAD' # requests will result in a file being returned. class Static def initialize(app, path, cache_control=nil) @app = app @file_handler = FileHandler.new(path, cache_control) end def call(env) case env['REQUEST_METHOD'] when 'GET', 'HEAD' path = env['PATH_INFO'].chomp('/') if match = @file_handler.match?(path) env["PATH_INFO"] = match return @file_handler.call(env) end end @app.call(env) end end end rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/000077500000000000000000000000001266740050600247745ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/000077500000000000000000000000001266740050600264455ustar00rootroot00000000000000_request_and_response.html.erb000066400000000000000000000026471266740050600344230ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues<% unless @exception.blamed_files.blank? %> <% if (hide = @exception.blamed_files.length > 8) %> Toggle blamed files <% end %>
><%= @exception.describe_blame %>
<% end %> <% clean_params = @request.filtered_parameters.clone clean_params.delete("action") clean_params.delete("controller") request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") def debug_hash(object) object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") end unless self.class.method_defined?(:debug_hash) %>

Request

Parameters:

<%= request_dump %>

Response

Headers:

<%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %>
_request_and_response.text.erb000066400000000000000000000012331266740050600344310ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues<% clean_params = @request.filtered_parameters.clone clean_params.delete("action") clean_params.delete("controller") request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") def debug_hash(object) object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") end unless self.class.method_defined?(:debug_hash) %> Request parameters <%= request_dump %> Session dump <%= debug_hash @request.session %> Env dump <%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %> Response headers <%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb000066400000000000000000000016411266740050600306000ustar00rootroot00000000000000<% @source_extracts.each_with_index do |source_extract, index| %> <% if source_extract[:code] %>
" id="frame-source-<%=index%>">
Extracted source (around line #<%= source_extract[:line_number] %>):
                <% source_extract[:code].each_key do |line_number| %>
<%= line_number -%>
                <% end %>
              
<% source_extract[:code].each do |line, source| -%>
"><%= source -%>
<% end -%>
<% end %> <% end %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb000066400000000000000000000035421266740050600313430ustar00rootroot00000000000000<% names = @traces.keys %>

Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>

<% names.each do |name| %> <% show = "show('#{name.gsub(/\s/, '-')}');" hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"} %> <%= name %> <%= '|' unless names.last == name %> <% end %> <% @traces.each do |name, trace| %>
<% trace.each do |frame| %><%= frame[:trace] %>
<% end %>
<% end %>
rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb000066400000000000000000000003361266740050600313610ustar00rootroot00000000000000Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %> <% @traces.each do |name, trace| %> <% if trace.any? %> <%= name %> <%= trace.map { |t| t[:trace] }.join("\n") %> <% end %> <% end %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb000066400000000000000000000007571266740050600324220ustar00rootroot00000000000000

<%= @exception.class.to_s %> <% if @request.parameters['controller'] %> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> <% end %>

<%= h @exception.message %>

<%= render template: "rescues/_source" %> <%= render template: "rescues/_trace" %> <%= render template: "rescues/_request_and_response" %>
rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb000066400000000000000000000006141266740050600324320ustar00rootroot00000000000000<%= @exception.class.to_s %><% if @request.parameters['controller'] %> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> <% end %> <%= @exception.message %> <%= render template: "rescues/_source" %> <%= render template: "rescues/_trace" %> <%= render template: "rescues/_request_and_response" %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb000066400000000000000000000054171266740050600304630ustar00rootroot00000000000000 Action Controller: Exception caught <%= yield %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb000066400000000000000000000004101266740050600334410ustar00rootroot00000000000000

Template is missing

<%= h @exception.message %>

<%= render template: "rescues/_source" %> <%= render template: "rescues/_trace" %> <%= render template: "rescues/_request_and_response" %>
rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb000066400000000000000000000000571266740050600334700ustar00rootroot00000000000000Template is missing <%= @exception.message %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb000066400000000000000000000013541266740050600330050ustar00rootroot00000000000000

Routing Error

<%= h @exception.message %>

<% unless @exception.failures.empty? %>

Failure reasons:

    <% @exception.failures.each do |route, reason| %>
  1. <%= route.inspect.delete('\\') %> failed because <%= reason.downcase %>
  2. <% end %>

<% end %> <%= render template: "rescues/_trace" %> <% if @routes_inspector %>

Routes

Routes match in priority from top to bottom

<%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %> <% end %> <%= render template: "rescues/_request_and_response" %>
rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb000066400000000000000000000004641266740050600330260ustar00rootroot00000000000000Routing Error <%= @exception.message %> <% unless @exception.failures.empty? %> Failure reasons: <% @exception.failures.each do |route, reason| %> - <%= route.inspect.delete('\\') %> failed because <%= reason.downcase %> <% end %> <% end %> <%= render template: "rescues/_trace", format: :text %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb000066400000000000000000000011451266740050600331270ustar00rootroot00000000000000

<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>

Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised:

<%= h @exception.message %>
<%= render template: "rescues/_source" %>

<%= @exception.sub_template_message %>

<%= render template: "rescues/_trace" %> <%= render template: "rescues/_request_and_response" %>
rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb000066400000000000000000000007061266740050600331510ustar00rootroot00000000000000<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised: <%= @exception.message %> <%= @exception.sub_template_message %> <%= render template: "rescues/_trace", format: :text %> <%= render template: "rescues/_request_and_response", format: :text %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb000066400000000000000000000001601266740050600331330ustar00rootroot00000000000000

Unknown action

<%= h @exception.message %>

rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb000066400000000000000000000000521266740050600331530ustar00rootroot00000000000000Unknown action <%= @exception.message %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/routes/000077500000000000000000000000001266740050600263155ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb000066400000000000000000000007341266740050600312530ustar00rootroot00000000000000 <% if route[:name].present? %> <%= route[:name] %>_path <% end %> <%= route[:verb] %> <%= route[:path] %> <%= route[:reqs] %> rails-4.2.6/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb000066400000000000000000000134421266740050600312040ustar00rootroot00000000000000<% content_for :style do %> #route_table { margin: 0; border-collapse: collapse; } #route_table thead tr { border-bottom: 2px solid #ddd; } #route_table thead tr.bottom { border-bottom: none; } #route_table thead tr.bottom th { padding: 10px 0; line-height: 15px; } #route_table tbody tr { border-bottom: 1px solid #ddd; } #route_table tbody tr:nth-child(odd) { background: #f2f2f2; } #route_table tbody.exact_matches, #route_table tbody.fuzzy_matches { background-color: LightGoldenRodYellow; border-bottom: solid 2px SlateGrey; } #route_table tbody.exact_matches tr, #route_table tbody.fuzzy_matches tr { background: none; border-bottom: none; } #route_table td { padding: 4px 30px; } #path_search { width: 80%; font-size: inherit; } <% end %> <%= yield %>
Helper HTTP Verb Path Controller#Action
<%# Helper %> <%= link_to "Path", "#", 'data-route-helper' => '_path', title: "Returns a relative path (without the http or domain)" %> / <%= link_to "Url", "#", 'data-route-helper' => '_url', title: "Returns an absolute url (with the http and domain)" %> <%# HTTP Verb %> <%# Path %> <%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %> <%# Controller#action %>
rails-4.2.6/actionpack/lib/action_dispatch/railtie.rb000066400000000000000000000044051266740050600226420ustar00rootroot00000000000000require "action_dispatch" module ActionDispatch class Railtie < Rails::Railtie # :nodoc: config.action_dispatch = ActiveSupport::OrderedOptions.new config.action_dispatch.x_sendfile_header = nil config.action_dispatch.ip_spoofing_check = true config.action_dispatch.show_exceptions = true config.action_dispatch.tld_length = 1 config.action_dispatch.ignore_accept_header = false config.action_dispatch.rescue_templates = { } config.action_dispatch.rescue_responses = { } config.action_dispatch.default_charset = nil config.action_dispatch.rack_cache = false config.action_dispatch.http_auth_salt = 'http authentication' config.action_dispatch.signed_cookie_salt = 'signed cookie' config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie' config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie' config.action_dispatch.perform_deep_munge = true config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block', 'X-Content-Type-Options' => 'nosniff' } config.eager_load_namespaces << ActionDispatch initializer "action_dispatch.configure" do |app| ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses) ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates) config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil? ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie ActionDispatch.test_app = app ActionDispatch::Routing::RouteSet.relative_url_root = app.config.relative_url_root end end end rails-4.2.6/actionpack/lib/action_dispatch/request/000077500000000000000000000000001266740050600223515ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/request/session.rb000066400000000000000000000104131266740050600243600ustar00rootroot00000000000000require 'rack/session/abstract/id' module ActionDispatch class Request < Rack::Request # Session is responsible for lazily loading the session from store. class Session # :nodoc: ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc: ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc: # Singleton object used to determine if an optional param wasn't specified Unspecified = Object.new def self.create(store, env, default_options) session_was = find env session = Request::Session.new(store, env) session.merge! session_was if session_was set(env, session) Options.set(env, Request::Session::Options.new(store, env, default_options)) session end def self.find(env) env[ENV_SESSION_KEY] end def self.set(env, session) env[ENV_SESSION_KEY] = session end class Options #:nodoc: def self.set(env, options) env[ENV_SESSION_OPTIONS_KEY] = options end def self.find(env) env[ENV_SESSION_OPTIONS_KEY] end def initialize(by, env, default_options) @by = by @env = env @delegate = default_options.dup end def [](key) if key == :id @delegate.fetch(key) { @delegate[:id] = @by.send(:extract_session_id, @env) } else @delegate[key] end end def []=(k,v); @delegate[k] = v; end def to_hash; @delegate.dup; end def values_at(*args); @delegate.values_at(*args); end end def initialize(by, env) @by = by @env = env @delegate = {} @loaded = false @exists = nil # we haven't checked yet end def id options[:id] end def options Options.find @env end def destroy clear options = self.options || {} new_sid = @by.send(:destroy_session, @env, options[:id], options) options[:id] = new_sid # Reset session id with a new value or nil # Load the new sid to be written with the response @loaded = false load_for_write! end def [](key) load_for_read! @delegate[key.to_s] end def has_key?(key) load_for_read! @delegate.key?(key.to_s) end alias :key? :has_key? alias :include? :has_key? def keys @delegate.keys end def values @delegate.values end def []=(key, value) load_for_write! @delegate[key.to_s] = value end def clear load_for_write! @delegate.clear end def to_hash load_for_read! @delegate.dup.delete_if { |_,v| v.nil? } end def update(hash) load_for_write! @delegate.update stringify_keys(hash) end def delete(key) load_for_write! @delegate.delete key.to_s end def fetch(key, default=Unspecified, &block) load_for_read! if default == Unspecified @delegate.fetch(key.to_s, &block) else @delegate.fetch(key.to_s, default, &block) end end def inspect if loaded? super else "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>" end end def exists? return @exists unless @exists.nil? @exists = @by.send(:session_exists?, @env) end def loaded? @loaded end def empty? load_for_read! @delegate.empty? end def merge!(other) load_for_write! @delegate.merge!(other) end private def load_for_read! load! if !loaded? && exists? end def load_for_write! load! unless loaded? end def load! id, session = @by.load_session @env options[:id] = id @delegate.replace(stringify_keys(session)) @loaded = true end def stringify_keys(other) other.each_with_object({}) { |(key, value), hash| hash[key.to_s] = value } end end end end rails-4.2.6/actionpack/lib/action_dispatch/request/utils.rb000066400000000000000000000014701266740050600240400ustar00rootroot00000000000000module ActionDispatch class Request < Rack::Request class Utils # :nodoc: mattr_accessor :perform_deep_munge self.perform_deep_munge = true class << self # Remove nils from the params hash def deep_munge(hash, keys = []) return hash unless perform_deep_munge hash.each do |k, v| keys << k case v when Array v.grep(Hash) { |x| deep_munge(x, keys) } v.compact! if v.empty? hash[k] = nil ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys) end when Hash deep_munge(v, keys) end keys.pop end hash end end end end end rails-4.2.6/actionpack/lib/action_dispatch/routing.rb000066400000000000000000000203051266740050600226750ustar00rootroot00000000000000# encoding: UTF-8 require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/regexp' require 'active_support/dependencies/autoload' module ActionDispatch # The routing module provides URL rewriting in native Ruby. It's a way to # redirect incoming requests to controllers and actions. This replaces # mod_rewrite rules. Best of all, Rails' \Routing works with any web server. # Routes are defined in config/routes.rb. # # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # # Rails.application.routes.draw do # Pattern 1 tells some request to go to one place # Pattern 2 tell them to go to another # ... # end # # The following symbols are special: # # :controller maps to your controller name # :action maps to an action with your controllers # # Other names simply map to a parameter as in the case of :id. # # == Resources # # Resource routing allows you to quickly declare all of the common routes # for a given resourceful controller. Instead of declaring separate routes # for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+ # actions, a resourceful route declares them in a single line of code: # # resources :photos # # Sometimes, you have a resource that clients always look up without # referencing an ID. A common example, /profile always shows the profile of # the currently logged in user. In this case, you can use a singular resource # to map /profile (rather than /profile/:id) to the show action. # # resource :profile # # It's common to have resources that are logically children of other # resources: # # resources :magazines do # resources :ads # end # # You may wish to organize groups of controllers under a namespace. Most # commonly, you might group a number of administrative controllers under # an +admin+ namespace. You would place these controllers under the # app/controllers/admin directory, and you can group them together # in your router: # # namespace "admin" do # resources :posts, :comments # end # # Alternately, you can add prefixes to your path without using a separate # directory by using +scope+. +scope+ takes additional options which # apply to all enclosed routes. # # scope path: "/cpanel", as: 'admin' do # resources :posts, :comments # end # # For more, see Routing::Mapper::Resources#resources, # Routing::Mapper::Scoping#namespace, and # Routing::Mapper::Scoping#scope. # # == Non-resourceful routes # # For routes that don't fit the resources mold, you can use the HTTP helper # methods get, post, patch, put and delete. # # get 'post/:id' => 'posts#show' # post 'post/:id' => 'posts#create_comment' # # If your route needs to respond to more than one HTTP method (or all methods) then using the # :via option on match is preferable. # # match 'post/:id' => 'posts#show', via: [:get, :post] # # Now, if you POST to /posts/:id, it will route to the create_comment action. A GET on the same # URL will route to the show action. # # == Named routes # # Routes can be named by passing an :as option, # allowing for easy reference within your source as +name_of_route_url+ # for the full URL and +name_of_route_path+ for the URI path. # # Example: # # # In routes.rb # get '/login' => 'accounts#login', as: 'login' # # # With render, redirect_to, tests, etc. # redirect_to login_url # # Arguments can be passed as well. # # redirect_to show_item_path(id: 25) # # Use root as a shorthand to name a route for the root path "/". # # # In routes.rb # root to: 'blogs#index' # # # would recognize http://www.example.com/ as # params = { controller: 'blogs', action: 'index' } # # # and provide these named routes # root_url # => 'http://www.example.com/' # root_path # => '/' # # Note: when using +controller+, the route is simply named after the # method you call on the block parameter rather than map. # # # In routes.rb # controller :blog do # get 'blog/show' => :list # get 'blog/delete' => :delete # get 'blog/edit/:id' => :edit # end # # # provides named routes for show, delete, and edit # link_to @article.title, show_path(id: @article.id) # # == Pretty URLs # # Routes can generate pretty URLs. For example: # # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: { # year: /\d{4}/, # month: /\d{1,2}/, # day: /\d{1,2}/ # } # # Using the route above, the URL "http://localhost:3000/articles/2005/11/06" # maps to # # params = {year: '2005', month: '11', day: '06'} # # == Regular Expressions and parameters # You can specify a regular expression to define a format for a parameter. # # controller 'geocode' do # get 'geocode/:postalcode' => :show, constraints: { # postalcode: /\d{5}(-\d{4})?/ # } # # Constraints can include the 'ignorecase' and 'extended syntax' regular # expression modifiers: # # controller 'geocode' do # get 'geocode/:postalcode' => :show, constraints: { # postalcode: /hx\d\d\s\d[a-z]{2}/i # } # end # # controller 'geocode' do # get 'geocode/:postalcode' => :show, constraints: { # postalcode: /# Postcode format # \d{5} #Prefix # (-\d{4})? #Suffix # /x # } # end # # Using the multiline modifier will raise an +ArgumentError+. # Encoding regular expression modifiers are silently ignored. The # match will always use the default encoding or ASCII. # # == External redirects # # You can redirect any path to another path using the redirect helper in your router: # # get "/stories" => redirect("/posts") # # == Unicode character routes # # You can specify unicode character routes in your router: # # get "ã“ã‚“ã«ã¡ã¯" => "welcome#index" # # == Routing to Rack Applications # # Instead of a String, like posts#index, which corresponds to the # index action in the PostsController, you can specify any Rack application # as the endpoint for a matcher: # # get "/application.js" => Sprockets # # == Reloading routes # # You can reload routes if you feel you must: # # Rails.application.reload_routes! # # This will clear all named routes and reload routes.rb if the file has been modified from # last load. To absolutely force reloading, use reload!. # # == Testing Routes # # The two main methods for testing your routes: # # === +assert_routing+ # # def test_movie_route_properly_splits # opts = {controller: "plugin", action: "checkout", id: "2"} # assert_routing "plugin/checkout/2", opts # end # # +assert_routing+ lets you test whether or not the route properly resolves into options. # # === +assert_recognizes+ # # def test_route_has_options # opts = {controller: "plugin", action: "show", id: "12"} # assert_recognizes opts, "/plugins/show/12" # end # # Note the subtle difference between the two: +assert_routing+ tests that # a URL fits options while +assert_recognizes+ tests that a URL # breaks into parameters properly. # # In tests you can simply pass the URL or named route to +get+ or +post+. # # def send_to_jail # get '/jail' # assert_response :success # assert_template "jail/front" # end # # def goes_to_login # get login_url # #... # end # # == View a list of all your routes # # rake routes # # Target specific controllers by prefixing the command with CONTROLLER=x. # module Routing extend ActiveSupport::Autoload autoload :Mapper autoload :RouteSet autoload :RoutesProxy autoload :UrlFor autoload :PolymorphicRoutes SEPARATORS = %w( / . ? ) #:nodoc: HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc: end end rails-4.2.6/actionpack/lib/action_dispatch/routing/000077500000000000000000000000001266740050600223505ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/routing/endpoint.rb000066400000000000000000000003461266740050600245200ustar00rootroot00000000000000module ActionDispatch module Routing class Endpoint # :nodoc: def dispatcher?; false; end def redirect?; false; end def matches?(req); true; end def app; self; end end end end rails-4.2.6/actionpack/lib/action_dispatch/routing/inspector.rb000066400000000000000000000132741266740050600247120ustar00rootroot00000000000000require 'delegate' require 'active_support/core_ext/string/strip' module ActionDispatch module Routing class RouteWrapper < SimpleDelegator def endpoint app.dispatcher? ? "#{controller}##{action}" : rack_app.inspect end def constraints requirements.except(:controller, :action) end def rack_app app.app end def verb super.source.gsub(/[$^]/, '') end def path super.spec.to_s end def name super.to_s end def regexp __getobj__.path.to_regexp end def json_regexp str = regexp.inspect. sub('\\A' , '^'). sub('\\Z' , '$'). sub('\\z' , '$'). sub(/^\// , ''). sub(/\/[a-z]*$/ , ''). gsub(/\(\?#.+\)/ , ''). gsub(/\(\?-\w+:/ , '('). gsub(/\s/ , '') Regexp.new(str).source end def reqs @reqs ||= begin reqs = endpoint reqs += " #{constraints}" unless constraints.empty? reqs end end def controller requirements[:controller] || ':controller' end def action requirements[:action] || ':action' end def internal? controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z} end def engine? rack_app.respond_to?(:routes) end end ## # This class is just used for displaying route information when someone # executes `rake routes` or looks at the RoutingError page. # People should not use this class. class RoutesInspector # :nodoc: def initialize(routes) @engines = {} @routes = routes end def format(formatter, filter = nil) routes_to_display = filter_routes(filter) routes = collect_routes(routes_to_display) if routes.none? formatter.no_routes return formatter.result end formatter.header routes formatter.section routes @engines.each do |name, engine_routes| formatter.section_title "Routes for #{name}" formatter.section engine_routes end formatter.result end private def filter_routes(filter) if filter @routes.select { |route| route.defaults[:controller] == filter } else @routes end end def collect_routes(routes) routes.collect do |route| RouteWrapper.new(route) end.reject do |route| route.internal? end.collect do |route| collect_engine_routes(route) { name: route.name, verb: route.verb, path: route.path, reqs: route.reqs, regexp: route.json_regexp } end end def collect_engine_routes(route) name = route.endpoint return unless route.engine? return if @engines[name] routes = route.rack_app.routes if routes.is_a?(ActionDispatch::Routing::RouteSet) @engines[name] = collect_routes(routes.routes) end end end class ConsoleFormatter def initialize @buffer = [] end def result @buffer.join("\n") end def section_title(title) @buffer << "\n#{title}:" end def section(routes) @buffer << draw_section(routes) end def header(routes) @buffer << draw_header(routes) end def no_routes @buffer << <<-MESSAGE.strip_heredoc You don't have any routes defined! Please add some routes in config/routes.rb. For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html. MESSAGE end private def draw_section(routes) header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length) name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) routes.map do |r| "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" end end def draw_header(routes) name_width, verb_width, path_width = widths(routes) "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action" end def widths(routes) [routes.map { |r| r[:name].length }.max || 0, routes.map { |r| r[:verb].length }.max || 0, routes.map { |r| r[:path].length }.max || 0] end end class HtmlTableFormatter def initialize(view) @view = view @buffer = [] end def section_title(title) @buffer << %(#{title}) end def section(routes) @buffer << @view.render(partial: "routes/route", collection: routes) end # the header is part of the HTML page, so we don't construct it here. def header(routes) end def no_routes @buffer << <<-MESSAGE.strip_heredoc

You don't have any routes defined!

MESSAGE end def result @view.raw @view.render(layout: "routes/table") { @view.raw @buffer.join("\n") } end end end end rails-4.2.6/actionpack/lib/action_dispatch/routing/mapper.rb000066400000000000000000002034471266740050600241730ustar00rootroot00000000000000require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/string/filters' require 'active_support/inflector' require 'action_dispatch/routing/redirection' require 'action_dispatch/routing/endpoint' require 'active_support/deprecation' module ActionDispatch module Routing class Mapper URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] class Constraints < Endpoint #:nodoc: attr_reader :app, :constraints def initialize(app, constraints, dispatcher_p) # Unwrap Constraints objects. I don't actually think it's possible # to pass a Constraints object to this constructor, but there were # multiple places that kept testing children of this object. I # *think* they were just being defensive, but I have no idea. if app.is_a?(self.class) constraints += app.constraints app = app.app end @dispatcher = dispatcher_p @app, @constraints, = app, constraints end def dispatcher?; @dispatcher; end def matches?(req) @constraints.all? do |constraint| (constraint.respond_to?(:matches?) && constraint.matches?(req)) || (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req))) end end def serve(req) return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req) if dispatcher? @app.serve req else @app.call req.env end end private def constraint_args(constraint, request) constraint.arity == 1 ? [request] : [request.path_parameters, request] end end class Mapping #:nodoc: ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} attr_reader :requirements, :conditions, :defaults attr_reader :to, :default_controller, :default_action, :as, :anchor def self.build(scope, set, path, as, options) options = scope[:options].merge(options) if scope[:options] options.delete :only options.delete :except options.delete :shallow_path options.delete :shallow_prefix options.delete :shallow defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {} new scope, set, path, defaults, as, options end def initialize(scope, set, path, defaults, as, options) @requirements, @conditions = {}, {} @defaults = defaults @set = set @to = options.delete :to @default_controller = options.delete(:controller) || scope[:controller] @default_action = options.delete(:action) || scope[:action] @as = as @anchor = options.delete :anchor formatted = options.delete :format via = Array(options.delete(:via) { [] }) options_constraints = options.delete :constraints path = normalize_path! path, formatted ast = path_ast path path_params = path_params ast options = normalize_options!(options, formatted, path_params, ast, scope[:module]) split_constraints(path_params, scope[:constraints]) if scope[:constraints] constraints = constraints(options, path_params) split_constraints path_params, constraints @blocks = blocks(options_constraints, scope[:blocks]) if options_constraints.is_a?(Hash) split_constraints path_params, options_constraints options_constraints.each do |key, default| if URL_OPTIONS.include?(key) && (String === default || Fixnum === default) @defaults[key] ||= default end end end normalize_format!(formatted) @conditions[:path_info] = path @conditions[:parsed_path_info] = ast add_request_method(via, @conditions) normalize_defaults!(options) end def to_route [ app(@blocks), conditions, requirements, defaults, as, anchor ] end private def normalize_path!(path, format) path = Mapper.normalize_path(path) if format == true "#{path}.:format" elsif optional_format?(path, format) "#{path}(.:format)" else path end end def optional_format?(path, format) format != false && !path.include?(':format') && !path.end_with?('/') end def normalize_options!(options, formatted, path_params, path_ast, modyoule) # Add a constraint for wildcard route to make it non-greedy and match the # optional format part of the route by default if formatted != false path_ast.grep(Journey::Nodes::Star) do |node| options[node.name.to_sym] ||= /.+?/ end end if path_params.include?(:controller) raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule # Add a default constraint for :controller path segments that matches namespaced # controllers with default routes like :controller/:action/:id(.:format), e.g: # GET /admin/products/show/1 # => { controller: 'admin/products', action: 'show', id: '1' } options[:controller] ||= /.+?/ end if to.respond_to? :call options else to_endpoint = split_to to controller = to_endpoint[0] || default_controller action = to_endpoint[1] || default_action controller = add_controller_module(controller, modyoule) options.merge! check_controller_and_action(path_params, controller, action) end end def split_constraints(path_params, constraints) constraints.each_pair do |key, requirement| if path_params.include?(key) || key == :controller verify_regexp_requirement(requirement) if requirement.is_a?(Regexp) @requirements[key] = requirement else @conditions[key] = requirement end end end def normalize_format!(formatted) if formatted == true @requirements[:format] ||= /.+/ elsif Regexp === formatted @requirements[:format] = formatted @defaults[:format] = nil elsif String === formatted @requirements[:format] = Regexp.compile(formatted) @defaults[:format] = formatted end end def verify_regexp_requirement(requirement) if requirement.source =~ ANCHOR_CHARACTERS_REGEX raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" end if requirement.multiline? raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" end end def normalize_defaults!(options) options.each_pair do |key, default| unless Regexp === default @defaults[key] = default end end end def verify_callable_constraint(callable_constraint) unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?) raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?" end end def add_request_method(via, conditions) return if via == [:all] if via.empty? msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \ "If you want to expose your action to GET, use `get` in the router:\n" \ " Instead of: match \"controller#action\"\n" \ " Do: get \"controller#action\"" raise ArgumentError, msg end conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase } end def app(blocks) if to.respond_to?(:call) Constraints.new(to, blocks, false) elsif blocks.any? Constraints.new(dispatcher(defaults), blocks, true) else dispatcher(defaults) end end def check_controller_and_action(path_params, controller, action) hash = check_part(:controller, controller, path_params, {}) do |part| translate_controller(part) { message = "'#{part}' is not a supported controller name. This can lead to potential routing problems." message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use" raise ArgumentError, message } end check_part(:action, action, path_params, hash) { |part| part.is_a?(Regexp) ? part : part.to_s } end def check_part(name, part, path_params, hash) if part hash[name] = yield(part) else unless path_params.include?(name) message = "Missing :#{name} key on routes definition, please check your routes." raise ArgumentError, message end end hash end def split_to(to) case to when Symbol ActiveSupport::Deprecation.warn(<<-MSG.squish) Defining a route where `to` is a symbol is deprecated. Please change `to: :#{to}` to `action: :#{to}`. MSG [nil, to.to_s] when /#/ then to.split('#') when String ActiveSupport::Deprecation.warn(<<-MSG.squish) Defining a route where `to` is a controller without an action is deprecated. Please change `to: '#{to}'` to `controller: '#{to}'`. MSG [to, nil] else [] end end def add_controller_module(controller, modyoule) if modyoule && !controller.is_a?(Regexp) if controller =~ %r{\A/} controller[1..-1] else [modyoule, controller].compact.join("/") end else controller end end def translate_controller(controller) return controller if Regexp === controller return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/ yield end def blocks(options_constraints, scope_blocks) if options_constraints && !options_constraints.is_a?(Hash) verify_callable_constraint(options_constraints) [options_constraints] else scope_blocks || [] end end def constraints(options, path_params) constraints = {} required_defaults = [] options.each_pair do |key, option| if Regexp === option constraints[key] = option else required_defaults << key unless path_params.include?(key) end end @conditions[:required_defaults] = required_defaults constraints end def path_params(ast) ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym } end def path_ast(path) parser = Journey::Parser.new parser.parse path end def dispatcher(defaults) @set.dispatcher defaults end end # Invokes Journey::Router::Utils.normalize_path and ensure that # (:locale) becomes (/:locale) instead of /(:locale). Except # for root cases, where the latter is the correct one. def self.normalize_path(path) path = Journey::Router::Utils.normalize_path(path) path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^)]+\)$} path end def self.normalize_name(name) normalize_path(name)[1..-1].tr("/", "_") end module Base # You can specify what Rails should route "/" to with the root method: # # root to: 'pages#main' # # For options, see +match+, as +root+ uses it internally. # # You can also pass a string which will expand # # root 'pages#main' # # You should put the root route at the top of config/routes.rb, # because this means it will be matched first. As this is the most popular route # of most Rails applications, this is beneficial. def root(options = {}) match '/', { :as => :root, :via => :get }.merge!(options) end # Matches a url pattern to one or more routes. # # You should not use the +match+ method in your router # without specifying an HTTP method. # # If you want to expose your action to both GET and POST, use: # # # sets :controller, :action and :id in params # match ':controller/:action/:id', via: [:get, :post] # # Note that +:controller+, +:action+ and +:id+ are interpreted as url # query parameters and thus available through +params+ in an action. # # If you want to expose your action to GET, use +get+ in the router: # # Instead of: # # match ":controller/:action/:id" # # Do: # # get ":controller/:action/:id" # # Two of these symbols are special, +:controller+ maps to the controller # and +:action+ to the controller's action. A pattern can also map # wildcard segments (globs) to params: # # get 'songs/*category/:title', to: 'songs#show' # # # 'songs/rock/classic/stairway-to-heaven' sets # # params[:category] = 'rock/classic' # # params[:title] = 'stairway-to-heaven' # # To match a wildcard parameter, it must have a name assigned to it. # Without a variable name to attach the glob parameter to, the route # can't be parsed. # # When a pattern points to an internal route, the route's +:action+ and # +:controller+ should be set in options or hash shorthand. Examples: # # match 'photos/:id' => 'photos#show', via: :get # match 'photos/:id', to: 'photos#show', via: :get # match 'photos/:id', controller: 'photos', action: 'show', via: :get # # A pattern can also point to a +Rack+ endpoint i.e. anything that # responds to +call+: # # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get # match 'photos/:id', to: PhotoRackApp, via: :get # # Yes, controller actions are just rack endpoints # match 'photos/:id', to: PhotosController.action(:show), via: :get # # Because requesting various HTTP verbs with a single action has security # implications, you must either specify the actions in # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers] # instead +match+ # # === Options # # Any options not seen here are passed on as params with the url. # # [:controller] # The route's controller. # # [:action] # The route's action. # # [:param] # Overrides the default resource identifier +:id+ (name of the # dynamic segment used to generate the routes). # You can access that segment from your controller using # params[<:param>]. # # [:path] # The path prefix for the routes. # # [:module] # The namespace for :controller. # # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get # # => Sekret::PostsController # # See Scoping#namespace for its scope equivalent. # # [:as] # The name used to generate routing helpers. # # [:via] # Allowed HTTP verb(s) for route. # # match 'path', to: 'c#a', via: :get # match 'path', to: 'c#a', via: [:get, :post] # match 'path', to: 'c#a', via: :all # # [:to] # Points to a +Rack+ endpoint. Can be an object that responds to # +call+ or a string representing a controller's action. # # match 'path', to: 'controller#action', via: :get # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get # match 'path', to: RackApp, via: :get # # [:on] # Shorthand for wrapping routes in a specific RESTful context. Valid # values are +:member+, +:collection+, and +:new+. Only use within # resource(s) block. For example: # # resource :bar do # match 'foo', to: 'c#a', on: :member, via: [:get, :post] # end # # Is equivalent to: # # resource :bar do # member do # match 'foo', to: 'c#a', via: [:get, :post] # end # end # # [:constraints] # Constrains parameters with a hash of regular expressions # or an object that responds to matches?. In addition, constraints # other than path can also be specified with any object # that responds to === (eg. String, Array, Range, etc.). # # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get # # match 'json_only', constraints: { format: 'json' }, via: :get # # class Whitelist # def matches?(request) request.remote_ip == '1.2.3.4' end # end # match 'path', to: 'c#a', constraints: Whitelist.new, via: :get # # See Scoping#constraints for more examples with its scope # equivalent. # # [:defaults] # Sets defaults for parameters # # # Sets params[:format] to 'jpg' by default # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get # # See Scoping#defaults for its scope equivalent. # # [:anchor] # Boolean to anchor a match pattern. Default is true. When set to # false, the pattern matches any request prefixed with the given path. # # # Matches any request starting with 'path' # match 'path', to: 'c#a', anchor: false, via: :get # # [:format] # Allows you to specify the default value for optional +format+ # segment or disable it by supplying +false+. def match(path, options=nil) end # Mount a Rack-based application to be used within the application. # # mount SomeRackApp, at: "some_route" # # Alternatively: # # mount(SomeRackApp => "some_route") # # For options, see +match+, as +mount+ uses it internally. # # All mounted applications come with routing helpers to access them. # These are named after the class specified, so for the above example # the helper is either +some_rack_app_path+ or +some_rack_app_url+. # To customize this helper's name, use the +:as+ option: # # mount(SomeRackApp => "some_route", as: "exciting") # # This will generate the +exciting_path+ and +exciting_url+ helpers # which can be used to navigate to this mounted app. def mount(app, options = nil) if options path = options.delete(:at) else unless Hash === app raise ArgumentError, "must be called with mount point" end options = app app, path = options.find { |k, _| k.respond_to?(:call) } options.delete(app) if app end raise "A rack application must be specified" unless path rails_app = rails_app? app options[:as] ||= app_name(app, rails_app) target_as = name_for_action(options[:as], path) options[:via] ||= :all match(path, options.merge(:to => app, :anchor => false, :format => false)) define_generate_prefix(app, target_as) if rails_app self end def default_url_options=(options) @set.default_url_options = options end alias_method :default_url_options, :default_url_options= def with_default_scope(scope, &block) scope(scope) do instance_exec(&block) end end # Query if the following named route was already defined. def has_named_route?(name) @set.named_routes.routes[name.to_sym] end private def rails_app?(app) app.is_a?(Class) && app < Rails::Railtie end def app_name(app, rails_app) if rails_app app.railtie_name elsif app.is_a?(Class) class_name = app.name ActiveSupport::Inflector.underscore(class_name).tr("/", "_") end end def define_generate_prefix(app, name) _route = @set.named_routes.get name _routes = @set app.routes.define_mounted_helper(name) app.routes.extend Module.new { def optimize_routes_generation?; false; end define_method :find_script_name do |options| if options.key? :script_name super(options) else prefix_options = options.slice(*_route.segment_keys) prefix_options[:relative_url_root] = ''.freeze # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } _routes.url_helpers.send("#{name}_path", prefix_options) end end } end end module HttpHelpers # Define a route that only recognizes HTTP GET. # For supported arguments, see match[rdoc-ref:Base#match] # # get 'bacon', to: 'food#bacon' def get(*args, &block) map_method(:get, args, &block) end # Define a route that only recognizes HTTP POST. # For supported arguments, see match[rdoc-ref:Base#match] # # post 'bacon', to: 'food#bacon' def post(*args, &block) map_method(:post, args, &block) end # Define a route that only recognizes HTTP PATCH. # For supported arguments, see match[rdoc-ref:Base#match] # # patch 'bacon', to: 'food#bacon' def patch(*args, &block) map_method(:patch, args, &block) end # Define a route that only recognizes HTTP PUT. # For supported arguments, see match[rdoc-ref:Base#match] # # put 'bacon', to: 'food#bacon' def put(*args, &block) map_method(:put, args, &block) end # Define a route that only recognizes HTTP DELETE. # For supported arguments, see match[rdoc-ref:Base#match] # # delete 'broccoli', to: 'food#broccoli' def delete(*args, &block) map_method(:delete, args, &block) end private def map_method(method, args, &block) options = args.extract_options! options[:via] = method match(*args, options, &block) self end end # You may wish to organize groups of controllers under a namespace. # Most commonly, you might group a number of administrative controllers # under an +admin+ namespace. You would place these controllers under # the app/controllers/admin directory, and you can group them # together in your router: # # namespace "admin" do # resources :posts, :comments # end # # This will create a number of routes for each of the posts and comments # controller. For Admin::PostsController, Rails will create: # # GET /admin/posts # GET /admin/posts/new # POST /admin/posts # GET /admin/posts/1 # GET /admin/posts/1/edit # PATCH/PUT /admin/posts/1 # DELETE /admin/posts/1 # # If you want to route /posts (without the prefix /admin) to # Admin::PostsController, you could use # # scope module: "admin" do # resources :posts # end # # or, for a single case # # resources :posts, module: "admin" # # If you want to route /admin/posts to +PostsController+ # (without the Admin:: module prefix), you could use # # scope "/admin" do # resources :posts # end # # or, for a single case # # resources :posts, path: "/admin/posts" # # In each of these cases, the named routes remain the same as if you did # not use scope. In the last case, the following paths map to # +PostsController+: # # GET /admin/posts # GET /admin/posts/new # POST /admin/posts # GET /admin/posts/1 # GET /admin/posts/1/edit # PATCH/PUT /admin/posts/1 # DELETE /admin/posts/1 module Scoping # Scopes a set of routes to the given default options. # # Take the following route definition as an example: # # scope path: ":account_id", as: "account" do # resources :projects # end # # This generates helpers such as +account_projects_path+, just like +resources+ does. # The difference here being that the routes generated are like /:account_id/projects, # rather than /accounts/:account_id/projects. # # === Options # # Takes same options as Base#match and Resources#resources. # # # route /posts (without the prefix /admin) to Admin::PostsController # scope module: "admin" do # resources :posts # end # # # prefix the posts resource's requests with '/admin' # scope path: "/admin" do # resources :posts # end # # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+ # scope as: "sekret" do # resources :posts # end def scope(*args) options = args.extract_options!.dup scope = {} options[:path] = args.flatten.join('/') if args.any? options[:constraints] ||= {} unless nested_scope? options[:shallow_path] ||= options[:path] if options.key?(:path) options[:shallow_prefix] ||= options[:as] if options.key?(:as) end if options[:constraints].is_a?(Hash) defaults = options[:constraints].select do |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) end (options[:defaults] ||= {}).reverse_merge!(defaults) else block, options[:constraints] = options[:constraints], {} end @scope.options.each do |option| if option == :blocks value = block elsif option == :options value = options else value = options.delete(option) end if value scope[option] = send("merge_#{option}_scope", @scope[option], value) end end @scope = @scope.new scope yield self ensure @scope = @scope.parent end # Scopes routes to a specific controller # # controller "food" do # match "bacon", action: "bacon" # end def controller(controller, options={}) options[:controller] = controller scope(options) { yield } end # Scopes routes to a specific namespace. For example: # # namespace :admin do # resources :posts # end # # This generates the following routes: # # admin_posts GET /admin/posts(.:format) admin/posts#index # admin_posts POST /admin/posts(.:format) admin/posts#create # new_admin_post GET /admin/posts/new(.:format) admin/posts#new # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit # admin_post GET /admin/posts/:id(.:format) admin/posts#show # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy # # === Options # # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ # options all default to the name of the namespace. # # For options, see Base#match. For +:shallow_path+ option, see # Resources#resources. # # # accessible through /sekret/posts rather than /admin/posts # namespace :admin, path: "sekret" do # resources :posts # end # # # maps to Sekret::PostsController rather than Admin::PostsController # namespace :admin, module: "sekret" do # resources :posts # end # # # generates +sekret_posts_path+ rather than +admin_posts_path+ # namespace :admin, as: "sekret" do # resources :posts # end def namespace(path, options = {}) path = path.to_s defaults = { module: path, path: options.fetch(:path, path), as: options.fetch(:as, path), shallow_path: options.fetch(:path, path), shallow_prefix: options.fetch(:as, path) } scope(defaults.merge!(options)) { yield } end # === Parameter Restriction # Allows you to constrain the nested routes based on a set of rules. # For instance, in order to change the routes to allow for a dot character in the +id+ parameter: # # constraints(id: /\d+\.\d+/) do # resources :posts # end # # Now routes such as +/posts/1+ will no longer be valid, but +/posts/1.1+ will be. # The +id+ parameter must match the constraint passed in for this example. # # You may use this to also restrict other parameters: # # resources :posts do # constraints(post_id: /\d+\.\d+/) do # resources :comments # end # end # # === Restricting based on IP # # Routes can also be constrained to an IP or a certain range of IP addresses: # # constraints(ip: /192\.168\.\d+\.\d+/) do # resources :posts # end # # Any user connecting from the 192.168.* range will be able to see this resource, # where as any user connecting outside of this range will be told there is no such route. # # === Dynamic request matching # # Requests to routes can be constrained based on specific criteria: # # constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do # resources :iphones # end # # You are able to move this logic out into a class if it is too complex for routes. # This class must have a +matches?+ method defined on it which either returns +true+ # if the user should be given access to that route, or +false+ if the user should not. # # class Iphone # def self.matches?(request) # request.env["HTTP_USER_AGENT"] =~ /iPhone/ # end # end # # An expected place for this code would be +lib/constraints+. # # This class is then used like this: # # constraints(Iphone) do # resources :iphones # end def constraints(constraints = {}) scope(:constraints => constraints) { yield } end # Allows you to set default parameters for a route, such as this: # defaults id: 'home' do # match 'scoped_pages/(:id)', to: 'pages#show' # end # Using this, the +:id+ parameter here will default to 'home'. def defaults(defaults = {}) scope(:defaults => defaults) { yield } end private def merge_path_scope(parent, child) #:nodoc: Mapper.normalize_path("#{parent}/#{child}") end def merge_shallow_path_scope(parent, child) #:nodoc: Mapper.normalize_path("#{parent}/#{child}") end def merge_as_scope(parent, child) #:nodoc: parent ? "#{parent}_#{child}" : child end def merge_shallow_prefix_scope(parent, child) #:nodoc: parent ? "#{parent}_#{child}" : child end def merge_module_scope(parent, child) #:nodoc: parent ? "#{parent}/#{child}" : child end def merge_controller_scope(parent, child) #:nodoc: child end def merge_action_scope(parent, child) #:nodoc: child end def merge_path_names_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end def merge_constraints_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end def merge_defaults_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end def merge_blocks_scope(parent, child) #:nodoc: merged = parent ? parent.dup : [] merged << child if child merged end def merge_options_scope(parent, child) #:nodoc: (parent || {}).except(*override_keys(child)).merge!(child) end def merge_shallow_scope(parent, child) #:nodoc: child ? true : false end def override_keys(child) #:nodoc: child.key?(:only) || child.key?(:except) ? [:only, :except] : [] end end # Resource routing allows you to quickly declare all of the common routes # for a given resourceful controller. Instead of declaring separate routes # for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+ # actions, a resourceful route declares them in a single line of code: # # resources :photos # # Sometimes, you have a resource that clients always look up without # referencing an ID. A common example, /profile always shows the profile of # the currently logged in user. In this case, you can use a singular resource # to map /profile (rather than /profile/:id) to the show action. # # resource :profile # # It's common to have resources that are logically children of other # resources: # # resources :magazines do # resources :ads # end # # You may wish to organize groups of controllers under a namespace. Most # commonly, you might group a number of administrative controllers under # an +admin+ namespace. You would place these controllers under the # app/controllers/admin directory, and you can group them together # in your router: # # namespace "admin" do # resources :posts, :comments # end # # By default the +:id+ parameter doesn't accept dots. If you need to # use dots as part of the +:id+ parameter add a constraint which # overrides this restriction, e.g: # # resources :articles, id: /[^\/]+/ # # This allows any character other than a slash as part of your +:id+. # module Resources # CANONICAL_ACTIONS holds all actions that does not need a prefix or # a path appended since they fit properly in their scope level. VALID_ON_OPTIONS = [:new, :collection, :member] RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns] CANONICAL_ACTIONS = %w(index create new show update destroy) class Resource #:nodoc: attr_reader :controller, :path, :options, :param def initialize(entities, options = {}) @name = entities.to_s @path = (options[:path] || @name).to_s @controller = (options[:controller] || @name).to_s @as = options[:as] @param = (options[:param] || :id).to_sym @options = options @shallow = false end def default_actions [:index, :create, :new, :show, :update, :destroy, :edit] end def actions if only = @options[:only] Array(only).map(&:to_sym) elsif except = @options[:except] default_actions - Array(except).map(&:to_sym) else default_actions end end def name @as || @name end def plural @plural ||= name.to_s end def singular @singular ||= name.to_s.singularize end alias :member_name :singular # Checks for uncountable plurals, and appends "_index" if the plural # and singular form are the same. def collection_name singular == plural ? "#{plural}_index" : plural end def resource_scope { :controller => controller } end alias :collection_scope :path def member_scope "#{path}/:#{param}" end alias :shallow_scope :member_scope def new_scope(new_path) "#{path}/#{new_path}" end def nested_param :"#{singular}_#{param}" end def nested_scope "#{path}/:#{nested_param}" end def shallow=(value) @shallow = value end def shallow? @shallow end end class SingletonResource < Resource #:nodoc: def initialize(entities, options) super @as = nil @controller = (options[:controller] || plural).to_s @as = options[:as] end def default_actions [:show, :create, :update, :destroy, :new, :edit] end def plural @plural ||= name.to_s.pluralize end def singular @singular ||= name.to_s end alias :member_name :singular alias :collection_name :singular alias :member_scope :path alias :nested_scope :path end def resources_path_names(options) @scope[:path_names].merge!(options) end # Sometimes, you have a resource that clients always look up without # referencing an ID. A common example, /profile always shows the # profile of the currently logged in user. In this case, you can use # a singular resource to map /profile (rather than /profile/:id) to # the show action: # # resource :profile # # creates six different routes in your application, all mapping to # the +Profiles+ controller (note that the controller is named after # the plural): # # GET /profile/new # POST /profile # GET /profile # GET /profile/edit # PATCH/PUT /profile # DELETE /profile # # === Options # Takes same options as +resources+. def resource(*resources, &block) options = resources.extract_options!.dup if apply_common_behavior_for(:resource, resources, options, &block) return self end resource_scope(:resource, SingletonResource.new(resources.pop, options)) do yield if block_given? concerns(options[:concerns]) if options[:concerns] collection do post :create end if parent_resource.actions.include?(:create) new do get :new end if parent_resource.actions.include?(:new) set_member_mappings_for_resource end self end # In Rails, a resourceful route provides a mapping between HTTP verbs # and URLs and controller actions. By convention, each action also maps # to particular CRUD operations in a database. A single entry in the # routing file, such as # # resources :photos # # creates seven different routes in your application, all mapping to # the +Photos+ controller: # # GET /photos # GET /photos/new # POST /photos # GET /photos/:id # GET /photos/:id/edit # PATCH/PUT /photos/:id # DELETE /photos/:id # # Resources can also be nested infinitely by using this block syntax: # # resources :photos do # resources :comments # end # # This generates the following comments routes: # # GET /photos/:photo_id/comments # GET /photos/:photo_id/comments/new # POST /photos/:photo_id/comments # GET /photos/:photo_id/comments/:id # GET /photos/:photo_id/comments/:id/edit # PATCH/PUT /photos/:photo_id/comments/:id # DELETE /photos/:photo_id/comments/:id # # === Options # Takes same options as Base#match as well as: # # [:path_names] # Allows you to change the segment component of the +edit+ and +new+ actions. # Actions not specified are not changed. # # resources :posts, path_names: { new: "brand_new" } # # The above example will now change /posts/new to /posts/brand_new # # [:path] # Allows you to change the path prefix for the resource. # # resources :posts, path: 'postings' # # The resource and all segments will now route to /postings instead of /posts # # [:only] # Only generate routes for the given actions. # # resources :cows, only: :show # resources :cows, only: [:show, :index] # # [:except] # Generate all routes except for the given actions. # # resources :cows, except: :show # resources :cows, except: [:show, :index] # # [:shallow] # Generates shallow routes for nested resource(s). When placed on a parent resource, # generates shallow routes for all nested resources. # # resources :posts, shallow: true do # resources :comments # end # # Is the same as: # # resources :posts do # resources :comments, except: [:show, :edit, :update, :destroy] # end # resources :comments, only: [:show, :edit, :update, :destroy] # # This allows URLs for resources that otherwise would be deeply nested such # as a comment on a blog post like /posts/a-long-permalink/comments/1234 # to be shortened to just /comments/1234. # # [:shallow_path] # Prefixes nested shallow routes with the specified path. # # scope shallow_path: "sekret" do # resources :posts do # resources :comments, shallow: true # end # end # # The +comments+ resource here will have the following routes generated for it: # # post_comments GET /posts/:post_id/comments(.:format) # post_comments POST /posts/:post_id/comments(.:format) # new_post_comment GET /posts/:post_id/comments/new(.:format) # edit_comment GET /sekret/comments/:id/edit(.:format) # comment GET /sekret/comments/:id(.:format) # comment PATCH/PUT /sekret/comments/:id(.:format) # comment DELETE /sekret/comments/:id(.:format) # # [:shallow_prefix] # Prefixes nested shallow route names with specified prefix. # # scope shallow_prefix: "sekret" do # resources :posts do # resources :comments, shallow: true # end # end # # The +comments+ resource here will have the following routes generated for it: # # post_comments GET /posts/:post_id/comments(.:format) # post_comments POST /posts/:post_id/comments(.:format) # new_post_comment GET /posts/:post_id/comments/new(.:format) # edit_sekret_comment GET /comments/:id/edit(.:format) # sekret_comment GET /comments/:id(.:format) # sekret_comment PATCH/PUT /comments/:id(.:format) # sekret_comment DELETE /comments/:id(.:format) # # [:format] # Allows you to specify the default value for optional +format+ # segment or disable it by supplying +false+. # # === Examples # # # routes call Admin::PostsController # resources :posts, module: "admin" # # # resource actions are at /admin/posts. # resources :posts, path: "admin/posts" def resources(*resources, &block) options = resources.extract_options!.dup if apply_common_behavior_for(:resources, resources, options, &block) return self end resource_scope(:resources, Resource.new(resources.pop, options)) do yield if block_given? concerns(options[:concerns]) if options[:concerns] collection do get :index if parent_resource.actions.include?(:index) post :create if parent_resource.actions.include?(:create) end new do get :new end if parent_resource.actions.include?(:new) set_member_mappings_for_resource end self end # To add a route to the collection: # # resources :photos do # collection do # get 'search' # end # end # # This will enable Rails to recognize paths such as /photos/search # with GET, and route to the search action of +PhotosController+. It will also # create the search_photos_url and search_photos_path # route helpers. def collection unless resource_scope? raise ArgumentError, "can't use collection outside resource(s) scope" end with_scope_level(:collection) do scope(parent_resource.collection_scope) do yield end end end # To add a member route, add a member block into the resource block: # # resources :photos do # member do # get 'preview' # end # end # # This will recognize /photos/1/preview with GET, and route to the # preview action of +PhotosController+. It will also create the # preview_photo_url and preview_photo_path helpers. def member unless resource_scope? raise ArgumentError, "can't use member outside resource(s) scope" end with_scope_level(:member) do if shallow? shallow_scope(parent_resource.member_scope) { yield } else scope(parent_resource.member_scope) { yield } end end end def new unless resource_scope? raise ArgumentError, "can't use new outside resource(s) scope" end with_scope_level(:new) do scope(parent_resource.new_scope(action_path(:new))) do yield end end end def nested unless resource_scope? raise ArgumentError, "can't use nested outside resource(s) scope" end with_scope_level(:nested) do if shallow? && shallow_nesting_depth >= 1 shallow_scope(parent_resource.nested_scope, nested_options) { yield } else scope(parent_resource.nested_scope, nested_options) { yield } end end end # See ActionDispatch::Routing::Mapper::Scoping#namespace def namespace(path, options = {}) if resource_scope? nested { super } else super end end def shallow scope(:shallow => true) do yield end end def shallow? parent_resource.instance_of?(Resource) && @scope[:shallow] end # match 'path' => 'controller#action' # match 'path', to: 'controller#action' # match 'path', 'otherpath', on: :member, via: :get def match(path, *rest) if rest.empty? && Hash === path options = path path, to = options.find { |name, _value| name.is_a?(String) } case to when Symbol options[:action] = to when String if to =~ /#/ options[:to] = to else options[:controller] = to end else options[:to] = to end options.delete(path) paths = [path] else options = rest.pop || {} paths = [path] + rest end options[:anchor] = true unless options.key?(:anchor) if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) raise ArgumentError, "Unknown scope #{on.inspect} given to :on" end if @scope[:controller] && @scope[:action] options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" end paths.each do |_path| route_options = options.dup route_options[:path] ||= _path if _path.is_a?(String) path_without_format = _path.to_s.sub(/\(\.:format\)$/, '') if using_match_shorthand?(path_without_format, route_options) route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1') route_options[:to].tr!("-", "_") end decomposed_match(_path, route_options) end self end def using_match_shorthand?(path, options) path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$} end def decomposed_match(path, options) # :nodoc: if on = options.delete(:on) send(on) { decomposed_match(path, options) } else case @scope.scope_level when :resources nested { decomposed_match(path, options) } when :resource member { decomposed_match(path, options) } else add_route(path, options) end end end def add_route(action, options) # :nodoc: path = path_for_action(action, options.delete(:path)) raise ArgumentError, "path is required" if path.blank? action = action.to_s.dup if action =~ /^[\w\-\/]+$/ options[:action] ||= action.tr('-', '_') unless action.include?("/") else action = nil end as = if !options.fetch(:as, true) # if it's set to nil or false options.delete(:as) else name_for_action(options.delete(:as), action) end mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options) app, conditions, requirements, defaults, as, anchor = mapping.to_route @set.add_route(app, conditions, requirements, defaults, as, anchor) end def root(path, options={}) if path.is_a?(String) options[:to] = path elsif path.is_a?(Hash) and options.empty? options = path else raise ArgumentError, "must be called with a path and/or options" end if @scope.resources? with_scope_level(:root) do scope(parent_resource.path) do super(options) end end else super(options) end end protected def parent_resource #:nodoc: @scope[:scope_level_resource] end def apply_common_behavior_for(method, resources, options, &block) #:nodoc: if resources.length > 1 resources.each { |r| send(method, r, options, &block) } return true end if options.delete(:shallow) shallow do send(method, resources.pop, options, &block) end return true end if resource_scope? nested { send(method, resources.pop, options, &block) } return true end options.keys.each do |k| (options[:constraints] ||= {})[k] = options.delete(k) if options[k].is_a?(Regexp) end scope_options = options.slice!(*RESOURCE_OPTIONS) unless scope_options.empty? scope(scope_options) do send(method, resources.pop, options, &block) end return true end unless action_options?(options) options.merge!(scope_action_options) if scope_action_options? end false end def action_options?(options) #:nodoc: options[:only] || options[:except] end def scope_action_options? #:nodoc: @scope[:options] && (@scope[:options][:only] || @scope[:options][:except]) end def scope_action_options #:nodoc: @scope[:options].slice(:only, :except) end def resource_scope? #:nodoc: @scope.resource_scope? end def resource_method_scope? #:nodoc: @scope.resource_method_scope? end def nested_scope? #:nodoc: @scope.nested? end def with_exclusive_scope begin @scope = @scope.new(:as => nil, :path => nil) with_scope_level(:exclusive) do yield end ensure @scope = @scope.parent end end def with_scope_level(kind) @scope = @scope.new_level(kind) yield ensure @scope = @scope.parent end def resource_scope(kind, resource) #:nodoc: resource.shallow = @scope[:shallow] @scope = @scope.new(:scope_level_resource => resource) @nesting.push(resource) with_scope_level(kind) do scope(parent_resource.resource_scope) { yield } end ensure @nesting.pop @scope = @scope.parent end def nested_options #:nodoc: options = { :as => parent_resource.member_name } options[:constraints] = { parent_resource.nested_param => param_constraint } if param_constraint? options end def nesting_depth #:nodoc: @nesting.size end def shallow_nesting_depth #:nodoc: @nesting.select(&:shallow?).size end def param_constraint? #:nodoc: @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) end def param_constraint #:nodoc: @scope[:constraints][parent_resource.param] end def canonical_action?(action) #:nodoc: resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) end def shallow_scope(path, options = {}) #:nodoc: scope = { :as => @scope[:shallow_prefix], :path => @scope[:shallow_path] } @scope = @scope.new scope scope(path, options) { yield } ensure @scope = @scope.parent end def path_for_action(action, path) #:nodoc: if path.blank? && canonical_action?(action) @scope[:path].to_s else "#{@scope[:path]}/#{action_path(action, path)}" end end def action_path(name, path = nil) #:nodoc: name = name.to_sym if name.is_a?(String) path || @scope[:path_names][name] || name.to_s end def prefix_name_for_action(as, action) #:nodoc: if as prefix = as elsif !canonical_action?(action) prefix = action end if prefix && prefix != '/' && !prefix.empty? Mapper.normalize_name prefix.to_s.tr('-', '_') end end def name_for_action(as, action) #:nodoc: prefix = prefix_name_for_action(as, action) name_prefix = @scope[:as] if parent_resource return nil unless as || action collection_name = parent_resource.collection_name member_name = parent_resource.member_name end name = @scope.action_name(name_prefix, prefix, collection_name, member_name) if candidate = name.compact.join("_").presence # If a name was not explicitly given, we check if it is valid # and return nil in case it isn't. Otherwise, we pass the invalid name # forward so the underlying router engine treats it and raises an exception. if as.nil? candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate) else candidate end end end def set_member_mappings_for_resource member do get :edit if parent_resource.actions.include?(:edit) get :show if parent_resource.actions.include?(:show) if parent_resource.actions.include?(:update) patch :update put :update end delete :destroy if parent_resource.actions.include?(:destroy) end end end # Routing Concerns allow you to declare common routes that can be reused # inside others resources and routes. # # concern :commentable do # resources :comments # end # # concern :image_attachable do # resources :images, only: :index # end # # These concerns are used in Resources routing: # # resources :messages, concerns: [:commentable, :image_attachable] # # or in a scope or namespace: # # namespace :posts do # concerns :commentable # end module Concerns # Define a routing concern using a name. # # Concerns may be defined inline, using a block, or handled by # another object, by passing that object as the second parameter. # # The concern object, if supplied, should respond to call, # which will receive two parameters: # # * The current mapper # * A hash of options which the concern object may use # # Options may also be used by concerns defined in a block by accepting # a block parameter. So, using a block, you might do something as # simple as limit the actions available on certain resources, passing # standard resource options through the concern: # # concern :commentable do |options| # resources :comments, options # end # # resources :posts, concerns: :commentable # resources :archived_posts do # # Don't allow comments on archived posts # concerns :commentable, only: [:index, :show] # end # # Or, using a callable object, you might implement something more # specific to your application, which would be out of place in your # routes file. # # # purchasable.rb # class Purchasable # def initialize(defaults = {}) # @defaults = defaults # end # # def call(mapper, options = {}) # options = @defaults.merge(options) # mapper.resources :purchases # mapper.resources :receipts # mapper.resources :returns if options[:returnable] # end # end # # # routes.rb # concern :purchasable, Purchasable.new(returnable: true) # # resources :toys, concerns: :purchasable # resources :electronics, concerns: :purchasable # resources :pets do # concerns :purchasable, returnable: false # end # # Any routing helpers can be used inside a concern. If using a # callable, they're accessible from the Mapper that's passed to # call. def concern(name, callable = nil, &block) callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) } @concerns[name] = callable end # Use the named concerns # # resources :posts do # concerns :commentable # end # # concerns also work in any routes helper that you want to use: # # namespace :posts do # concerns :commentable # end def concerns(*args) options = args.extract_options! args.flatten.each do |name| if concern = @concerns[name] concern.call(self, options) else raise ArgumentError, "No concern named #{name} was found!" end end end end class Scope # :nodoc: OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, :controller, :action, :path_names, :constraints, :shallow, :blocks, :defaults, :options] RESOURCE_SCOPES = [:resource, :resources] RESOURCE_METHOD_SCOPES = [:collection, :member, :new] attr_reader :parent, :scope_level def initialize(hash, parent = {}, scope_level = nil) @hash = hash @parent = parent @scope_level = scope_level end def nested? scope_level == :nested end def resources? scope_level == :resources end def resource_method_scope? RESOURCE_METHOD_SCOPES.include? scope_level end def action_name(name_prefix, prefix, collection_name, member_name) case scope_level when :nested [name_prefix, prefix] when :collection [prefix, name_prefix, collection_name] when :new [prefix, :new, name_prefix, member_name] when :member [prefix, name_prefix, member_name] when :root [name_prefix, collection_name, prefix] else [name_prefix, member_name, prefix] end end def resource_scope? RESOURCE_SCOPES.include? scope_level end def options OPTIONS end def new(hash) self.class.new hash, self, scope_level end def new_level(level) self.class.new(self, self, level) end def fetch(key, &block) @hash.fetch(key, &block) end def [](key) @hash.fetch(key) { @parent[key] } end def []=(k,v) @hash[k] = v end end def initialize(set) #:nodoc: @set = set @scope = Scope.new({ :path_names => @set.resources_path_names }) @concerns = {} @nesting = [] end include Base include HttpHelpers include Redirection include Scoping include Concerns include Resources end end end rails-4.2.6/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb000066400000000000000000000262241266740050600266510ustar00rootroot00000000000000require 'action_controller/model_naming' module ActionDispatch module Routing # Polymorphic URL helpers are methods for smart resolution to a named route call when # given an Active Record model instance. They are to be used in combination with # ActionController::Resources. # # These methods are useful when you want to generate correct URL or path to a RESTful # resource without having to know the exact type of the record in question. # # Nested resources and/or namespaces are also supported, as illustrated in the example: # # polymorphic_url([:admin, @article, @comment]) # # results in: # # admin_article_comment_url(@article, @comment) # # == Usage within the framework # # Polymorphic URL helpers are used in a number of places throughout the \Rails framework: # # * url_for, so you can use it with a record as the argument, e.g. # url_for(@article); # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write # form_for(@article) without having to specify :url parameter for the form # action; # * redirect_to (which, in fact, uses url_for) so you can write # redirect_to(post) in your controllers; # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs # for feed entries. # # == Prefixed polymorphic helpers # # In addition to polymorphic_url and polymorphic_path methods, a # number of prefixed helpers are available as a shorthand to action: "..." # in options. Those are: # # * edit_polymorphic_url, edit_polymorphic_path # * new_polymorphic_url, new_polymorphic_path # # Example usage: # # edit_polymorphic_path(@post) # => "/posts/1/edit" # polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf" # # == Usage with mounted engines # # If you are using a mounted engine and you need to use a polymorphic_url # pointing at the engine's routes, pass in the engine's route proxy as the first # argument to the method. For example: # # polymorphic_url([blog, @post]) # calls blog.post_path(@post) # form_for([blog, @post]) # => "/blog/posts/1" # module PolymorphicRoutes include ActionController::ModelNaming # Constructs a call to a named RESTful route for the given record and returns the # resulting URL string. For example: # # # calls post_url(post) # polymorphic_url(post) # => "http://example.com/posts/1" # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" # polymorphic_url(Comment) # => "http://example.com/comments" # # ==== Options # # * :action - Specifies the action prefix for the named route: # :new or :edit. Default is no prefix. # * :routing_type - Allowed values are :path or :url. # Default is :url. # # Also includes all the options from url_for. These include such # things as :anchor or :trailing_slash. Example usage # is given below: # # polymorphic_url([blog, post], anchor: 'my_anchor') # # => "http://example.com/blogs/1/posts/1#my_anchor" # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" # # For all of these options, see the documentation for url_for. # # ==== Functionality # # # an Article record # polymorphic_url(record) # same as article_url(record) # # # a Comment record # polymorphic_url(record) # same as comment_url(record) # # # it recognizes new records and maps to the collection # record = Comment.new # polymorphic_url(record) # same as comments_url() # # # the class of a record will also map to the collection # polymorphic_url(Comment) # same as comments_url() # def polymorphic_url(record_or_hash_or_array, options = {}) if Hash === record_or_hash_or_array options = record_or_hash_or_array.merge(options) record = options.delete :id return polymorphic_url record, options end opts = options.dup action = opts.delete :action type = opts.delete(:routing_type) || :url HelperMethodBuilder.polymorphic_method self, record_or_hash_or_array, action, type, opts end # Returns the path component of a URL for the given record. It uses # polymorphic_url with routing_type: :path. def polymorphic_path(record_or_hash_or_array, options = {}) if Hash === record_or_hash_or_array options = record_or_hash_or_array.merge(options) record = options.delete :id return polymorphic_path record, options end opts = options.dup action = opts.delete :action type = :path HelperMethodBuilder.polymorphic_method self, record_or_hash_or_array, action, type, opts end %w(edit new).each do |action| module_eval <<-EOT, __FILE__, __LINE__ + 1 def #{action}_polymorphic_url(record_or_hash, options = {}) polymorphic_url_for_action("#{action}", record_or_hash, options) end def #{action}_polymorphic_path(record_or_hash, options = {}) polymorphic_path_for_action("#{action}", record_or_hash, options) end EOT end private def polymorphic_url_for_action(action, record_or_hash, options) polymorphic_url(record_or_hash, options.merge(:action => action)) end def polymorphic_path_for_action(action, record_or_hash, options) polymorphic_path(record_or_hash, options.merge(:action => action)) end class HelperMethodBuilder # :nodoc: CACHE = { 'path' => {}, 'url' => {} } def self.get(action, type) type = type.to_s CACHE[type].fetch(action) { build action, type } end def self.url; CACHE['url'.freeze][nil]; end def self.path; CACHE['path'.freeze][nil]; end def self.build(action, type) prefix = action ? "#{action}_" : "" suffix = type if action.to_s == 'new' HelperMethodBuilder.singular prefix, suffix else HelperMethodBuilder.plural prefix, suffix end end def self.singular(prefix, suffix) new(->(name) { name.singular_route_key }, prefix, suffix) end def self.plural(prefix, suffix) new(->(name) { name.route_key }, prefix, suffix) end def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options) builder = get action, type case record_or_hash_or_array when Array record_or_hash_or_array = record_or_hash_or_array.compact if record_or_hash_or_array.empty? raise ArgumentError, "Nil location provided. Can't build URI." end if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) recipient = record_or_hash_or_array.shift end method, args = builder.handle_list record_or_hash_or_array when String, Symbol method, args = builder.handle_string record_or_hash_or_array when Class method, args = builder.handle_class record_or_hash_or_array when nil raise ArgumentError, "Nil location provided. Can't build URI." else method, args = builder.handle_model record_or_hash_or_array end if options.empty? recipient.send(method, *args) else recipient.send(method, *args, options) end end attr_reader :suffix, :prefix def initialize(key_strategy, prefix, suffix) @key_strategy = key_strategy @prefix = prefix @suffix = suffix end def handle_string(record) [get_method_for_string(record), []] end def handle_string_call(target, str) target.send get_method_for_string str end def handle_class(klass) [get_method_for_class(klass), []] end def handle_class_call(target, klass) target.send get_method_for_class klass end def handle_model(record) args = [] model = record.to_model name = if model.persisted? args << model model.model_name.singular_route_key else @key_strategy.call model.model_name end named_route = prefix + "#{name}_#{suffix}" [named_route, args] end def handle_model_call(target, model) method, args = handle_model model target.send(method, *args) end def handle_list(list) record_list = list.dup record = record_list.pop args = [] route = record_list.map { |parent| case parent when Symbol, String parent.to_s when Class args << parent parent.model_name.singular_route_key else args << parent.to_model parent.to_model.model_name.singular_route_key end } route << case record when Symbol, String record.to_s when Class @key_strategy.call record.model_name else model = record.to_model if model.persisted? args << model model.model_name.singular_route_key else @key_strategy.call model.model_name end end route << suffix named_route = prefix + route.join("_") [named_route, args] end private def get_method_for_class(klass) name = @key_strategy.call klass.model_name prefix + "#{name}_#{suffix}" end def get_method_for_string(str) prefix + "#{str}_#{suffix}" end [nil, 'new', 'edit'].each do |action| CACHE['url'][action] = build action, 'url' CACHE['path'][action] = build action, 'path' end end end end end rails-4.2.6/actionpack/lib/action_dispatch/routing/redirection.rb000066400000000000000000000145171266740050600252140ustar00rootroot00000000000000require 'action_dispatch/http/request' require 'active_support/core_ext/uri' require 'active_support/core_ext/array/extract_options' require 'rack/utils' require 'action_controller/metal/exceptions' require 'action_dispatch/routing/endpoint' module ActionDispatch module Routing class Redirect < Endpoint # :nodoc: attr_reader :status, :block def initialize(status, block) @status = status @block = block end def redirect?; true; end def call(env) serve Request.new env end def serve(req) req.check_path_parameters! uri = URI.parse(path(req.path_parameters, req)) unless uri.host if relative_path?(uri.path) uri.path = "#{req.script_name}/#{uri.path}" elsif uri.path.empty? uri.path = req.script_name.empty? ? "/" : req.script_name end end uri.scheme ||= req.scheme uri.host ||= req.host uri.port ||= req.port unless req.standard_port? body = %(You are being redirected.) headers = { 'Location' => uri.to_s, 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s } [ status, headers, [body] ] end def path(params, request) block.call params, request end def inspect "redirect(#{status})" end private def relative_path?(path) path && !path.empty? && path[0] != '/' end def escape(params) Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] end def escape_fragment(params) Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }] end def escape_path(params) Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }] end end class PathRedirect < Redirect URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/ def path(params, request) if block.match(URL_PARTS) path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1 query = interpolation_required?($2, params) ? $2 % escape(params) : $2 fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3 "#{path}#{query}#{fragment}" else interpolation_required?(block, params) ? block % escape(params) : block end end def inspect "redirect(#{status}, #{block})" end private def interpolation_required?(string, params) !params.empty? && string && string.match(/%\{\w*\}/) end end class OptionRedirect < Redirect # :nodoc: alias :options :block def path(params, request) url_options = { :protocol => request.protocol, :host => request.host, :port => request.optional_port, :path => request.path, :params => request.query_parameters }.merge! options if !params.empty? && url_options[:path].match(/%\{\w*\}/) url_options[:path] = (url_options[:path] % escape_path(params)) end unless options[:host] || options[:domain] if relative_path?(url_options[:path]) url_options[:path] = "/#{url_options[:path]}" url_options[:script_name] = request.script_name elsif url_options[:path].empty? url_options[:path] = request.script_name.empty? ? "/" : "" url_options[:script_name] = request.script_name end end ActionDispatch::Http::URL.url_for url_options end def inspect "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})" end end module Redirection # Redirect any path to another path: # # get "/stories" => redirect("/posts") # # You can also use interpolation in the supplied redirect argument: # # get 'docs/:article', to: redirect('/wiki/%{article}') # # Note that if you return a path without a leading slash then the url is prefixed with the # current SCRIPT_NAME environment variable. This is typically '/' but may be different in # a mounted engine or where the application is deployed to a subdirectory of a website. # # Alternatively you can use one of the other syntaxes: # # The block version of redirect allows for the easy encapsulation of any logic associated with # the redirect in question. Either the params and request are supplied as arguments, or just # params, depending of how many arguments your block accepts. A string is required as a # return value. # # get 'jokes/:number', to: redirect { |params, request| # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp") # "http://#{request.host_with_port}/#{path}" # } # # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass # the block to +get+ instead of +redirect+. Use { ... } instead. # # The options version of redirect allows you to supply only the parts of the url which need # to change, it also supports interpolation of the path similar to the first example. # # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}') # get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}') # # Finally, an object which responds to call can be supplied to redirect, allowing you to reuse # common redirect routes. The call method must accept two arguments, params and request, and return # a string. # # get 'accounts/:name' => redirect(SubdomainRedirector.new('api')) # def redirect(*args, &block) options = args.extract_options! status = options.delete(:status) || 301 path = args.shift return OptionRedirect.new(status, options) if options.any? return PathRedirect.new(status, path) if String === path block = path if path.respond_to? :call raise ArgumentError, "redirection argument not supported" unless block Redirect.new status, block end end end end rails-4.2.6/actionpack/lib/action_dispatch/routing/route_set.rb000066400000000000000000000673301266740050600247170ustar00rootroot00000000000000require 'action_dispatch/journey' require 'forwardable' require 'active_support/concern' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/string/filters' require 'action_controller/metal/exceptions' require 'action_dispatch/http/request' require 'action_dispatch/routing/endpoint' module ActionDispatch module Routing # :stopdoc: class RouteSet # Since the router holds references to many parts of the system # like engines, controllers and the application itself, inspecting # the route set can actually be really slow, therefore we default # alias inspect to to_s. alias inspect to_s mattr_accessor :relative_url_root class Dispatcher < Routing::Endpoint def initialize(defaults) @defaults = defaults end def dispatcher?; true; end def serve(req) req.check_path_parameters! params = req.path_parameters prepare_params!(params) # Just raise undefined constant errors if a controller was specified as default. unless controller = controller(params, @defaults.key?(:controller)) return [404, {'X-Cascade' => 'pass'}, []] end dispatch(controller, params[:action], req.env) end def prepare_params!(params) normalize_controller!(params) merge_default_action!(params) end # If this is a default_controller (i.e. a controller specified by the user) # we should raise an error in case it's not found, because it usually means # a user error. However, if the controller was retrieved through a dynamic # segment, as in :controller(/:action), we should simply return nil and # delegate the control back to Rack cascade. Besides, if this is not a default # controller, it means we should respect the @scope[:module] parameter. def controller(params, default_controller=true) if params && params.key?(:controller) controller_param = params[:controller] controller_reference(controller_param) end rescue NameError => e raise ActionController::RoutingError, e.message, e.backtrace if default_controller end private def controller_reference(controller_param) const_name = "#{controller_param.camelize}Controller" ActiveSupport::Dependencies.constantize(const_name) end def dispatch(controller, action, env) controller.action(action).call(env) end def normalize_controller!(params) params[:controller] = params[:controller].underscore if params.key?(:controller) end def merge_default_action!(params) params[:action] ||= 'index' end end # A NamedRouteCollection instance is a collection of named routes, and also # maintains an anonymous module that can be used to install helpers for the # named routes. class NamedRouteCollection include Enumerable attr_reader :routes, :url_helpers_module def initialize @routes = {} @path_helpers = Set.new @url_helpers = Set.new @url_helpers_module = Module.new @path_helpers_module = Module.new end def route_defined?(name) key = name.to_sym @path_helpers.include?(key) || @url_helpers.include?(key) end def helpers ActiveSupport::Deprecation.warn(<<-MSG.squish) `named_routes.helpers` is deprecated, please use `route_defined?(route_name)` to see if a named route was defined. MSG @path_helpers + @url_helpers end def helper_names @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s) end def clear! @path_helpers.each do |helper| @path_helpers_module.send :undef_method, helper end @url_helpers.each do |helper| @url_helpers_module.send :undef_method, helper end @routes.clear @path_helpers.clear @url_helpers.clear end def add(name, route) key = name.to_sym path_name = :"#{name}_path" url_name = :"#{name}_url" if routes.key? key @path_helpers_module.send :undef_method, path_name @url_helpers_module.send :undef_method, url_name end routes[key] = route define_url_helper @path_helpers_module, route, path_name, route.defaults, name, LEGACY define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN @path_helpers << path_name @url_helpers << url_name end def get(name) routes[name.to_sym] end def key?(name) routes.key? name.to_sym end alias []= add alias [] get alias clear clear! def each routes.each { |name, route| yield name, route } self end def names routes.keys end def length routes.length end def path_helpers_module(warn = false) if warn mod = @path_helpers_module helpers = @path_helpers Module.new do include mod helpers.each do |meth| define_method(meth) do |*args, &block| ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead") super(*args, &block) end end end else @path_helpers_module end end class UrlHelper def self.create(route, options, route_name, url_strategy) if optimize_helper?(route) OptimizedUrlHelper.new(route, options, route_name, url_strategy) else new route, options, route_name, url_strategy end end def self.optimize_helper?(route) !route.glob? && route.path.requirements.empty? end attr_reader :url_strategy, :route_name class OptimizedUrlHelper < UrlHelper attr_reader :arg_size def initialize(route, options, route_name, url_strategy) super @required_parts = @route.required_parts @arg_size = @required_parts.size end def call(t, args, inner_options) if args.size == arg_size && !inner_options && optimize_routes_generation?(t) options = t.url_options.merge @options options[:path] = optimized_helper(args) url_strategy.call options else super end end private def optimized_helper(args) params = parameterize_args(args) missing_keys = missing_keys(params) unless missing_keys.empty? raise_generation_error(params, missing_keys) end @route.format params end def optimize_routes_generation?(t) t.send(:optimize_routes_generation?) end def parameterize_args(args) params = {} @required_parts.zip(args.map(&:to_param)) { |k,v| params[k] = v } params end def missing_keys(args) args.select{ |part, arg| arg.nil? || arg.empty? }.keys end def raise_generation_error(args, missing_keys) constraints = Hash[@route.requirements.merge(args).sort_by{|k,v| k.to_s}] message = "No route matches #{constraints.inspect}" message << " missing required keys: #{missing_keys.sort.inspect}" raise ActionController::UrlGenerationError, message end end def initialize(route, options, route_name, url_strategy) @options = options @segment_keys = route.segment_keys.uniq @route = route @url_strategy = url_strategy @route_name = route_name end def call(t, args, inner_options) controller_options = t.url_options options = controller_options.merge @options hash = handle_positional_args(controller_options, deprecate_string_options(inner_options) || {}, args, options, @segment_keys) t._routes.url_for(hash, route_name, url_strategy) end def handle_positional_args(controller_options, inner_options, args, result, path_params) if args.size > 0 # take format into account if path_params.include?(:format) path_params_size = path_params.size - 1 else path_params_size = path_params.size end if args.size < path_params_size path_params -= controller_options.keys path_params -= result.keys end path_params.each { |param| value = inner_options.fetch(param) { args.shift } unless param == :format && value.nil? result[param] = value end } end result.merge!(inner_options) end DEPRECATED_STRING_OPTIONS = %w[controller action] def deprecate_string_options(options) options ||= {} deprecated_string_options = options.keys & DEPRECATED_STRING_OPTIONS if deprecated_string_options.any? msg = "Calling URL helpers with string keys #{deprecated_string_options.join(", ")} is deprecated. Use symbols instead." ActiveSupport::Deprecation.warn(msg) deprecated_string_options.each do |option| value = options.delete(option) options[option.to_sym] = value end end options end end private # Create a url helper allowing ordered parameters to be associated # with corresponding dynamic segments, so you can do: # # foo_url(bar, baz, bang) # # Instead of: # # foo_url(bar: bar, baz: baz, bang: bang) # # Also allow options hash, so you can do: # # foo_url(bar, baz, bang, sort_by: 'baz') # def define_url_helper(mod, route, name, opts, route_key, url_strategy) helper = UrlHelper.create(route, opts, route_key, url_strategy) mod.module_eval do define_method(name) do |*args| options = nil options = args.pop if args.last.is_a? Hash helper.call self, args, options end end end end # strategy for building urls to send to the client PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) } FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) } UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) } LEGACY = ->(options) { if options.key?(:only_path) if options[:only_path] ActiveSupport::Deprecation.warn(<<-MSG.squish) You are calling a `*_path` helper with the `only_path` option explicitly set to `true`. This option will stop working on path helpers in Rails 5. Simply remove the `only_path: true` argument from your call as it is redundant when applied to a path helper. MSG PATH.call(options) else ActiveSupport::Deprecation.warn(<<-MSG.squish) You are calling a `*_path` helper with the `only_path` option explicitly set to `false`. This option will stop working on path helpers in Rails 5. Use the corresponding `*_url` helper instead. MSG FULL.call(options) end else PATH.call(options) end } attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :default_url_options, :request_class alias :routes :set def self.default_resources_path_names { :new => 'new', :edit => 'edit' } end def initialize(request_class = ActionDispatch::Request) self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names self.default_url_options = {} self.request_class = request_class @append = [] @prepend = [] @disable_clear_and_finalize = false @finalized = false @set = Journey::Routes.new @router = Journey::Router.new @set @formatter = Journey::Formatter.new @set end def draw(&block) clear! unless @disable_clear_and_finalize eval_block(block) finalize! unless @disable_clear_and_finalize nil end def append(&block) @append << block end def prepend(&block) @prepend << block end def eval_block(block) if block.arity == 1 raise "You are using the old router DSL which has been removed in Rails 3.1. " << "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" end mapper = Mapper.new(self) if default_scope mapper.with_default_scope(default_scope, &block) else mapper.instance_exec(&block) end end private :eval_block def finalize! return if @finalized @append.each { |blk| eval_block(blk) } @finalized = true end def clear! @finalized = false named_routes.clear set.clear formatter.clear @prepend.each { |blk| eval_block(blk) } end def dispatcher(defaults) Routing::RouteSet::Dispatcher.new(defaults) end module MountedHelpers extend ActiveSupport::Concern include UrlFor end # Contains all the mounted helpers across different # engines and the `main_app` helper for the application. # You can include this in your classes if you want to # access routes for other engines. def mounted_helpers MountedHelpers end def define_mounted_helper(name) return if MountedHelpers.method_defined?(name) routes = self helpers = routes.url_helpers MountedHelpers.class_eval do define_method "_#{name}" do RoutesProxy.new(routes, _routes_context, helpers) end end MountedHelpers.class_eval(<<-RUBY, __FILE__, __LINE__ + 1) def #{name} @_#{name} ||= _#{name} end RUBY end def url_helpers(supports_path = true) routes = self Module.new do extend ActiveSupport::Concern include UrlFor # Define url_for in the singleton level so one can do: # Rails.application.routes.url_helpers.url_for(args) @_routes = routes class << self delegate :url_for, :optimize_routes_generation?, to: '@_routes' attr_reader :_routes def url_options; {}; end end url_helpers = routes.named_routes.url_helpers_module # Make named_routes available in the module singleton # as well, so one can do: # Rails.application.routes.url_helpers.posts_path extend url_helpers # Any class that includes this module will get all # named routes... include url_helpers if supports_path path_helpers = routes.named_routes.path_helpers_module else path_helpers = routes.named_routes.path_helpers_module(true) end include path_helpers extend path_helpers # plus a singleton class method called _routes ... included do singleton_class.send(:redefine_method, :_routes) { routes } end # And an instance method _routes. Note that # UrlFor (included in this module) add extra # conveniences for working with @_routes. define_method(:_routes) { @_routes || routes } define_method(:_generate_paths_by_default) do supports_path end private :_generate_paths_by_default end end def empty? routes.empty? end def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) if name && named_routes[name] raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \ "You may have defined two routes with the same name using the `:as` option, or " \ "you may be overriding a route already defined by a resource with the same naming. " \ "For the latter, you can restrict the routes created with `resources` as explained here: \n" \ "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created" end path = conditions.delete :path_info ast = conditions.delete :parsed_path_info path = build_path(path, ast, requirements, anchor) conditions = build_conditions(conditions, path.names.map { |x| x.to_sym }) route = @set.add_route(app, path, conditions, defaults, name) named_routes[name] = route if name route end def build_path(path, ast, requirements, anchor) strexp = Journey::Router::Strexp.new( ast, path, requirements, SEPARATORS, anchor) pattern = Journey::Path::Pattern.new(strexp) builder = Journey::GTG::Builder.new pattern.spec # Get all the symbol nodes followed by literals that are not the # dummy node. symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n| builder.followpos(n).first.literal? } # Get all the symbol nodes preceded by literals. symbols.concat pattern.spec.find_all(&:literal?).map { |n| builder.followpos(n).first }.find_all(&:symbol?) symbols.each { |x| x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/ } pattern end private :build_path def build_conditions(current_conditions, path_values) conditions = current_conditions.dup # Rack-Mount requires that :request_method be a regular expression. # :request_method represents the HTTP verb that matches this route. # # Here we munge values before they get sent on to rack-mount. verbs = conditions[:request_method] || [] unless verbs.empty? conditions[:request_method] = %r[^#{verbs.join('|')}$] end conditions.keep_if do |k, _| k == :action || k == :controller || k == :required_defaults || @request_class.public_method_defined?(k) || path_values.include?(k) end end private :build_conditions class Generator PARAMETERIZE = lambda do |name, value| if name == :controller value elsif value.is_a?(Array) value.map { |v| v.to_param }.join('/') elsif param = value.to_param param end end attr_reader :options, :recall, :set, :named_route def initialize(named_route, options, recall, set) @named_route = named_route @options = options.dup @recall = recall.dup @set = set normalize_recall! normalize_options! normalize_controller_action_id! use_relative_controller! normalize_controller! normalize_action! end def controller @options[:controller] end def current_controller @recall[:controller] end def use_recall_for(key) if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key]) if !named_route_exists? || segment_keys.include?(key) @options[key] = @recall.delete(key) end end end # Set 'index' as default action for recall def normalize_recall! @recall[:action] ||= 'index' end def normalize_options! # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like # # generate({controller: 'content'}, {controller: 'content', action: 'show'}) # # (the above is from the unit tests). In the above case, because the # controller was explicitly given, but no action, the action is implied to # be "index", not the recalled action of "show". if options[:controller] options[:action] ||= 'index' options[:controller] = options[:controller].to_s end if options.key?(:action) options[:action] = (options[:action] || 'index').to_s end end # This pulls :controller, :action, and :id out of the recall. # The recall key is only used if there is no key in the options # or if the key in the options is identical. If any of # :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! use_recall_for(:controller) or return use_recall_for(:action) or return use_recall_for(:id) end # if the current controller is "foo/bar/baz" and controller: "baz/bat" # is specified, the controller becomes "foo/baz/bat" def use_relative_controller! if !named_route && different_controller? && !controller.start_with?("/") old_parts = current_controller.split('/') size = controller.count("/") + 1 parts = old_parts[0...-size] << controller @options[:controller] = parts.join("/") end end # Remove leading slashes from controllers def normalize_controller! @options[:controller] = controller.sub(%r{^/}, '') if controller end # Move 'index' action from options to recall def normalize_action! if @options[:action] == 'index' @recall[:action] = @options.delete(:action) end end # Generates a path from routes, returns [path, params]. # If no route is generated the formatter will raise ActionController::UrlGenerationError def generate @set.formatter.generate(named_route, options, recall, PARAMETERIZE) end def different_controller? return false unless current_controller controller.to_param != current_controller.to_param end private def named_route_exists? named_route && set.named_routes[named_route] end def segment_keys set.named_routes[named_route].segment_keys end end # Generate the path indicated by the arguments, and return an array of # the keys that were not used to generate it. def extra_keys(options, recall={}) generate_extras(options, recall).last end def generate_extras(options, recall={}) route_key = options.delete :use_route path, params = generate(route_key, options, recall) return path, params.keys end def generate(route_key, options, recall = {}) Generator.new(route_key, options, recall, self).generate end private :generate RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length, :trailing_slash, :anchor, :params, :only_path, :script_name, :original_script_name, :relative_url_root] def optimize_routes_generation? default_url_options.empty? end def find_script_name(options) options.delete(:script_name) || find_relative_url_root(options) || '' end def find_relative_url_root(options) options.delete(:relative_url_root) || relative_url_root end def path_for(options, route_name = nil) url_for(options, route_name, PATH) end # The +options+ argument must be a hash whose keys are *symbols*. def url_for(options, route_name = nil, url_strategy = UNKNOWN) options = default_url_options.merge options user = password = nil if options[:user] && options[:password] user = options.delete :user password = options.delete :password end recall = options.delete(:_recall) { {} } original_script_name = options.delete(:original_script_name) script_name = find_script_name options if original_script_name script_name = original_script_name + script_name end path_options = options.dup RESERVED_OPTIONS.each { |ro| path_options.delete ro } path, params = generate(route_name, path_options, recall) if options.key? :params params.merge! options[:params] end options[:path] = path options[:script_name] = script_name options[:params] = params options[:user] = user options[:password] = password url_strategy.call options end def call(env) req = request_class.new(env) req.path_info = Journey::Router::Utils.normalize_path(req.path_info) @router.serve(req) end def recognize_path(path, environment = {}) method = (environment[:method] || "GET").to_s.upcase path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://} extras = environment[:extras] || {} begin env = Rack::MockRequest.env_for(path, {:method => method}) rescue URI::InvalidURIError => e raise ActionController::RoutingError, e.message end req = request_class.new(env) @router.recognize(req) do |route, params| params.merge!(extras) params.each do |key, value| if value.is_a?(String) value = value.dup.force_encoding(Encoding::BINARY) params[key] = URI.parser.unescape(value) end end old_params = req.path_parameters req.path_parameters = old_params.merge params app = route.app if app.matches?(req) && app.dispatcher? dispatcher = app.app if dispatcher.controller(params, false) dispatcher.prepare_params!(params) return params else raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller" end end end raise ActionController::RoutingError, "No route matches #{path.inspect}" end end # :startdoc: end end rails-4.2.6/actionpack/lib/action_dispatch/routing/routes_proxy.rb000066400000000000000000000020421266740050600254550ustar00rootroot00000000000000require 'active_support/core_ext/array/extract_options' module ActionDispatch module Routing class RoutesProxy #:nodoc: include ActionDispatch::Routing::UrlFor attr_accessor :scope, :routes alias :_routes :routes def initialize(routes, scope, helpers) @routes, @scope = routes, scope @helpers = helpers end def url_options scope.send(:_with_routes, routes) do scope.url_options end end def respond_to?(method, include_private = false) super || @helpers.respond_to?(method) end def method_missing(method, *args) if @helpers.respond_to?(method) self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method}(*args) options = args.extract_options! args << url_options.merge((options || {}).symbolize_keys) @helpers.#{method}(*args) end RUBY send(method, *args) else super end end end end end rails-4.2.6/actionpack/lib/action_dispatch/routing/url_for.rb000066400000000000000000000202751266740050600243530ustar00rootroot00000000000000module ActionDispatch module Routing # In config/routes.rb you define URL-to-controller mappings, but the reverse # is also possible: an URL can be generated from one of your routing definitions. # URL generation functionality is centralized in this module. # # See ActionDispatch::Routing for general information about routing and routes.rb. # # Tip: If you need to generate URLs from your models or some other place, # then ActionController::UrlFor is what you're looking for. Read on for # an introduction. In general, this module should not be included on its own, # as it is usually included by url_helpers (as in Rails.application.routes.url_helpers). # # == URL generation from parameters # # As you may know, some functions, such as ActionController::Base#url_for # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set # of parameters. For example, you've probably had the chance to write code # like this in one of your views: # # <%= link_to('Click here', controller: 'users', # action: 'new', message: 'Welcome!') %> # # => Click here # # link_to, and all other functions that require URL generation functionality, # actually use ActionController::UrlFor under the hood. And in particular, # they use the ActionController::UrlFor#url_for method. One can generate # the same path as the above example by using the following code: # # include UrlFor # url_for(controller: 'users', # action: 'new', # message: 'Welcome!', # only_path: true) # # => "/users/new?message=Welcome%21" # # Notice the only_path: true part. This is because UrlFor has no # information about the website hostname that your Rails app is serving. So if you # want to include the hostname as well, then you must also pass the :host # argument: # # include UrlFor # url_for(controller: 'users', # action: 'new', # message: 'Welcome!', # host: 'www.example.com') # # => "http://www.example.com/users/new?message=Welcome%21" # # By default, all controllers and views have access to a special version of url_for, # that already knows what the current hostname is. So if you use url_for in your # controllers or your views, then you don't need to explicitly pass the :host # argument. # # For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for. # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for' # in full. However, mailers don't have hostname information, and that's why you'll still # have to specify the :host argument when generating URLs in mailers. # # # == URL generation for named routes # # UrlFor also allows one to access methods that have been auto-generated from # named routes. For example, suppose that you have a 'users' resource in your # config/routes.rb: # # resources :users # # This generates, among other things, the method users_path. By default, # this method is accessible from your controllers, views and mailers. If you need # to access this auto-generated method from other places (such as a model), then # you can do that by including Rails.application.routes.url_helpers in your class: # # class User < ActiveRecord::Base # include Rails.application.routes.url_helpers # # def base_uri # user_path(self) # end # end # # User.find(1).base_uri # => "/users/1" # module UrlFor extend ActiveSupport::Concern include PolymorphicRoutes included do unless method_defined?(:default_url_options) # Including in a class uses an inheritable hash. Modules get a plain hash. if respond_to?(:class_attribute) class_attribute :default_url_options else mattr_writer :default_url_options end self.default_url_options = {} end include(*_url_for_modules) if respond_to?(:_url_for_modules) end def initialize(*) @_routes = nil super end # Hook overridden in controller to add request information # with `default_url_options`. Application logic should not # go into url_options. def url_options default_url_options end # Generate a url based on the options provided, default_url_options and the # routes defined in routes.rb. The following options are supported: # # * :only_path - If true, the relative url is returned. Defaults to +false+. # * :protocol - The protocol to connect to. Defaults to 'http'. # * :host - Specifies the host the link should be targeted at. # If :only_path is false, this option must be # provided either explicitly, or via +default_url_options+. # * :subdomain - Specifies the subdomain of the link, using the +tld_length+ # to split the subdomain from the host. # If false, removes all subdomains from the host part of the link. # * :domain - Specifies the domain of the link, using the +tld_length+ # to split the domain from the host. # * :tld_length - Number of labels the TLD id composed of, only used if # :subdomain or :domain are supplied. Defaults to # ActionDispatch::Http::URL.tld_length, which in turn defaults to 1. # * :port - Optionally specify the port to connect to. # * :anchor - An anchor name to be appended to the path. # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/" # * :script_name - Specifies application path relative to domain root. If provided, prepends application path. # # Any other key (:controller, :action, etc.) given to # +url_for+ is forwarded to the Routes module. # # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', port: '8080' # # => 'http://somehost.org:8080/tasks/testing' # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', anchor: 'ok', only_path: true # # => '/tasks/testing#ok' # url_for controller: 'tasks', action: 'testing', trailing_slash: true # # => 'http://somehost.org/tasks/testing/' # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', number: '33' # # => 'http://somehost.org/tasks/testing?number=33' # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp" # # => 'http://somehost.org/myapp/tasks/testing' # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true # # => '/myapp/tasks/testing' def url_for(options = nil) case options when nil _routes.url_for(url_options.symbolize_keys) when Hash route_name = options.delete :use_route _routes.url_for(options.symbolize_keys.reverse_merge!(url_options), route_name) when String options when Symbol HelperMethodBuilder.url.handle_string_call self, options when Array components = options.dup polymorphic_url(components, components.extract_options!) when Class HelperMethodBuilder.url.handle_class_call self, options else HelperMethodBuilder.url.handle_model_call self, options end end protected def optimize_routes_generation? _routes.optimize_routes_generation? && default_url_options.empty? end def _with_routes(routes) old_routes, @_routes = @_routes, routes yield ensure @_routes = old_routes end def _routes_context self end private def _generate_paths_by_default true end end end end rails-4.2.6/actionpack/lib/action_dispatch/testing/000077500000000000000000000000001266740050600223365ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/testing/assertions.rb000066400000000000000000000011371266740050600250570ustar00rootroot00000000000000require 'rails-dom-testing' module ActionDispatch module Assertions autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response' autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing' extend ActiveSupport::Concern include ResponseAssertions include RoutingAssertions include Rails::Dom::Testing::Assertions def html_document @html_document ||= if @response.content_type.to_s =~ /xml$/ Nokogiri::XML::Document.parse(@response.body) else Nokogiri::HTML::Document.parse(@response.body) end end end end rails-4.2.6/actionpack/lib/action_dispatch/testing/assertions/000077500000000000000000000000001266740050600245305ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_dispatch/testing/assertions/dom.rb000066400000000000000000000002431266740050600256330ustar00rootroot00000000000000require 'active_support/deprecation' ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::DomAssertions has been extracted to the rails-dom-testing gem.")rails-4.2.6/actionpack/lib/action_dispatch/testing/assertions/response.rb000066400000000000000000000066021266740050600267170ustar00rootroot00000000000000 module ActionDispatch module Assertions # A small suite of assertions that test responses from \Rails applications. module ResponseAssertions # Asserts that the response is one of the following types: # # * :success - Status code was in the 200-299 range # * :redirect - Status code was in the 300-399 range # * :missing - Status code was 404 # * :error - Status code was in the 500-599 range # # You can also pass an explicit status number like assert_response(501) # or its symbolic equivalent assert_response(:not_implemented). # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list. # # # assert that the response was a redirection # assert_response :redirect # # # assert that the response code was status code 401 (unauthorized) # assert_response 401 def assert_response(type, message = nil) message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>" if Symbol === type if [:success, :missing, :redirect, :error].include?(type) assert @response.send("#{type}?"), message else code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type] if code.nil? raise ArgumentError, "Invalid response type :#{type}" end assert_equal code, @response.response_code, message end else assert_equal type, @response.response_code, message end end # Assert that the redirection options passed in match those of the redirect called in the latest action. # This match can be partial, such that assert_redirected_to(controller: "weblog") will also # match the redirection of redirect_to(controller: "weblog", action: "show") and so on. # # # assert that the redirection was to the "index" action on the WeblogController # assert_redirected_to controller: "weblog", action: "index" # # # assert that the redirection was to the named route login_url # assert_redirected_to login_url # # # assert that the redirection was to the url for @customer # assert_redirected_to @customer # # # asserts that the redirection matches the regular expression # assert_redirected_to %r(\Ahttp://example.org) def assert_redirected_to(options = {}, message=nil) assert_response(:redirect, message) return true if options === @response.location redirect_is = normalize_argument_to_redirection(@response.location) redirect_expected = normalize_argument_to_redirection(options) message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>" assert_operator redirect_expected, :===, redirect_is, message end private # Proxy to to_param if the object will respond to it. def parameterize(value) value.respond_to?(:to_param) ? value.to_param : value end def normalize_argument_to_redirection(fragment) if Regexp === fragment fragment else handle = @controller || ActionController::Redirecting handle._compute_redirect_to_location(@request, fragment) end end end end end rails-4.2.6/actionpack/lib/action_dispatch/testing/assertions/routing.rb000066400000000000000000000234451266740050600265540ustar00rootroot00000000000000require 'uri' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' require 'action_controller/metal/exceptions' module ActionDispatch module Assertions # Suite of assertions to test routes generated by \Rails and the handling of requests made to them. module RoutingAssertions # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash) # match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+. # # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes # requiring a specific HTTP method. The hash should contain a :path with the incoming request path # and a :method containing the required HTTP verb. # # # assert that POSTing to /items will call the create action on ItemsController # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post}) # # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the # extras argument, appending the query string on the path directly will not work. For example: # # # assert that a path of '/items/list/1?view=print' returns the correct options # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" }) # # The +message+ parameter allows you to pass in an error message that is displayed upon failure. # # # Check the default route (i.e., the index action) # assert_recognizes({controller: 'items', action: 'index'}, 'items') # # # Test a specific action # assert_recognizes({controller: 'items', action: 'list'}, 'items/list') # # # Test an action with a parameter # assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1') # # # Test a custom route # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1') def assert_recognizes(expected_options, path, extras={}, msg=nil) if path.is_a?(Hash) && path[:method].to_s == "all" [:get, :post, :put, :delete].each do |method| assert_recognizes(expected_options, path.merge(method: method), extras, msg) end else request = recognized_request_for(path, extras, msg) expected_options = expected_options.clone expected_options.stringify_keys! msg = message(msg, "") { sprintf("The recognized options <%s> did not match <%s>, difference:", request.path_parameters, expected_options) } assert_equal(expected_options, request.path_parameters, msg) end end # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+. # The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in # a query string. The +message+ parameter allows you to specify a custom error message for assertion failures. # # The +defaults+ parameter is unused. # # # Asserts that the default action is generated for a route with no action # assert_generates "/items", controller: "items", action: "index" # # # Tests that the list action is properly routed # assert_generates "/items/list", controller: "items", action: "list" # # # Tests the generation of a route with a parameter # assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" } # # # Asserts that the generated route gives us our custom route # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" } def assert_generates(expected_path, options, defaults={}, extras={}, message=nil) if expected_path =~ %r{://} fail_on(URI::InvalidURIError, message) do uri = URI.parse(expected_path) expected_path = uri.path.to_s.empty? ? "/" : uri.path end else expected_path = "/#{expected_path}" unless expected_path.first == '/' end # Load routes.rb if it hasn't been loaded. generated_path, extra_keys = @routes.generate_extras(options, defaults) found_extras = options.reject { |k, _| ! extra_keys.include? k } msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras) assert_equal(extras, found_extras, msg) msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path, expected_path) assert_equal(expected_path, generated_path, msg) end # Asserts that path and options match both ways; in other words, it verifies that path generates # options and then that options generates path. This essentially combines +assert_recognizes+ # and +assert_generates+ into one step. # # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The # +message+ parameter allows you to specify a custom error message to display upon failure. # # # Assert a basic route: a controller with the default action (index) # assert_routing '/home', controller: 'home', action: 'index' # # # Test a route generated with a specific controller, action, and parameter (id) # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23 # # # Assert a basic route (controller + default action), with an error message if it fails # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly' # # # Tests a route, providing a defaults hash # assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"} # # # Tests a route with a HTTP method # assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" }) def assert_routing(path, options, defaults={}, extras={}, message=nil) assert_recognizes(options, path, extras, message) controller, default_controller = options[:controller], defaults[:controller] if controller && controller.include?(?/) && default_controller && default_controller.include?(?/) options[:controller] = "/#{controller}" end generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) } assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message) end # A helper to make it easier to test different route configurations. # This method temporarily replaces @routes # with a new RouteSet instance. # # The new instance is yielded to the passed block. Typically the block # will create some routes using set.draw { match ... }: # # with_routing do |set| # set.draw do # resources :users # end # assert_equal "/users", users_path # end # def with_routing old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new if defined?(@controller) && @controller old_controller, @controller = @controller, @controller.clone _routes = @routes @controller.singleton_class.send(:include, _routes.url_helpers) @controller.view_context_class = Class.new(@controller.view_context_class) do include _routes.url_helpers end end yield @routes ensure @routes = old_routes if defined?(@controller) && @controller @controller = old_controller end end # ROUTES TODO: These assertions should really work in an integration context def method_missing(selector, *args, &block) if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector) @controller.send(selector, *args, &block) else super end end private # Recognizes the route for a given path. def recognized_request_for(path, extras = {}, msg) if path.is_a?(Hash) method = path[:method] path = path[:path] else method = :get end # Assume given controller request = ActionController::TestRequest.new if path =~ %r{://} fail_on(URI::InvalidURIError, msg) do uri = URI.parse(path) request.env["rack.url_scheme"] = uri.scheme || "http" request.host = uri.host if uri.host request.port = uri.port if uri.port request.path = uri.path.to_s.empty? ? "/" : uri.path end else path = "/#{path}" unless path.first == "/" request.path = path end request.request_method = method if method params = fail_on(ActionController::RoutingError, msg) do @routes.recognize_path(path, { :method => method, :extras => extras }) end request.path_parameters = params.with_indifferent_access request end def fail_on(exception_class, message) yield rescue exception_class => e raise Minitest::Assertion, message || e.message end end end end rails-4.2.6/actionpack/lib/action_dispatch/testing/assertions/selector.rb000066400000000000000000000002511266740050600266730ustar00rootroot00000000000000require 'active_support/deprecation' ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::SelectorAssertions has been extracted to the rails-dom-testing gem.") rails-4.2.6/actionpack/lib/action_dispatch/testing/assertions/tag.rb000066400000000000000000000002461266740050600256320ustar00rootroot00000000000000require 'active_support/deprecation' ActiveSupport::Deprecation.warn('`ActionDispatch::Assertions::TagAssertions` has been extracted to the rails-dom-testing gem.') rails-4.2.6/actionpack/lib/action_dispatch/testing/integration.rb000066400000000000000000000420151266740050600252100ustar00rootroot00000000000000require 'stringio' require 'uri' require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/object/try' require 'rack/test' require 'minitest' module ActionDispatch module Integration #:nodoc: module RequestHelpers # Performs a GET request with the given parameters. # # - +path+: The URI (as a String) on which you want to perform a GET # request. # - +parameters+: The HTTP parameters that you want to pass. This may # be +nil+, # a Hash, or a String that is appropriately encoded # (application/x-www-form-urlencoded or # multipart/form-data). # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be # merged into the Rack env hash. # # This method returns a Response object, which one can use to # inspect the details of the response. Furthermore, if this method was # called from an ActionDispatch::IntegrationTest object, then that # object's @response instance variable will point to the same # response object. # # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with # +#post+, +#patch+, +#put+, +#delete+, and +#head+. def get(path, parameters = nil, headers_or_env = nil) process :get, path, parameters, headers_or_env end # Performs a POST request with the given parameters. See +#get+ for more # details. def post(path, parameters = nil, headers_or_env = nil) process :post, path, parameters, headers_or_env end # Performs a PATCH request with the given parameters. See +#get+ for more # details. def patch(path, parameters = nil, headers_or_env = nil) process :patch, path, parameters, headers_or_env end # Performs a PUT request with the given parameters. See +#get+ for more # details. def put(path, parameters = nil, headers_or_env = nil) process :put, path, parameters, headers_or_env end # Performs a DELETE request with the given parameters. See +#get+ for # more details. def delete(path, parameters = nil, headers_or_env = nil) process :delete, path, parameters, headers_or_env end # Performs a HEAD request with the given parameters. See +#get+ for more # details. def head(path, parameters = nil, headers_or_env = nil) process :head, path, parameters, headers_or_env end # Performs an XMLHttpRequest request with the given parameters, mirroring # a request from the Prototype library. # # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart # string; the headers are a hash. def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil) headers_or_env ||= {} headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') process(request_method, path, parameters, headers_or_env) end alias xhr :xml_http_request # Follow a single redirect response. If the last response was not a # redirect, an exception will be raised. Otherwise, the redirect is # performed on the location header. def follow_redirect! raise "not a redirect! #{status} #{status_message}" unless redirect? get(response.location) status end # Performs a request using the specified method, following any subsequent # redirect. Note that the redirects are followed until the response is # not a redirect--this means you may run into an infinite loop if your # redirect loops back to itself. def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil) process(http_method, path, parameters, headers_or_env) follow_redirect! while redirect? status end # Performs a GET request, following any subsequent redirect. # See +request_via_redirect+ for more information. def get_via_redirect(path, parameters = nil, headers_or_env = nil) request_via_redirect(:get, path, parameters, headers_or_env) end # Performs a POST request, following any subsequent redirect. # See +request_via_redirect+ for more information. def post_via_redirect(path, parameters = nil, headers_or_env = nil) request_via_redirect(:post, path, parameters, headers_or_env) end # Performs a PATCH request, following any subsequent redirect. # See +request_via_redirect+ for more information. def patch_via_redirect(path, parameters = nil, headers_or_env = nil) request_via_redirect(:patch, path, parameters, headers_or_env) end # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. def put_via_redirect(path, parameters = nil, headers_or_env = nil) request_via_redirect(:put, path, parameters, headers_or_env) end # Performs a DELETE request, following any subsequent redirect. # See +request_via_redirect+ for more information. def delete_via_redirect(path, parameters = nil, headers_or_env = nil) request_via_redirect(:delete, path, parameters, headers_or_env) end end # An instance of this class represents a set of requests and responses # performed sequentially by a test process. Because you can instantiate # multiple sessions and run them side-by-side, you can also mimic (to some # limited extent) multiple simultaneous users interacting with your system. # # Typically, you will instantiate a new session using # IntegrationTest#open_session, rather than instantiating # Integration::Session directly. class Session DEFAULT_HOST = "www.example.com" include Minitest::Assertions include TestProcess, RequestHelpers, Assertions %w( status status_message headers body redirect? ).each do |method| delegate method, :to => :response, :allow_nil => true end %w( path ).each do |method| delegate method, :to => :request, :allow_nil => true end # The hostname used in the last request. def host @host || DEFAULT_HOST end attr_writer :host # The remote_addr used in the last request. attr_accessor :remote_addr # The Accept header to send. attr_accessor :accept # A map of the cookies returned by the last response, and which will be # sent with the next request. def cookies _mock_session.cookie_jar end # A reference to the controller instance used by the last request. attr_reader :controller # A reference to the request instance used by the last request. attr_reader :request # A reference to the response instance used by the last request. attr_reader :response # A running counter of the number of requests processed. attr_accessor :request_count include ActionDispatch::Routing::UrlFor # Create and initialize a new Session instance. def initialize(app) super() @app = app # If the app is a Rails app, make url_helpers available on the session # This makes app.url_for and app.foo_path available in the console if app.respond_to?(:routes) singleton_class.class_eval do include app.routes.url_helpers include app.routes.mounted_helpers end end reset! end def url_options @url_options ||= default_url_options.dup.tap do |url_options| url_options.reverse_merge!(controller.url_options) if controller if @app.respond_to?(:routes) url_options.reverse_merge!(@app.routes.default_url_options) end url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http") end end # Resets the instance. This can be used to reset the state information # in an existing session instance, so it can be used from a clean-slate # condition. # # session.reset! def reset! @https = false @controller = @request = @response = nil @_mock_session = nil @request_count = 0 @url_options = nil self.host = DEFAULT_HOST self.remote_addr = "127.0.0.1" self.accept = "text/xml,application/xml,application/xhtml+xml," + "text/html;q=0.9,text/plain;q=0.8,image/png," + "*/*;q=0.5" unless defined? @named_routes_configured # the helpers are made protected by default--we make them public for # easier access during testing and troubleshooting. @named_routes_configured = true end end # Specify whether or not the session should mimic a secure HTTPS request. # # session.https! # session.https!(false) def https!(flag = true) @https = flag end # Returns +true+ if the session is mimicking a secure HTTPS request. # # if session.https? # ... # end def https? @https end # Set the host name to use in the next request. # # session.host! "www.example.com" alias :host! :host= private def _mock_session @_mock_session ||= Rack::MockSession.new(@app, host) end # Performs the actual request. def process(method, path, parameters = nil, headers_or_env = nil) if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme host! "#{location.host}:#{location.port}" if location.host path = location.query ? "#{location.path}?#{location.query}" : location.path end hostname, port = host.split(':') env = { :method => method, :params => parameters, "SERVER_NAME" => hostname, "SERVER_PORT" => port || (https? ? "443" : "80"), "HTTPS" => https? ? "on" : "off", "rack.url_scheme" => https? ? "https" : "http", "REQUEST_URI" => path, "HTTP_HOST" => host, "REMOTE_ADDR" => remote_addr, "CONTENT_TYPE" => "application/x-www-form-urlencoded", "HTTP_ACCEPT" => accept } # this modifies the passed env directly Http::Headers.new(env).merge!(headers_or_env || {}) session = Rack::Test::Session.new(_mock_session) # NOTE: rack-test v0.5 doesn't build a default uri correctly # Make sure requested path is always a full uri session.request(build_full_uri(path, env), env) @request_count += 1 @request = ActionDispatch::Request.new(session.last_request.env) response = _mock_session.last_response @response = ActionDispatch::TestResponse.from_response(response) @html_document = nil @html_scanner_document = nil @url_options = nil @controller = session.last_request.env['action_controller.instance'] return response.status end def build_full_uri(path, env) "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" end end module Runner include ActionDispatch::Assertions def app @app ||= nil end # Reset the current session. This is useful for testing multiple sessions # in a single test case. def reset! @integration_session = Integration::Session.new(app) end def remove! # :nodoc: @integration_session = nil end %w(get post patch put head delete cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| reset! unless integration_session # reset the html_document variable, except for cookies/assigns calls unless method == 'cookies' || method == 'assigns' @html_document = nil @html_scanner_document = nil reset_template_assertion end integration_session.__send__(method, *args).tap do copy_session_variables! end end end # Open a new session instance. If a block is given, the new session is # yielded to the block before being returned. # # session = open_session do |sess| # sess.extend(CustomAssertions) # end # # By default, a single session is automatically created for you, but you # can use this method to open multiple sessions that ought to be tested # simultaneously. def open_session dup.tap do |session| yield session if block_given? end end # Copy the instance variables from the current session instance into the # test instance. def copy_session_variables! #:nodoc: return unless integration_session %w(controller response request).each do |var| instance_variable_set("@#{var}", @integration_session.__send__(var)) end end def default_url_options reset! unless integration_session integration_session.default_url_options end def default_url_options=(options) reset! unless integration_session integration_session.default_url_options = options end def respond_to?(method, include_private = false) integration_session.respond_to?(method, include_private) || super end # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) reset! unless integration_session if integration_session.respond_to?(sym) integration_session.__send__(sym, *args, &block).tap do copy_session_variables! end else super end end private def integration_session @integration_session ||= nil end end end # An integration test spans multiple controllers and actions, # tying them all together to ensure they work together as expected. It tests # more completely than either unit or functional tests do, exercising the # entire stack, from the dispatcher to the database. # # At its simplest, you simply extend IntegrationTest and write your tests # using the get/post methods: # # require "test_helper" # # class ExampleTest < ActionDispatch::IntegrationTest # fixtures :people # # def test_login # # get the login page # get "/login" # assert_equal 200, status # # # post the login and follow through to the home page # post "/login", username: people(:jamis).username, # password: people(:jamis).password # follow_redirect! # assert_equal 200, status # assert_equal "/home", path # end # end # # However, you can also have multiple session instances open per test, and # even extend those instances with assertions and methods to create a very # powerful testing DSL that is specific for your application. You can even # reference any named routes you happen to have defined. # # require "test_helper" # # class AdvancedTest < ActionDispatch::IntegrationTest # fixtures :people, :rooms # # def test_login_and_speak # jamis, david = login(:jamis), login(:david) # room = rooms(:office) # # jamis.enter(room) # jamis.speak(room, "anybody home?") # # david.enter(room) # david.speak(room, "hello!") # end # # private # # module CustomAssertions # def enter(room) # # reference a named route, for maximum internal consistency! # get(room_url(id: room.id)) # assert(...) # ... # end # # def speak(room, message) # xml_http_request "/say/#{room.id}", message: message # assert(...) # ... # end # end # # def login(who) # open_session do |sess| # sess.extend(CustomAssertions) # who = people(who) # sess.post "/login", username: who.username, # password: who.password # assert(...) # end # end # end class IntegrationTest < ActiveSupport::TestCase include Integration::Runner include ActionController::TemplateAssertions include ActionDispatch::Routing::UrlFor @@app = nil def self.app @@app || ActionDispatch.test_app end def self.app=(app) @@app = app end def app super || self.class.app end def url_options reset! unless integration_session integration_session.url_options end def document_root_element html_document.root end end end rails-4.2.6/actionpack/lib/action_dispatch/testing/test_process.rb000066400000000000000000000024741266740050600254070ustar00rootroot00000000000000require 'action_dispatch/middleware/cookies' require 'action_dispatch/middleware/flash' require 'active_support/core_ext/hash/indifferent_access' module ActionDispatch module TestProcess def assigns(key = nil) assigns = {}.with_indifferent_access @controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) } key.nil? ? assigns : assigns[key] end def session @request.session end def flash @request.flash end def cookies @request.cookie_jar end def redirect_to_url @response.redirect_url end # Shortcut for Rack::Test::UploadedFile.new(File.join(ActionController::TestCase.fixture_path, path), type): # # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png') # # To upload binary files on Windows, pass :binary as the last parameter. # This will not affect other platforms: # # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary) def fixture_file_upload(path, mime_type = nil, binary = false) if self.class.respond_to?(:fixture_path) && self.class.fixture_path path = File.join(self.class.fixture_path, path) end Rack::Test::UploadedFile.new(path, mime_type, binary) end end end rails-4.2.6/actionpack/lib/action_dispatch/testing/test_request.rb000066400000000000000000000032021266740050600254070ustar00rootroot00000000000000require 'active_support/core_ext/hash/indifferent_access' require 'rack/utils' module ActionDispatch class TestRequest < Request DEFAULT_ENV = Rack::MockRequest.env_for('/', 'HTTP_HOST' => 'test.host', 'REMOTE_ADDR' => '0.0.0.0', 'HTTP_USER_AGENT' => 'Rails Testing' ) def self.new(env = {}) super end def initialize(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application super(default_env.merge(env)) end def request_method=(method) @env['REQUEST_METHOD'] = method.to_s.upcase end def host=(host) @env['HTTP_HOST'] = host end def port=(number) @env['SERVER_PORT'] = number.to_i end def request_uri=(uri) @env['REQUEST_URI'] = uri end def path=(path) @env['PATH_INFO'] = path end def action=(action_name) path_parameters[:action] = action_name.to_s end def if_modified_since=(last_modified) @env['HTTP_IF_MODIFIED_SINCE'] = last_modified end def if_none_match=(etag) @env['HTTP_IF_NONE_MATCH'] = etag end def remote_addr=(addr) @env['REMOTE_ADDR'] = addr end def user_agent=(user_agent) @env['HTTP_USER_AGENT'] = user_agent end def accept=(mime_types) @env.delete('action_dispatch.request.accepts') @env['HTTP_ACCEPT'] = Array(mime_types).collect { |mime_type| mime_type.to_s }.join(",") end alias :rack_cookies :cookies def cookies @cookies ||= {}.with_indifferent_access end private def default_env DEFAULT_ENV end end end rails-4.2.6/actionpack/lib/action_dispatch/testing/test_response.rb000066400000000000000000000014551266740050600255650ustar00rootroot00000000000000module ActionDispatch # Integration test methods such as ActionDispatch::Integration::Session#get # and ActionDispatch::Integration::Session#post return objects of class # TestResponse, which represent the HTTP response results of the requested # controller actions. # # See Response for more information on controller response objects. class TestResponse < Response def self.from_response(response) new response.status, response.headers, response.body, default_headers: nil end # Was the response successful? alias_method :success?, :successful? # Was the URL not found? alias_method :missing?, :not_found? # Were we redirected? alias_method :redirect?, :redirection? # Was there a server-side error? alias_method :error?, :server_error? end end rails-4.2.6/actionpack/lib/action_pack.rb000066400000000000000000000021751266740050600203320ustar00rootroot00000000000000#-- # Copyright (c) 2004-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ require 'action_pack/version' rails-4.2.6/actionpack/lib/action_pack/000077500000000000000000000000001266740050600200005ustar00rootroot00000000000000rails-4.2.6/actionpack/lib/action_pack/gem_version.rb000066400000000000000000000004731266740050600226460ustar00rootroot00000000000000module ActionPack # Returns the version of the currently loaded Action Pack as a Gem::Version def self.gem_version Gem::Version.new VERSION::STRING end module VERSION MAJOR = 4 MINOR = 2 TINY = 6 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end end rails-4.2.6/actionpack/lib/action_pack/version.rb000066400000000000000000000002651266740050600220150ustar00rootroot00000000000000require_relative 'gem_version' module ActionPack # Returns the version of the currently loaded ActionPack as a Gem::Version def self.version gem_version end end rails-4.2.6/actionpack/test/000077500000000000000000000000001266740050600157365ustar00rootroot00000000000000rails-4.2.6/actionpack/test/abstract/000077500000000000000000000000001266740050600175415ustar00rootroot00000000000000rails-4.2.6/actionpack/test/abstract/callbacks_test.rb000066400000000000000000000177631266740050600230620ustar00rootroot00000000000000require 'abstract_unit' module AbstractController module Testing class ControllerWithCallbacks < AbstractController::Base include AbstractController::Callbacks end class Callback1 < ControllerWithCallbacks set_callback :process_action, :before, :first def first @text = "Hello world" end def index self.response_body = @text end end class TestCallbacks1 < ActiveSupport::TestCase test "basic callbacks work" do controller = Callback1.new controller.process(:index) assert_equal "Hello world", controller.response_body end end class Callback2 < ControllerWithCallbacks before_action :first after_action :second around_action :aroundz def first @text = "Hello world" end def second @second = "Goodbye" end def aroundz @aroundz = "FIRST" yield @aroundz << "SECOND" end def index @text ||= nil self.response_body = @text.to_s end end class Callback2Overwrite < Callback2 before_action :first, except: :index end class TestCallbacks2 < ActiveSupport::TestCase def setup @controller = Callback2.new end test "before_action works" do @controller.process(:index) assert_equal "Hello world", @controller.response_body end test "after_action works" do @controller.process(:index) assert_equal "Goodbye", @controller.instance_variable_get("@second") end test "around_action works" do @controller.process(:index) assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz") end test "before_action with overwritten condition" do @controller = Callback2Overwrite.new @controller.process(:index) assert_equal "", @controller.response_body end end class Callback3 < ControllerWithCallbacks before_action do |c| c.instance_variable_set("@text", "Hello world") end after_action do |c| c.instance_variable_set("@second", "Goodbye") end def index self.response_body = @text end end class TestCallbacks3 < ActiveSupport::TestCase def setup @controller = Callback3.new end test "before_action works with procs" do @controller.process(:index) assert_equal "Hello world", @controller.response_body end test "after_action works with procs" do @controller.process(:index) assert_equal "Goodbye", @controller.instance_variable_get("@second") end end class CallbacksWithConditions < ControllerWithCallbacks before_action :list, :only => :index before_action :authenticate, :except => :index def index self.response_body = @list.join(", ") end def sekrit_data self.response_body = (@list + [@authenticated]).join(", ") end private def list @list = ["Hello", "World"] end def authenticate @list ||= [] @authenticated = "true" end end class TestCallbacksWithConditions < ActiveSupport::TestCase def setup @controller = CallbacksWithConditions.new end test "when :only is specified, a before action is triggered on that action" do @controller.process(:index) assert_equal "Hello, World", @controller.response_body end test "when :only is specified, a before action is not triggered on other actions" do @controller.process(:sekrit_data) assert_equal "true", @controller.response_body end test "when :except is specified, an after action is not triggered on that action" do @controller.process(:index) assert !@controller.instance_variable_defined?("@authenticated") end end class CallbacksWithArrayConditions < ControllerWithCallbacks before_action :list, only: [:index, :listy] before_action :authenticate, except: [:index, :listy] def index self.response_body = @list.join(", ") end def sekrit_data self.response_body = (@list + [@authenticated]).join(", ") end private def list @list = ["Hello", "World"] end def authenticate @list = [] @authenticated = "true" end end class TestCallbacksWithArrayConditions < ActiveSupport::TestCase def setup @controller = CallbacksWithArrayConditions.new end test "when :only is specified with an array, a before action is triggered on that action" do @controller.process(:index) assert_equal "Hello, World", @controller.response_body end test "when :only is specified with an array, a before action is not triggered on other actions" do @controller.process(:sekrit_data) assert_equal "true", @controller.response_body end test "when :except is specified with an array, an after action is not triggered on that action" do @controller.process(:index) assert !@controller.instance_variable_defined?("@authenticated") end end class ChangedConditions < Callback2 before_action :first, :only => :index def not_index @text ||= nil self.response_body = @text.to_s end end class TestCallbacksWithChangedConditions < ActiveSupport::TestCase def setup @controller = ChangedConditions.new end test "when a callback is modified in a child with :only, it works for the :only action" do @controller.process(:index) assert_equal "Hello world", @controller.response_body end test "when a callback is modified in a child with :only, it does not work for other actions" do @controller.process(:not_index) assert_equal "", @controller.response_body end end class SetsResponseBody < ControllerWithCallbacks before_action :set_body def index self.response_body = "Fail" end def set_body self.response_body = "Success" end end class TestHalting < ActiveSupport::TestCase test "when a callback sets the response body, the action should not be invoked" do controller = SetsResponseBody.new controller.process(:index) assert_equal "Success", controller.response_body end end class CallbacksWithArgs < ControllerWithCallbacks set_callback :process_action, :before, :first def first @text = "Hello world" end def index(text) self.response_body = @text + text end end class TestCallbacksWithArgs < ActiveSupport::TestCase test "callbacks still work when invoking process with multiple arguments" do controller = CallbacksWithArgs.new controller.process(:index, " Howdy!") assert_equal "Hello world Howdy!", controller.response_body end end class AliasedCallbacks < ControllerWithCallbacks before_filter :first after_filter :second around_filter :aroundz def first @text = "Hello world" end def second @second = "Goodbye" end def aroundz @aroundz = "FIRST" yield @aroundz << "SECOND" end def index @text ||= nil self.response_body = @text.to_s end end class TestAliasedCallbacks < ActiveSupport::TestCase def setup @controller = AliasedCallbacks.new end test "before_filter works" do @controller.process(:index) assert_equal "Hello world", @controller.response_body end test "after_filter works" do @controller.process(:index) assert_equal "Goodbye", @controller.instance_variable_get("@second") end test "around_filter works" do @controller.process(:index) assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz") end end end end rails-4.2.6/actionpack/test/abstract/collector_test.rb000066400000000000000000000034611266740050600231170ustar00rootroot00000000000000require 'abstract_unit' module AbstractController module Testing class MyCollector include AbstractController::Collector attr_accessor :responses def initialize @responses = [] end def custom(mime, *args, &block) @responses << [mime, args, block] end end class TestCollector < ActiveSupport::TestCase test "responds to default mime types" do collector = MyCollector.new assert_respond_to collector, :html assert_respond_to collector, :text end test "does not respond to unknown mime types" do collector = MyCollector.new assert_not_respond_to collector, :unknown end test "register mime types on method missing" do AbstractController::Collector.send(:remove_method, :js) begin collector = MyCollector.new assert_not_respond_to collector, :js collector.js assert_respond_to collector, :js ensure unless AbstractController::Collector.method_defined? :js AbstractController::Collector.generate_method_for_mime :js end end end test "does not register unknown mime types" do collector = MyCollector.new assert_raise NoMethodError do collector.unknown end end test "generated methods call custom with arguments received" do collector = MyCollector.new collector.html collector.text(:foo) collector.js(:bar) { :baz } assert_equal [Mime::HTML, [], nil], collector.responses[0] assert_equal [Mime::TEXT, [:foo], nil], collector.responses[1] assert_equal [Mime::JS, [:bar]], collector.responses[2][0,2] assert_equal :baz, collector.responses[2][2].call end end end end rails-4.2.6/actionpack/test/abstract/translation_test.rb000066400000000000000000000026771266740050600234770ustar00rootroot00000000000000require 'abstract_unit' module AbstractController module Testing class TranslationController < AbstractController::Base include AbstractController::Translation end class TranslationControllerTest < ActiveSupport::TestCase def setup @controller = TranslationController.new end def test_action_controller_base_responds_to_translate assert_respond_to @controller, :translate end def test_action_controller_base_responds_to_t assert_respond_to @controller, :t end def test_action_controller_base_responds_to_localize assert_respond_to @controller, :localize end def test_action_controller_base_responds_to_l assert_respond_to @controller, :l end def test_lazy_lookup expected = 'bar' @controller.stubs(action_name: :index) I18n.stubs(:translate).with('abstract_controller.testing.translation.index.foo').returns(expected) assert_equal expected, @controller.t('.foo') end def test_default_translation key, expected = 'one.two', 'bar' I18n.stubs(:translate).with(key).returns(expected) assert_equal expected, @controller.t(key) end def test_localize time, expected = Time.gm(2000), 'Sat, 01 Jan 2000 00:00:00 +0000' I18n.stubs(:localize).with(time).returns(expected) assert_equal expected, @controller.l(time) end end end end rails-4.2.6/actionpack/test/abstract_unit.rb000066400000000000000000000323151266740050600211310ustar00rootroot00000000000000require File.expand_path('../../../load_paths', __FILE__) $:.unshift(File.dirname(__FILE__) + '/lib') $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') $:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') require 'active_support/core_ext/kernel/reporting' # These are the normal settings that will be set up by Railties # TODO: Have these tests support other combinations of these values silence_warnings do Encoding.default_internal = "UTF-8" Encoding.default_external = "UTF-8" end require 'drb' begin require 'drb/unix' rescue LoadError puts "'drb/unix' is not available" end require 'tempfile' PROCESS_COUNT = (ENV['N'] || 4).to_i require 'active_support/testing/autorun' require 'abstract_controller' require 'abstract_controller/railties/routes_helpers' require 'action_controller' require 'action_view' require 'action_view/testing/resolvers' require 'action_dispatch' require 'active_support/dependencies' require 'active_model' require 'active_record' require 'action_controller/caching' require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late module Rails class << self def env @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test") end end end ActiveSupport::Dependencies.hook! Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false # Register danish language for testing I18n.backend.store_translations 'da', {} I18n.backend.store_translations 'pt-BR', {} ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') FIXTURES = Pathname.new(FIXTURE_LOAD_PATH) module RackTestUtils def body_to_string(body) if body.respond_to?(:each) str = "" body.each {|s| str << s } str else body end end extend self end SharedTestRoutes = ActionDispatch::Routing::RouteSet.new module ActionDispatch module SharedRoutes def before_setup @routes = SharedTestRoutes super end end # Hold off drawing routes until all the possible controller classes # have been loaded. module DrawOnce class << self attr_accessor :drew end self.drew = false def before_setup super return if DrawOnce.drew SharedTestRoutes.draw do get ':controller(/:action)' end ActionDispatch::IntegrationTest.app.routes.draw do get ':controller(/:action)' end DrawOnce.drew = true end end end module ActiveSupport class TestCase include ActionDispatch::DrawOnce if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0 parallelize_me! end end end class RoutedRackApp attr_reader :routes def initialize(routes, &blk) @routes = routes @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes) end def call(env) @stack.call(env) end end class BasicController attr_accessor :request def config @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config| # VIEW TODO: View tests should not require a controller public_dir = File.expand_path("../fixtures/public", __FILE__) config.assets_dir = public_dir config.javascripts_dir = "#{public_dir}/javascripts" config.stylesheets_dir = "#{public_dir}/stylesheets" config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" }) config end end end class ActionDispatch::IntegrationTest < ActiveSupport::TestCase include ActionDispatch::SharedRoutes def self.build_app(routes = nil) RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware| middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") middleware.use "ActionDispatch::DebugExceptions" middleware.use "ActionDispatch::Callbacks" middleware.use "ActionDispatch::ParamsParser" middleware.use "ActionDispatch::Cookies" middleware.use "ActionDispatch::Flash" middleware.use "Rack::Head" yield(middleware) if block_given? end end self.app = build_app # Stub Rails dispatcher so it does not get controller references and # simply return the controller#action as Rack::Body. class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher protected def controller_reference(controller_param) controller_param end def dispatch(controller, action, env) [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] end end def self.stub_controllers old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher } yield ActionDispatch::Routing::RouteSet.new ensure ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher } end def with_routing(&block) temporary_routes = ActionDispatch::Routing::RouteSet.new old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes) old_routes = SharedTestRoutes silence_warnings { Object.const_set(:SharedTestRoutes, temporary_routes) } yield temporary_routes ensure self.class.app = old_app self.remove! silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) } end def with_autoload_path(path) path = File.join(File.dirname(__FILE__), "fixtures", path) if ActiveSupport::Dependencies.autoload_paths.include?(path) yield else begin ActiveSupport::Dependencies.autoload_paths << path yield ensure ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path} ActiveSupport::Dependencies.clear end end end end # Temporary base class class Rack::TestCase < ActionDispatch::IntegrationTest def self.testing(klass = nil) if klass @testing = "/#{klass.name.underscore}".sub!(/_controller$/, '') else @testing end end def get(thing, *args) if thing.is_a?(Symbol) super("#{self.class.testing}/#{thing}", *args) else super end end def assert_body(body) assert_equal body, Array(response.body).join end def assert_status(code) assert_equal code, response.status end def assert_response(body, status = 200, headers = {}) assert_body body assert_status status headers.each do |header, value| assert_header header, value end end def assert_content_type(type) assert_equal type, response.headers["Content-Type"] end def assert_header(name, value) assert_equal value, response.headers[name] end end module ActionController class Base # This stub emulates the Railtie including the URL helpers from a Rails application extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes) include SharedTestRoutes.mounted_helpers self.view_paths = FIXTURE_LOAD_PATH def self.test_routes(&block) routes = ActionDispatch::Routing::RouteSet.new routes.draw(&block) include routes.url_helpers end end class TestCase include ActionDispatch::TestProcess include ActionDispatch::SharedRoutes end end class ::ApplicationController < ActionController::Base end class Workshop extend ActiveModel::Naming include ActiveModel::Conversion attr_accessor :id def initialize(id) @id = id end def persisted? id.present? end def to_s id.to_s end end module ActionDispatch class DebugExceptions private remove_method :stderr_logger # Silence logger def stderr_logger nil end end end module ActionDispatch module RoutingVerbs def send_request(uri_or_host, method, path) host = uri_or_host.host unless path path ||= uri_or_host.path params = {'PATH_INFO' => path, 'REQUEST_METHOD' => method, 'HTTP_HOST' => host} routes.call(params) end def request_path_params(path, options = {}) method = options[:method] || 'GET' resp = send_request URI('http://localhost' + path), method.to_s.upcase, nil status = resp.first if status == 404 raise ActionController::RoutingError, "No route matches #{path.inspect}" end controller.request.path_parameters end def get(uri_or_host, path = nil) send_request(uri_or_host, 'GET', path)[2].join end def post(uri_or_host, path = nil) send_request(uri_or_host, 'POST', path)[2].join end def put(uri_or_host, path = nil) send_request(uri_or_host, 'PUT', path)[2].join end def delete(uri_or_host, path = nil) send_request(uri_or_host, 'DELETE', path)[2].join end def patch(uri_or_host, path = nil) send_request(uri_or_host, 'PATCH', path)[2].join end end end module RoutingTestHelpers def url_for(set, options) route_name = options.delete :use_route set.url_for options.merge(:only_path => true), route_name end def make_set(strict = true) tc = self TestSet.new ->(c) { tc.controller = c }, strict end class TestSet < ActionDispatch::Routing::RouteSet attr_reader :strict def initialize(block, strict = false) @block = block @strict = strict super() end class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher def initialize(defaults, set, block) super(defaults) @block = block @set = set end def controller(params, default_controller=true) super(params, @set.strict) end def controller_reference(controller_param) block = @block set = @set super if @set.strict Class.new(ActionController::Base) { include set.url_helpers define_method(:process) { |name| block.call(self) } def to_a; [200, {}, []]; end } end end def dispatcher defaults TestSet::Dispatcher.new defaults, self, @block end end end class ResourcesController < ActionController::Base def index() render :nothing => true end alias_method :show, :index end class ThreadsController < ResourcesController; end class MessagesController < ResourcesController; end class CommentsController < ResourcesController; end class ReviewsController < ResourcesController; end class LogosController < ResourcesController; end class AccountsController < ResourcesController; end class AdminController < ResourcesController; end class ProductsController < ResourcesController; end class ImagesController < ResourcesController; end class PreferencesController < ResourcesController; end module Backoffice class ProductsController < ResourcesController; end class ImagesController < ResourcesController; end module Admin class ProductsController < ResourcesController; end class ImagesController < ResourcesController; end end end # Skips the current run on Rubinius using Minitest::Assertions#skip def rubinius_skip(message = '') skip message if RUBY_ENGINE == 'rbx' end # Skips the current run on JRuby using Minitest::Assertions#skip def jruby_skip(message = '') skip message if defined?(JRUBY_VERSION) end require 'mocha/setup' # FIXME: stop using mocha class ForkingExecutor class Server include DRb::DRbUndumped def initialize @queue = Queue.new end def record reporter, result reporter.record result end def << o o[2] = DRbObject.new(o[2]) if o @queue << o end def pop; @queue.pop; end end def initialize size @size = size @queue = Server.new file = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('rails-tests', 'fd') @url = "drbunix://#{file}" @pool = nil DRb.start_service @url, @queue end def << work; @queue << work; end def shutdown pool = @size.times.map { fork { DRb.stop_service queue = DRbObject.new_with_uri @url while job = queue.pop klass = job[0] method = job[1] reporter = job[2] result = Minitest.run_one_method klass, method if result.error? translate_exceptions result end queue.record reporter, result end } } @size.times { @queue << nil } pool.each { |pid| Process.waitpid pid } end private def translate_exceptions(result) result.failures.map! { |e| begin Marshal.dump e e rescue TypeError ex = Exception.new e.message ex.set_backtrace e.backtrace Minitest::UnexpectedError.new ex end } end end if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0 # Use N processes (N defaults to 4) Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT) end # FIXME: we have tests that depend on run order, we should fix that and # remove this method call. require 'active_support/test_case' ActiveSupport::TestCase.test_order = :sorted rails-4.2.6/actionpack/test/assertions/000077500000000000000000000000001266740050600201305ustar00rootroot00000000000000rails-4.2.6/actionpack/test/assertions/response_assertions_test.rb000066400000000000000000000030131266740050600256210ustar00rootroot00000000000000require 'abstract_unit' require 'action_dispatch/testing/assertions/response' module ActionDispatch module Assertions class ResponseAssertionsTest < ActiveSupport::TestCase include ResponseAssertions FakeResponse = Struct.new(:response_code) do [:success, :missing, :redirect, :error].each do |sym| define_method("#{sym}?") do sym == response_code end end end def test_assert_response_predicate_methods [:success, :missing, :redirect, :error].each do |sym| @response = FakeResponse.new sym assert_response sym assert_raises(Minitest::Assertion) { assert_response :unauthorized } end end def test_assert_response_fixnum @response = FakeResponse.new 400 assert_response 400 assert_raises(Minitest::Assertion) { assert_response :unauthorized } assert_raises(Minitest::Assertion) { assert_response 500 } end def test_assert_response_sym_status @response = FakeResponse.new 401 assert_response :unauthorized assert_raises(Minitest::Assertion) { assert_response :ok } assert_raises(Minitest::Assertion) { assert_response :success } end def test_assert_response_sym_typo @response = FakeResponse.new 200 assert_raises(ArgumentError) { assert_response :succezz } end end end end rails-4.2.6/actionpack/test/controller/000077500000000000000000000000001266740050600201215ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/action_pack_assertions_test.rb000066400000000000000000000432011266740050600262320ustar00rootroot00000000000000require 'abstract_unit' require 'controller/fake_controllers' class ActionPackAssertionsController < ActionController::Base def nothing() head :ok end def hello_world() render :template => "test/hello_world"; end def hello_repeating_in_path() render :template => "test/hello/hello"; end def hello_xml_world() render :template => "test/hello_xml_world"; end def hello_xml_world_pdf self.content_type = "application/pdf" render :template => "test/hello_xml_world" end def hello_xml_world_pdf_header response.headers["Content-Type"] = "application/pdf; charset=utf-8" render :template => "test/hello_xml_world" end def partial() render :partial => 'test/partial'; end def redirect_internal() redirect_to "/nothing"; end def redirect_to_action() redirect_to :action => "flash_me", :id => 1, :params => { "panda" => "fun" }; end def redirect_to_controller() redirect_to :controller => "elsewhere", :action => "flash_me"; end def redirect_to_controller_with_symbol() redirect_to :controller => :elsewhere, :action => :flash_me; end def redirect_to_path() redirect_to '/some/path' end def redirect_invalid_external_route() redirect_to 'ht_tp://www.rubyonrails.org' end def redirect_to_named_route() redirect_to route_one_url end def redirect_external() redirect_to "http://www.rubyonrails.org"; end def redirect_external_protocol_relative() redirect_to "//www.rubyonrails.org"; end def response404() head '404 AWOL' end def response500() head '500 Sorry' end def response599() head '599 Whoah!' end def flash_me flash['hello'] = 'my name is inigo montoya...' render :text => "Inconceivable!" end def flash_me_naked flash.clear render :text => "wow!" end def assign_this @howdy = "ho" render :inline => "Mr. Henke" end def render_based_on_parameters render :text => "Mr. #{params[:name]}" end def render_url render :text => "
#{url_for(:action => 'flash_me', :only_path => true)}
" end def render_text_with_custom_content_type render :text => "Hello!", :content_type => Mime::RSS end def render_with_layout @variable_for_layout = nil render "test/hello_world", :layout => "layouts/standard" end def render_with_layout_and_partial @variable_for_layout = nil render "test/hello_world_with_partial", :layout => "layouts/standard" end def session_stuffing session['xmas'] = 'turkey' render :text => "ho ho ho" end def raise_exception_on_get raise "get" if request.get? render :text => "request method: #{request.env['REQUEST_METHOD']}" end def raise_exception_on_post raise "post" if request.post? render :text => "request method: #{request.env['REQUEST_METHOD']}" end def render_file_absolute_path render :file => File.expand_path('../../../README.rdoc', __FILE__) end def render_file_relative_path render :file => 'README.rdoc' end end # Used to test that assert_response includes the exception message # in the failure message when an action raises and assert_response # is expecting something other than an error. class AssertResponseWithUnexpectedErrorController < ActionController::Base def index raise 'FAIL' end def show render :text => "Boom", :status => 500 end end module Admin class InnerModuleController < ActionController::Base def index render :nothing => true end def redirect_to_index redirect_to admin_inner_module_path end def redirect_to_absolute_controller redirect_to :controller => '/content' end def redirect_to_fellow_controller redirect_to :controller => 'user' end def redirect_to_top_level_named_route redirect_to top_level_url(:id => "foo") end end end class ActionPackAssertionsControllerTest < ActionController::TestCase def test_render_file_absolute_path get :render_file_absolute_path assert_match(/\A= Action Pack/, @response.body) end def test_render_file_relative_path get :render_file_relative_path assert_match(/\A= Action Pack/, @response.body) end def test_get_request assert_raise(RuntimeError) { get :raise_exception_on_get } get :raise_exception_on_post assert_equal 'request method: GET', @response.body end def test_post_request assert_raise(RuntimeError) { post :raise_exception_on_post } post :raise_exception_on_get assert_equal 'request method: POST', @response.body end def test_get_post_request_switch post :raise_exception_on_get assert_equal 'request method: POST', @response.body get :raise_exception_on_post assert_equal 'request method: GET', @response.body post :raise_exception_on_get assert_equal 'request method: POST', @response.body get :raise_exception_on_post assert_equal 'request method: GET', @response.body end def test_string_constraint with_routing do |set| set.draw do get "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"} end end end def test_assert_redirect_to_named_route_failure with_routing do |set| set.draw do get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two get ':controller/:action' end process :redirect_to_named_route assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to 'http://test.host/route_two' end assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to %r(^http://test.host/route_two) end assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' end assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to route_two_url end end end def test_assert_redirect_to_nested_named_route @controller = Admin::InnerModuleController.new with_routing do |set| set.draw do get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module get ':controller/:action' end process :redirect_to_index # redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}> assert_redirected_to admin_inner_module_path end end def test_assert_redirected_to_top_level_named_route_from_nested_controller @controller = Admin::InnerModuleController.new with_routing do |set| set.draw do get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level get ':controller/:action' end process :redirect_to_top_level_named_route # assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return assert_redirected_to "/action_pack_assertions/foo" assert_redirected_to %r(/action_pack_assertions/foo) end end def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in_both_namespaces @controller = Admin::InnerModuleController.new with_routing do |set| set.draw do # this controller exists in the admin namespace as well which is the only difference from previous test get '/user/:id', :to => 'user#index', :as => :top_level get ':controller/:action' end process :redirect_to_top_level_named_route # assert_redirected_to top_level_url('foo') would pass because of exact match early return assert_redirected_to top_level_path('foo') end end def test_assert_redirect_failure_message_with_protocol_relative_url begin process :redirect_external_protocol_relative assert_redirected_to "/foo" rescue ActiveSupport::TestCase::Assertion => ex assert_no_match( /#{request.protocol}#{request.host}\/\/www.rubyonrails.org/, ex.message, 'protocol relative url was incorrectly normalized' ) end end def test_template_objects_exist process :assign_this assert !@controller.instance_variable_defined?(:"@hi") assert @controller.instance_variable_get(:"@howdy") end def test_template_objects_missing process :nothing assert !@controller.instance_variable_defined?(:@howdy) end def test_empty_flash process :flash_me_naked assert flash.empty? end def test_flash_exist process :flash_me assert flash.any? assert flash['hello'].present? end def test_flash_does_not_exist process :nothing assert flash.empty? end def test_session_exist process :session_stuffing assert_equal 'turkey', session['xmas'] end def session_does_not_exist process :nothing assert session.empty? end def test_render_template_action process :nothing assert_template nil process :hello_world assert_template 'hello_world' end def test_redirection_location process :redirect_internal assert_equal 'http://test.host/nothing', @response.redirect_url process :redirect_external assert_equal 'http://www.rubyonrails.org', @response.redirect_url process :redirect_external_protocol_relative assert_equal '//www.rubyonrails.org', @response.redirect_url end def test_no_redirect_url process :nothing assert_nil @response.redirect_url end def test_server_error_response_code process :response500 assert @response.server_error? process :response599 assert @response.server_error? process :response404 assert !@response.server_error? end def test_missing_response_code process :response404 assert @response.missing? end def test_client_error_response_code process :response404 assert @response.client_error? end def test_redirect_url_match process :redirect_external assert @response.redirect? assert_match(/rubyonrails/, @response.redirect_url) assert !/perloffrails/.match(@response.redirect_url) end def test_redirection process :redirect_internal assert @response.redirect? process :redirect_external assert @response.redirect? process :nothing assert !@response.redirect? end def test_successful_response_code process :nothing assert @response.success? end def test_response_object process :nothing assert_kind_of ActionController::TestResponse, @response end def test_render_based_on_parameters process :render_based_on_parameters, "GET", "name" => "David" assert_equal "Mr. David", @response.body end def test_assert_redirection_fails_with_incorrect_controller process :redirect_to_controller assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to :controller => "action_pack_assertions", :action => "flash_me" end end def test_assert_redirection_with_extra_controller_option get :redirect_to_action assert_redirected_to :controller => 'action_pack_assertions', :action => "flash_me", :id => 1, :params => { :panda => 'fun' } end def test_redirected_to_url_leading_slash process :redirect_to_path assert_redirected_to '/some/path' end def test_redirected_to_url_no_leading_slash_fails process :redirect_to_path assert_raise ActiveSupport::TestCase::Assertion do assert_redirected_to 'some/path' end end def test_redirect_invalid_external_route process :redirect_invalid_external_route assert_redirected_to "http://test.hostht_tp://www.rubyonrails.org" end def test_redirected_to_url_full_url process :redirect_to_path assert_redirected_to 'http://test.host/some/path' end def test_assert_redirection_with_symbol process :redirect_to_controller_with_symbol assert_nothing_raised { assert_redirected_to :controller => "elsewhere", :action => "flash_me" } process :redirect_to_controller_with_symbol assert_nothing_raised { assert_redirected_to :controller => :elsewhere, :action => :flash_me } end def test_redirected_to_with_nested_controller @controller = Admin::InnerModuleController.new get :redirect_to_absolute_controller assert_redirected_to :controller => '/content' get :redirect_to_fellow_controller assert_redirected_to :controller => 'admin/user' end def test_assert_response_uses_exception_message @controller = AssertResponseWithUnexpectedErrorController.new e = assert_raise RuntimeError, 'Expected non-success response' do get :index end assert_response :success assert_includes 'FAIL', e.message end def test_assert_response_failure_response_with_no_exception @controller = AssertResponseWithUnexpectedErrorController.new get :show assert_response 500 assert_equal 'Boom', response.body end end class AssertTemplateTest < ActionController::TestCase tests ActionPackAssertionsController def test_with_invalid_hash_keys_raises_argument_error assert_raise(ArgumentError) do assert_template foo: "bar" end end def test_with_partial get :partial assert_template :partial => '_partial' end def test_file_with_absolute_path_success get :render_file_absolute_path assert_template :file => File.expand_path('../../../README.rdoc', __FILE__) end def test_file_with_relative_path_success get :render_file_relative_path assert_template :file => 'README.rdoc' end def test_with_file_failure get :render_file_absolute_path assert_raise(ActiveSupport::TestCase::Assertion) do assert_template :file => 'test/hello_world' end get :render_file_absolute_path assert_raise(ActiveSupport::TestCase::Assertion) do assert_template file: nil end end def test_with_nil_passes_when_no_template_rendered get :nothing assert_template nil end def test_with_nil_fails_when_template_rendered get :hello_world assert_raise(ActiveSupport::TestCase::Assertion) do assert_template nil end end def test_with_empty_string_fails_when_template_rendered get :hello_world assert_raise(ActiveSupport::TestCase::Assertion) do assert_template "" end end def test_with_empty_string_fails_when_no_template_rendered get :nothing assert_raise(ActiveSupport::TestCase::Assertion) do assert_template "" end end def test_passes_with_correct_string get :hello_world assert_template 'hello_world' assert_template 'test/hello_world' end def test_passes_with_correct_symbol get :hello_world assert_template :hello_world end def test_fails_with_incorrect_string get :hello_world assert_raise(ActiveSupport::TestCase::Assertion) do assert_template 'hello_planet' end end def test_fails_with_incorrect_string_that_matches get :hello_world assert_raise(ActiveSupport::TestCase::Assertion) do assert_template 'est/he' end end def test_fails_with_repeated_name_in_path get :hello_repeating_in_path assert_raise(ActiveSupport::TestCase::Assertion) do assert_template 'test/hello' end end def test_fails_with_incorrect_symbol get :hello_world assert_raise(ActiveSupport::TestCase::Assertion) do assert_template :hello_planet end end def test_fails_with_incorrect_symbol_that_matches get :hello_world assert_raise(ActiveSupport::TestCase::Assertion) do assert_template :"est/he" end end def test_fails_with_wrong_layout get :render_with_layout assert_raise(ActiveSupport::TestCase::Assertion) do assert_template :layout => "application" end end def test_fails_expecting_no_layout get :render_with_layout assert_raise(ActiveSupport::TestCase::Assertion) do assert_template :layout => nil end end def test_passes_with_correct_layout get :render_with_layout assert_template :layout => "layouts/standard" end def test_passes_with_layout_and_partial get :render_with_layout_and_partial assert_template :layout => "layouts/standard" end def test_passed_with_no_layout get :hello_world assert_template :layout => nil end def test_passed_with_no_layout_false get :hello_world assert_template :layout => false end def test_passes_with_correct_layout_without_layouts_prefix get :render_with_layout assert_template :layout => "standard" end def test_passes_with_correct_layout_symbol get :render_with_layout assert_template :layout => :standard end def test_assert_template_reset_between_requests get :hello_world assert_template 'test/hello_world' get :nothing assert_template nil get :partial assert_template partial: 'test/_partial' get :nothing assert_template partial: nil get :render_with_layout assert_template layout: 'layouts/standard' get :nothing assert_template layout: nil get :render_file_relative_path assert_template file: 'README.rdoc' get :nothing assert_template file: nil end end class ActionPackHeaderTest < ActionController::TestCase tests ActionPackAssertionsController def test_rendering_xml_sets_content_type process :hello_xml_world assert_equal('application/xml; charset=utf-8', @response.headers['Content-Type']) end def test_rendering_xml_respects_content_type process :hello_xml_world_pdf assert_equal('application/pdf; charset=utf-8', @response.headers['Content-Type']) end def test_rendering_xml_respects_content_type_when_set_in_the_header process :hello_xml_world_pdf_header assert_equal('application/pdf; charset=utf-8', @response.headers['Content-Type']) end def test_render_text_with_custom_content_type get :render_text_with_custom_content_type assert_equal 'application/rss+xml; charset=utf-8', @response.headers['Content-Type'] end end rails-4.2.6/actionpack/test/controller/base_test.rb000066400000000000000000000236231266740050600224250ustar00rootroot00000000000000require 'abstract_unit' require 'active_support/logger' require 'controller/fake_models' require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late # Provide some controller to run the tests on. module Submodule class ContainedEmptyController < ActionController::Base end class ContainedNonEmptyController < ActionController::Base def public_action render :nothing => true end hide_action :hidden_action def hidden_action raise "Noooo!" end def another_hidden_action end hide_action :another_hidden_action end class SubclassedController < ContainedNonEmptyController hide_action :public_action # Hiding it here should not affect the superclass. end end class EmptyController < ActionController::Base end class NonEmptyController < ActionController::Base def public_action render :nothing => true end hide_action :hidden_action def hidden_action end end class DefaultUrlOptionsController < ActionController::Base def from_view render :inline => "<%= #{params[:route]} %>" end def default_url_options { :host => 'www.override.com', :action => 'new', :locale => 'en' } end end class OptionalDefaultUrlOptionsController < ActionController::Base def default_url_options { thing: 'default_thing' } end end class DefaultFormatController < ActionController::Base def show render nothing: true end def default_url_options { format: 'atom' } end end class UrlOptionsController < ActionController::Base def from_view render :inline => "<%= #{params[:route]} %>" end def url_options super.merge(:host => 'www.override.com') end end class RecordIdentifierIncludedController < ActionController::Base include ActionView::RecordIdentifier end class ActionMissingController < ActionController::Base def action_missing(action) render :text => "Response for #{action}" end end class ControllerClassTests < ActiveSupport::TestCase def test_controller_path assert_equal 'empty', EmptyController.controller_path assert_equal EmptyController.controller_path, EmptyController.new.controller_path assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path end def test_controller_name assert_equal 'empty', EmptyController.controller_name assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name end def test_no_deprecation_when_action_view_record_identifier_is_included record = Comment.new record.save dom_id = nil assert_not_deprecated do dom_id = RecordIdentifierIncludedController.new.dom_id(record) end assert_equal 'comment_1', dom_id dom_class = nil assert_not_deprecated do dom_class = RecordIdentifierIncludedController.new.dom_class(record) end assert_equal 'comment', dom_class end end class ControllerInstanceTests < ActiveSupport::TestCase def setup @empty = EmptyController.new @contained = Submodule::ContainedEmptyController.new @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new] @non_empty_controllers = [NonEmptyController.new, Submodule::ContainedNonEmptyController.new] end def test_performed? assert !@empty.performed? @empty.response_body = ["sweet"] assert @empty.performed? end def test_action_methods @empty_controllers.each do |c| assert_equal Set.new, c.class.action_methods, "#{c.controller_path} should be empty!" end @non_empty_controllers.each do |c| assert_equal Set.new(%w(public_action)), c.class.action_methods, "#{c.controller_path} should not be empty!" end end def test_temporary_anonymous_controllers name = 'ExamplesController' klass = Class.new(ActionController::Base) Object.const_set(name, klass) controller = klass.new assert_equal "examples", controller.controller_path end end class PerformActionTest < ActionController::TestCase def use_controller(controller_class) @controller = controller_class.new # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get # a more accurate simulation of what happens in "real life". @controller.logger = ActiveSupport::Logger.new(nil) @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @request.host = "www.nextangle.com" end def test_process_should_be_precise use_controller EmptyController exception = assert_raise AbstractController::ActionNotFound do get :non_existent end assert_equal "The action 'non_existent' could not be found for EmptyController", exception.message end def test_get_on_hidden_should_fail use_controller NonEmptyController assert_raise(AbstractController::ActionNotFound) { get :hidden_action } assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action } end def test_action_missing_should_work use_controller ActionMissingController get :arbitrary_action assert_equal "Response for arbitrary_action", @response.body end end class UrlOptionsTest < ActionController::TestCase tests UrlOptionsController def setup super @request.host = 'www.example.com' end def test_url_for_query_params_included rs = ActionDispatch::Routing::RouteSet.new rs.draw do get 'home' => 'pages#home' end options = { :action => "home", :controller => "pages", :only_path => true, :params => { "token" => "secret" } } assert_equal '/home?token=secret', rs.url_for(options) end def test_url_options_override with_routing do |set| set.draw do get 'from_view', :to => 'url_options#from_view', :as => :from_view get ':controller/:action' end get :from_view, :route => "from_view_url" assert_equal 'http://www.override.com/from_view', @response.body assert_equal 'http://www.override.com/from_view', @controller.send(:from_view_url) assert_equal 'http://www.override.com/default_url_options/index', @controller.url_for(:controller => 'default_url_options') end end def test_url_helpers_does_not_become_actions with_routing do |set| set.draw do get "account/overview" end assert !@controller.class.action_methods.include?("account_overview_path") end end end class DefaultUrlOptionsTest < ActionController::TestCase tests DefaultUrlOptionsController def setup super @request.host = 'www.example.com' end def test_default_url_options_override with_routing do |set| set.draw do get 'from_view', :to => 'default_url_options#from_view', :as => :from_view get ':controller/:action' end get :from_view, :route => "from_view_url" assert_equal 'http://www.override.com/from_view?locale=en', @response.body assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url) assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options') end end def test_default_url_options_are_used_in_non_positional_parameters with_routing do |set| set.draw do scope("/:locale") do resources :descriptions end get ':controller/:action' end get :from_view, :route => "description_path(1)" assert_equal '/en/descriptions/1', @response.body assert_equal '/en/descriptions', @controller.send(:descriptions_path) assert_equal '/pl/descriptions', @controller.send(:descriptions_path, "pl") assert_equal '/pl/descriptions', @controller.send(:descriptions_path, :locale => "pl") assert_equal '/pl/descriptions.xml', @controller.send(:descriptions_path, "pl", "xml") assert_equal '/en/descriptions.xml', @controller.send(:descriptions_path, :format => "xml") assert_equal '/en/descriptions/1', @controller.send(:description_path, 1) assert_equal '/pl/descriptions/1', @controller.send(:description_path, "pl", 1) assert_equal '/pl/descriptions/1', @controller.send(:description_path, 1, :locale => "pl") assert_equal '/pl/descriptions/1.xml', @controller.send(:description_path, "pl", 1, "xml") assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml") end end end class DefaultFormatControllerTest < ActionController::TestCase def test_default_format_preserved_when_missing_from_positional_arguments with_routing do |set| set.draw do get "/things/:id(.:format)" => 'default_format#show', :as => :thing end assert_equal '/things/1.atom', thing_path("1") end end end class OptionalDefaultUrlOptionsControllerTest < ActionController::TestCase def test_optional_default_url_options_are_overridden_by_missing_positional_args with_routing do |set| set.draw do get "/:category(/:thing)" => "optional_default_url_options#show", :as => :thing end assert_equal "/things/a_thing", thing_path('things', 'a_thing') assert_equal "/things/default_thing", thing_path('things') end end end class EmptyUrlOptionsTest < ActionController::TestCase tests NonEmptyController def setup super @request.host = 'www.example.com' end def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set get :public_action assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for end def test_named_routes_with_path_without_doing_a_request_first @controller = EmptyController.new @controller.request = @request with_routing do |set| set.draw do resources :things end assert_equal '/things', @controller.send(:things_path) end end end rails-4.2.6/actionpack/test/controller/caching_test.rb000066400000000000000000000256501266740050600231110ustar00rootroot00000000000000require 'fileutils' require 'abstract_unit' CACHE_DIR = 'test_cache' # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) class FragmentCachingMetalTestController < ActionController::Metal abstract! include ActionController::Caching def some_action; end end class FragmentCachingMetalTest < ActionController::TestCase def setup super @store = ActiveSupport::Cache::MemoryStore.new @controller = FragmentCachingMetalTestController.new @controller.perform_caching = true @controller.cache_store = @store @params = { controller: 'posts', action: 'index' } @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @controller.params = @params @controller.request = @request @controller.response = @response end def test_fragment_cache_key assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') end end class CachingController < ActionController::Base abstract! self.cache_store = :file_store, FILE_STORE_PATH end class FragmentCachingTestController < CachingController def some_action; end end class FragmentCachingTest < ActionController::TestCase def setup super @store = ActiveSupport::Cache::MemoryStore.new @controller = FragmentCachingTestController.new @controller.perform_caching = true @controller.cache_store = @store @params = {:controller => 'posts', :action => 'index'} @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @controller.params = @params @controller.request = @request @controller.response = @response end def test_fragment_cache_key assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') assert_equal "views/test.host/fragment_caching_test/some_action", @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action') end def test_read_fragment_with_caching_enabled @store.write('views/name', 'value') assert_equal 'value', @controller.read_fragment('name') end def test_read_fragment_with_caching_disabled @controller.perform_caching = false @store.write('views/name', 'value') assert_nil @controller.read_fragment('name') end def test_fragment_exist_with_caching_enabled @store.write('views/name', 'value') assert @controller.fragment_exist?('name') assert !@controller.fragment_exist?('other_name') end def test_fragment_exist_with_caching_disabled @controller.perform_caching = false @store.write('views/name', 'value') assert !@controller.fragment_exist?('name') assert !@controller.fragment_exist?('other_name') end def test_write_fragment_with_caching_enabled assert_nil @store.read('views/name') assert_equal 'value', @controller.write_fragment('name', 'value') assert_equal 'value', @store.read('views/name') end def test_write_fragment_with_caching_disabled assert_nil @store.read('views/name') @controller.perform_caching = false assert_equal 'value', @controller.write_fragment('name', 'value') assert_nil @store.read('views/name') end def test_expire_fragment_with_simple_key @store.write('views/name', 'value') @controller.expire_fragment 'name' assert_nil @store.read('views/name') end def test_expire_fragment_with_regexp @store.write('views/name', 'value') @store.write('views/another_name', 'another_value') @store.write('views/primalgrasp', 'will not expire ;-)') @controller.expire_fragment(/name/) assert_nil @store.read('views/name') assert_nil @store.read('views/another_name') assert_equal 'will not expire ;-)', @store.read('views/primalgrasp') end def test_fragment_for @store.write('views/expensive', 'fragment content') fragment_computed = false view_context = @controller.view_context buffer = 'generated till now -> '.html_safe buffer << view_context.send(:fragment_for, 'expensive') { fragment_computed = true } assert !fragment_computed assert_equal 'generated till now -> fragment content', buffer end def test_html_safety assert_nil @store.read('views/name') content = 'value'.html_safe assert_equal content, @controller.write_fragment('name', content) cached = @store.read('views/name') assert_equal content, cached assert_equal String, cached.class html_safe = @controller.read_fragment('name') assert_equal content, html_safe assert html_safe.html_safe? end end class FunctionalCachingController < CachingController def fragment_cached end def html_fragment_cached_with_partial respond_to do |format| format.html end end def formatted_fragment_cached respond_to do |format| format.html format.xml end end def formatted_fragment_cached_with_variant respond_to do |format| format.html.phone format.html end end def fragment_cached_without_digest end end class FunctionalFragmentCachingTest < ActionController::TestCase def setup super @store = ActiveSupport::Cache::MemoryStore.new @controller = FunctionalCachingController.new @controller.perform_caching = true @controller.cache_store = @store @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end def test_fragment_caching get :fragment_cached assert_response :success expected_body = <<-CACHED Hello This bit's fragment cached Ciao CACHED assert_equal expected_body, @response.body assert_equal "This bit's fragment cached", @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached")}") end def test_fragment_caching_in_partials get :html_fragment_cached_with_partial assert_response :success assert_match(/Old fragment caching in a partial/, @response.body) assert_match("Old fragment caching in a partial", @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial")}")) end def test_skipping_fragment_cache_digesting get :fragment_cached_without_digest, :format => "html" assert_response :success expected_body = "\n

ERB

\n\n" assert_equal expected_body, @response.body assert_equal "

ERB

", @store.read("views/nodigest") end def test_render_inline_before_fragment_caching get :inline_fragment_cached assert_response :success assert_match(/Some inline content/, @response.body) assert_match(/Some cached content/, @response.body) assert_match("Some cached content", @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}")) end def test_fragment_cache_instrumentation payload = nil subscriber = proc do |*args| event = ActiveSupport::Notifications::Event.new(*args) payload = event.payload end ActiveSupport::Notifications.subscribed(subscriber, "read_fragment.action_controller") do get :inline_fragment_cached end assert_equal "functional_caching", payload[:controller] assert_equal "inline_fragment_cached", payload[:action] end def test_html_formatted_fragment_caching get :formatted_fragment_cached, :format => "html" assert_response :success expected_body = "\n

ERB

\n\n" assert_equal expected_body, @response.body assert_equal "

ERB

", @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") end def test_xml_formatted_fragment_caching get :formatted_fragment_cached, :format => "xml" assert_response :success expected_body = "\n

Builder

\n\n" assert_equal expected_body, @response.body assert_equal "

Builder

\n", @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") end def test_fragment_caching_with_variant @request.variant = :phone get :formatted_fragment_cached_with_variant, :format => "html" assert_response :success expected_body = "\n

PHONE

\n\n" assert_equal expected_body, @response.body assert_equal "

PHONE

", @store.read("views/test.host/functional_caching/formatted_fragment_cached_with_variant/#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}") end private def template_digest(name) ActionView::Digestor.digest(name: name, finder: @controller.lookup_context) end end class CacheHelperOutputBufferTest < ActionController::TestCase class MockController def read_fragment(name, options) return false end def write_fragment(name, fragment, options) fragment end end def setup super end def test_output_buffer output_buffer = ActionView::OutputBuffer.new controller = MockController.new cache_helper = Object.new cache_helper.extend(ActionView::Helpers::CacheHelper) cache_helper.expects(:controller).returns(controller).at_least(0) cache_helper.expects(:output_buffer).returns(output_buffer).at_least(0) # if the output_buffer is changed, the new one should be html_safe and of the same type cache_helper.expects(:output_buffer=).with(responds_with(:html_safe?, true)).with(instance_of(output_buffer.class)).at_least(0) assert_nothing_raised do cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil } end end def test_safe_buffer output_buffer = ActiveSupport::SafeBuffer.new controller = MockController.new cache_helper = Object.new cache_helper.extend(ActionView::Helpers::CacheHelper) cache_helper.expects(:controller).returns(controller).at_least(0) cache_helper.expects(:output_buffer).returns(output_buffer).at_least(0) # if the output_buffer is changed, the new one should be html_safe and of the same type cache_helper.expects(:output_buffer=).with(responds_with(:html_safe?, true)).with(instance_of(output_buffer.class)).at_least(0) assert_nothing_raised do cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil } end end end class ViewCacheDependencyTest < ActionController::TestCase class NoDependenciesController < ActionController::Base end class HasDependenciesController < ActionController::Base view_cache_dependency { "trombone" } view_cache_dependency { "flute" } end def test_view_cache_dependencies_are_empty_by_default assert NoDependenciesController.new.view_cache_dependencies.empty? end def test_view_cache_dependencies_are_listed_in_declaration_order assert_equal %w(trombone flute), HasDependenciesController.new.view_cache_dependencies end end rails-4.2.6/actionpack/test/controller/content_type_test.rb000066400000000000000000000107361266740050600242270ustar00rootroot00000000000000require 'abstract_unit' class OldContentTypeController < ActionController::Base # :ported: def render_content_type_from_body response.content_type = Mime::RSS render :text => "hello world!" end # :ported: def render_defaults render :text => "hello world!" end # :ported: def render_content_type_from_render render :text => "hello world!", :content_type => Mime::RSS end # :ported: def render_charset_from_body response.charset = "utf-16" render :text => "hello world!" end # :ported: def render_nil_charset_from_body response.charset = nil render :text => "hello world!" end def render_default_for_erb end def render_default_for_builder end def render_change_for_builder response.content_type = Mime::HTML render :action => "render_default_for_builder" end def render_default_content_types_for_respond_to respond_to do |format| format.html { render :text => "hello world!" } format.xml { render :action => "render_default_content_types_for_respond_to" } format.js { render :text => "hello world!" } format.rss { render :text => "hello world!", :content_type => Mime::XML } end end end class ContentTypeTest < ActionController::TestCase tests OldContentTypeController def setup super # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get # a more accurate simulation of what happens in "real life". @controller.logger = ActiveSupport::Logger.new(nil) end # :ported: def test_render_defaults get :render_defaults assert_equal "utf-8", @response.charset assert_equal Mime::HTML, @response.content_type end def test_render_changed_charset_default with_default_charset "utf-16" do get :render_defaults assert_equal "utf-16", @response.charset assert_equal Mime::HTML, @response.content_type end end # :ported: def test_content_type_from_body get :render_content_type_from_body assert_equal Mime::RSS, @response.content_type assert_equal "utf-8", @response.charset end # :ported: def test_content_type_from_render get :render_content_type_from_render assert_equal Mime::RSS, @response.content_type assert_equal "utf-8", @response.charset end # :ported: def test_charset_from_body get :render_charset_from_body assert_equal Mime::HTML, @response.content_type assert_equal "utf-16", @response.charset end # :ported: def test_nil_charset_from_body get :render_nil_charset_from_body assert_equal Mime::HTML, @response.content_type assert_equal "utf-8", @response.charset, @response.headers.inspect end def test_nil_default_for_erb with_default_charset nil do get :render_default_for_erb assert_equal Mime::HTML, @response.content_type assert_nil @response.charset, @response.headers.inspect end end def test_default_for_erb get :render_default_for_erb assert_equal Mime::HTML, @response.content_type assert_equal "utf-8", @response.charset end def test_default_for_builder get :render_default_for_builder assert_equal Mime::XML, @response.content_type assert_equal "utf-8", @response.charset end def test_change_for_builder get :render_change_for_builder assert_equal Mime::HTML, @response.content_type assert_equal "utf-8", @response.charset end private def with_default_charset(charset) old_default_charset = ActionDispatch::Response.default_charset ActionDispatch::Response.default_charset = charset yield ensure ActionDispatch::Response.default_charset = old_default_charset end end class AcceptBasedContentTypeTest < ActionController::TestCase tests OldContentTypeController def test_render_default_content_types_for_respond_to @request.accept = Mime::HTML.to_s get :render_default_content_types_for_respond_to assert_equal Mime::HTML, @response.content_type @request.accept = Mime::JS.to_s get :render_default_content_types_for_respond_to assert_equal Mime::JS, @response.content_type end def test_render_default_content_types_for_respond_to_with_template @request.accept = Mime::XML.to_s get :render_default_content_types_for_respond_to assert_equal Mime::XML, @response.content_type end def test_render_default_content_types_for_respond_to_with_overwrite @request.accept = Mime::RSS.to_s get :render_default_content_types_for_respond_to assert_equal Mime::XML, @response.content_type end end rails-4.2.6/actionpack/test/controller/controller_fixtures/000077500000000000000000000000001266740050600242355ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/app/000077500000000000000000000000001266740050600250155ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/app/controllers/000077500000000000000000000000001266740050600273635ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/app/controllers/admin/000077500000000000000000000000001266740050600304535ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb000066400000000000000000000000001266740050600342070ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb000066400000000000000000000000001266740050600331170ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/vendor/000077500000000000000000000000001266740050600255325ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/vendor/plugins/000077500000000000000000000000001266740050600272135ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/000077500000000000000000000000001266740050600313175ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/000077500000000000000000000000001266740050600320655ustar00rootroot00000000000000plugin_controller.rb000066400000000000000000000000001266740050600360620ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/librails-4.2.6/actionpack/test/controller/default_url_options_with_before_action_test.rb000066400000000000000000000012661266740050600315050ustar00rootroot00000000000000require 'abstract_unit' class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base before_action { I18n.locale = params[:locale] } after_action { I18n.locale = "en" } def target render :text => "final response" end def redirect redirect_to :action => "target" end def default_url_options {:locale => "de"} end end class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::TestCase # This test has its roots in issue #1872 test "should redirect with correct locale :de" do get :redirect, :locale => "de" assert_redirected_to "/controller_with_before_action_and_default_url_options/target?locale=de" end end rails-4.2.6/actionpack/test/controller/filters_test.rb000066400000000000000000000740001266740050600231560ustar00rootroot00000000000000require 'abstract_unit' class ActionController::Base class << self %w(append_around_action prepend_after_action prepend_around_action prepend_before_action skip_after_action skip_before_action skip_action_callback).each do |pending| define_method(pending) do |*args| $stderr.puts "#{pending} unimplemented: #{args.inspect}" end unless method_defined?(pending) end def before_actions filters = _process_action_callbacks.select { |c| c.kind == :before } filters.map! { |c| c.raw_filter } end end def assigns(key = nil) assigns = {} instance_variables.each do |ivar| next if ActionController::Base.protected_instance_variables.include?(ivar) assigns[ivar[1..-1]] = instance_variable_get(ivar) end key.nil? ? assigns : assigns[key.to_s] end end class FilterTest < ActionController::TestCase class TestController < ActionController::Base before_action :ensure_login after_action :clean_up def show render :inline => "ran action" end private def ensure_login @ran_filter ||= [] @ran_filter << "ensure_login" end def clean_up @ran_after_action ||= [] @ran_after_action << "clean_up" end end class ChangingTheRequirementsController < TestController before_action :ensure_login, :except => [:go_wild] def go_wild render :text => "gobble" end end class TestMultipleFiltersController < ActionController::Base before_action :try_1 before_action :try_2 before_action :try_3 (1..3).each do |i| define_method "fail_#{i}" do render :text => i.to_s end end protected (1..3).each do |i| define_method "try_#{i}" do instance_variable_set :@try, i if action_name == "fail_#{i}" head(404) end end end end class RenderingController < ActionController::Base before_action :before_action_rendering after_action :unreached_after_action def show @ran_action = true render :inline => "ran action" end private def before_action_rendering @ran_filter ||= [] @ran_filter << "before_action_rendering" render :inline => "something else" end def unreached_after_action @ran_filter << "unreached_after_action_after_render" end end class RenderingForPrependAfterActionController < RenderingController prepend_after_action :unreached_prepend_after_action private def unreached_prepend_after_action @ran_filter << "unreached_preprend_after_action_after_render" end end class BeforeActionRedirectionController < ActionController::Base before_action :before_action_redirects after_action :unreached_after_action def show @ran_action = true render :inline => "ran show action" end def target_of_redirection @ran_target_of_redirection = true render :inline => "ran target_of_redirection action" end private def before_action_redirects @ran_filter ||= [] @ran_filter << "before_action_redirects" redirect_to(:action => 'target_of_redirection') end def unreached_after_action @ran_filter << "unreached_after_action_after_redirection" end end class BeforeActionRedirectionForPrependAfterActionController < BeforeActionRedirectionController prepend_after_action :unreached_prepend_after_action_after_redirection private def unreached_prepend_after_action_after_redirection @ran_filter << "unreached_prepend_after_action_after_redirection" end end class ConditionalFilterController < ActionController::Base def show render :inline => "ran action" end def another_action render :inline => "ran action" end def show_without_action render :inline => "ran action without action" end private def ensure_login @ran_filter ||= [] @ran_filter << "ensure_login" end def clean_up_tmp @ran_filter ||= [] @ran_filter << "clean_up_tmp" end end class ConditionalCollectionFilterController < ConditionalFilterController before_action :ensure_login, :except => [ :show_without_action, :another_action ] end class OnlyConditionSymController < ConditionalFilterController before_action :ensure_login, :only => :show end class ExceptConditionSymController < ConditionalFilterController before_action :ensure_login, :except => :show_without_action end class BeforeAndAfterConditionController < ConditionalFilterController before_action :ensure_login, :only => :show after_action :clean_up_tmp, :only => :show end class OnlyConditionProcController < ConditionalFilterController before_action(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_action", true) } end class ExceptConditionProcController < ConditionalFilterController before_action(:except => :show_without_action) {|c| c.instance_variable_set(:"@ran_proc_action", true) } end class ConditionalClassFilter def self.before(controller) controller.instance_variable_set(:"@ran_class_action", true) end end class OnlyConditionClassController < ConditionalFilterController before_action ConditionalClassFilter, :only => :show end class ExceptConditionClassController < ConditionalFilterController before_action ConditionalClassFilter, :except => :show_without_action end class AnomolousYetValidConditionController < ConditionalFilterController before_action(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_action1", true)}, :except => :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action2", true)} end class OnlyConditionalOptionsFilter < ConditionalFilterController before_action :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) } end class ConditionalOptionsFilter < ConditionalFilterController before_action :ensure_login, :if => Proc.new { |c| true } before_action :clean_up_tmp, :if => Proc.new { |c| false } end class ConditionalOptionsSkipFilter < ConditionalFilterController before_action :ensure_login before_action :clean_up_tmp skip_before_action :ensure_login, if: -> { false } skip_before_action :clean_up_tmp, if: -> { true } end class ClassController < ConditionalFilterController before_action ConditionalClassFilter end class PrependingController < TestController prepend_before_action :wonderful_life # skip_before_action :fire_flash private def wonderful_life @ran_filter ||= [] @ran_filter << "wonderful_life" end end class SkippingAndLimitedController < TestController skip_before_action :ensure_login before_action :ensure_login, :only => :index def index render :text => 'ok' end def public render :text => 'ok' end end class SkippingAndReorderingController < TestController skip_before_action :ensure_login before_action :find_record before_action :ensure_login def index render :text => 'ok' end private def find_record @ran_filter ||= [] @ran_filter << "find_record" end end class ConditionalSkippingController < TestController skip_before_action :ensure_login, :only => [ :login ] skip_after_action :clean_up, :only => [ :login ] before_action :find_user, :only => [ :change_password ] def login render :inline => "ran action" end def change_password render :inline => "ran action" end protected def find_user @ran_filter ||= [] @ran_filter << "find_user" end end class ConditionalParentOfConditionalSkippingController < ConditionalFilterController before_action :conditional_in_parent_before, :only => [:show, :another_action] after_action :conditional_in_parent_after, :only => [:show, :another_action] private def conditional_in_parent_before @ran_filter ||= [] @ran_filter << 'conditional_in_parent_before' end def conditional_in_parent_after @ran_filter ||= [] @ran_filter << 'conditional_in_parent_after' end end class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController skip_before_action :conditional_in_parent_before, :only => :another_action skip_after_action :conditional_in_parent_after, :only => :another_action end class AnotherChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController skip_before_action :conditional_in_parent_before, :only => :show end class ProcController < PrependingController before_action(proc { |c| c.instance_variable_set(:"@ran_proc_action", true) }) end class ImplicitProcController < PrependingController before_action { |c| c.instance_variable_set(:"@ran_proc_action", true) } end class AuditFilter def self.before(controller) controller.instance_variable_set(:"@was_audited", true) end end class AroundFilter def before(controller) @execution_log = "before" controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log controller.instance_variable_set(:"@before_ran", true) end def after(controller) controller.instance_variable_set(:"@execution_log", @execution_log + " and after") controller.instance_variable_set(:"@after_ran", true) controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log end def around(controller) before(controller) yield after(controller) end end class AppendedAroundFilter def before(controller) controller.class.execution_log << " before appended aroundfilter " end def after(controller) controller.class.execution_log << " after appended aroundfilter " end def around(controller) before(controller) yield after(controller) end end class AuditController < ActionController::Base before_action(AuditFilter) def show render :text => "hello" end end class AroundFilterController < PrependingController around_action AroundFilter.new end class BeforeAfterClassFilterController < PrependingController begin filter = AroundFilter.new before_action filter after_action filter end end class MixedFilterController < PrependingController cattr_accessor :execution_log def initialize @@execution_log = "" super() end before_action { |c| c.class.execution_log << " before procfilter " } prepend_around_action AroundFilter.new after_action { |c| c.class.execution_log << " after procfilter " } append_around_action AppendedAroundFilter.new end class MixedSpecializationController < ActionController::Base class OutOfOrder < StandardError; end before_action :first before_action :second, :only => :foo def foo render :text => 'foo' end def bar render :text => 'bar' end protected def first @first = true end def second raise OutOfOrder unless @first end end class DynamicDispatchController < ActionController::Base before_action :choose %w(foo bar baz).each do |action| define_method(action) { render :text => action } end private def choose self.action_name = params[:choose] end end class PrependingBeforeAndAfterController < ActionController::Base prepend_before_action :before_all prepend_after_action :after_all before_action :between_before_all_and_after_all def before_all @ran_filter ||= [] @ran_filter << 'before_all' end def after_all @ran_filter ||= [] @ran_filter << 'after_all' end def between_before_all_and_after_all @ran_filter ||= [] @ran_filter << 'between_before_all_and_after_all' end def show render :text => 'hello' end end class ErrorToRescue < Exception; end class RescuingAroundFilterWithBlock def around(controller) yield rescue ErrorToRescue => ex controller.__send__ :render, :text => "I rescued this: #{ex.inspect}" end end class RescuedController < ActionController::Base around_action RescuingAroundFilterWithBlock.new def show raise ErrorToRescue.new("Something made the bad noise.") end end class NonYieldingAroundFilterController < ActionController::Base before_action :filter_one around_action :non_yielding_action before_action :action_two after_action :action_three def index render :inline => "index" end private def filter_one @filters ||= [] @filters << "filter_one" end def action_two @filters << "action_two" end def non_yielding_action @filters << "it didn't yield" @filter_return_value end def action_three @filters << "action_three" end end class ImplicitActionsController < ActionController::Base before_action :find_only, :only => :edit before_action :find_except, :except => :edit private def find_only @only = 'Only' end def find_except @except = 'Except' end end def test_non_yielding_around_actions_not_returning_false_do_not_raise controller = NonYieldingAroundFilterController.new controller.instance_variable_set "@filter_return_value", true assert_nothing_raised do test_process(controller, "index") end end def test_non_yielding_around_actions_returning_false_do_not_raise controller = NonYieldingAroundFilterController.new controller.instance_variable_set "@filter_return_value", false assert_nothing_raised do test_process(controller, "index") end end def test_after_actions_are_not_run_if_around_action_returns_false controller = NonYieldingAroundFilterController.new controller.instance_variable_set "@filter_return_value", false test_process(controller, "index") assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters'] end def test_after_actions_are_not_run_if_around_action_does_not_yield controller = NonYieldingAroundFilterController.new controller.instance_variable_set "@filter_return_value", true test_process(controller, "index") assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters'] end def test_added_action_to_inheritance_graph assert_equal [ :ensure_login ], TestController.before_actions end def test_base_class_in_isolation assert_equal [ ], ActionController::Base.before_actions end def test_prepending_action assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_actions end def test_running_actions test_process(PrependingController) assert_equal %w( wonderful_life ensure_login ), assigns["ran_filter"] end def test_running_actions_with_proc test_process(ProcController) assert assigns["ran_proc_action"] end def test_running_actions_with_implicit_proc test_process(ImplicitProcController) assert assigns["ran_proc_action"] end def test_running_actions_with_class test_process(AuditController) assert assigns["was_audited"] end def test_running_anomolous_yet_valid_condition_actions test_process(AnomolousYetValidConditionController) assert_equal %w( ensure_login ), assigns["ran_filter"] assert assigns["ran_class_action"] assert assigns["ran_proc_action1"] assert assigns["ran_proc_action2"] test_process(AnomolousYetValidConditionController, "show_without_action") assert_nil assigns["ran_filter"] assert !assigns["ran_class_action"] assert !assigns["ran_proc_action1"] assert !assigns["ran_proc_action2"] end def test_running_conditional_options test_process(ConditionalOptionsFilter) assert_equal %w( ensure_login ), assigns["ran_filter"] end def test_running_conditional_skip_options test_process(ConditionalOptionsSkipFilter) assert_equal %w( ensure_login ), assigns["ran_filter"] end def test_skipping_class_actions test_process(ClassController) assert_equal true, assigns["ran_class_action"] skipping_class_controller = Class.new(ClassController) do skip_before_action ConditionalClassFilter end test_process(skipping_class_controller) assert_nil assigns['ran_class_action'] end def test_running_collection_condition_actions test_process(ConditionalCollectionFilterController) assert_equal %w( ensure_login ), assigns["ran_filter"] test_process(ConditionalCollectionFilterController, "show_without_action") assert_nil assigns["ran_filter"] test_process(ConditionalCollectionFilterController, "another_action") assert_nil assigns["ran_filter"] end def test_running_only_condition_actions test_process(OnlyConditionSymController) assert_equal %w( ensure_login ), assigns["ran_filter"] test_process(OnlyConditionSymController, "show_without_action") assert_nil assigns["ran_filter"] test_process(OnlyConditionProcController) assert assigns["ran_proc_action"] test_process(OnlyConditionProcController, "show_without_action") assert !assigns["ran_proc_action"] test_process(OnlyConditionClassController) assert assigns["ran_class_action"] test_process(OnlyConditionClassController, "show_without_action") assert !assigns["ran_class_action"] end def test_running_except_condition_actions test_process(ExceptConditionSymController) assert_equal %w( ensure_login ), assigns["ran_filter"] test_process(ExceptConditionSymController, "show_without_action") assert_nil assigns["ran_filter"] test_process(ExceptConditionProcController) assert assigns["ran_proc_action"] test_process(ExceptConditionProcController, "show_without_action") assert !assigns["ran_proc_action"] test_process(ExceptConditionClassController) assert assigns["ran_class_action"] test_process(ExceptConditionClassController, "show_without_action") assert !assigns["ran_class_action"] end def test_running_only_condition_and_conditional_options test_process(OnlyConditionalOptionsFilter, "show") assert_not assigns["ran_conditional_index_proc"] end def test_running_before_and_after_condition_actions test_process(BeforeAndAfterConditionController) assert_equal %w( ensure_login clean_up_tmp), assigns["ran_filter"] test_process(BeforeAndAfterConditionController, "show_without_action") assert_nil assigns["ran_filter"] end def test_around_action test_process(AroundFilterController) assert assigns["before_ran"] assert assigns["after_ran"] end def test_before_after_class_action test_process(BeforeAfterClassFilterController) assert assigns["before_ran"] assert assigns["after_ran"] end def test_having_properties_in_around_action test_process(AroundFilterController) assert_equal "before and after", assigns["execution_log"] end def test_prepending_and_appending_around_action test_process(MixedFilterController) assert_equal " before aroundfilter before procfilter before appended aroundfilter " + " after appended aroundfilter after procfilter after aroundfilter ", MixedFilterController.execution_log end def test_rendering_breaks_actioning_chain response = test_process(RenderingController) assert_equal "something else", response.body assert !assigns["ran_action"] end def test_before_action_rendering_breaks_actioning_chain_for_after_action test_process(RenderingController) assert_equal %w( before_action_rendering ), assigns["ran_filter"] assert !assigns["ran_action"] end def test_before_action_redirects_breaks_actioning_chain_for_after_action test_process(BeforeActionRedirectionController) assert_response :redirect assert_equal "http://test.host/filter_test/before_action_redirection/target_of_redirection", redirect_to_url assert_equal %w( before_action_redirects ), assigns["ran_filter"] end def test_before_action_rendering_breaks_actioning_chain_for_preprend_after_action test_process(RenderingForPrependAfterActionController) assert_equal %w( before_action_rendering ), assigns["ran_filter"] assert !assigns["ran_action"] end def test_before_action_redirects_breaks_actioning_chain_for_preprend_after_action test_process(BeforeActionRedirectionForPrependAfterActionController) assert_response :redirect assert_equal "http://test.host/filter_test/before_action_redirection_for_prepend_after_action/target_of_redirection", redirect_to_url assert_equal %w( before_action_redirects ), assigns["ran_filter"] end def test_actions_with_mixed_specialization_run_in_order assert_nothing_raised do response = test_process(MixedSpecializationController, 'bar') assert_equal 'bar', response.body end assert_nothing_raised do response = test_process(MixedSpecializationController, 'foo') assert_equal 'foo', response.body end end def test_dynamic_dispatch %w(foo bar baz).each do |action| request = ActionController::TestRequest.new request.query_parameters[:choose] = action response = DynamicDispatchController.action(action).call(request.env).last assert_equal action, response.body end end def test_running_prepended_before_and_after_action test_process(PrependingBeforeAndAfterController) assert_equal %w( before_all between_before_all_and_after_all after_all ), assigns["ran_filter"] end def test_skipping_and_limiting_controller test_process(SkippingAndLimitedController, "index") assert_equal %w( ensure_login ), assigns["ran_filter"] test_process(SkippingAndLimitedController, "public") assert_nil assigns["ran_filter"] end def test_skipping_and_reordering_controller test_process(SkippingAndReorderingController, "index") assert_equal %w( find_record ensure_login ), assigns["ran_filter"] end def test_conditional_skipping_of_actions test_process(ConditionalSkippingController, "login") assert_nil assigns["ran_filter"] test_process(ConditionalSkippingController, "change_password") assert_equal %w( ensure_login find_user ), assigns["ran_filter"] test_process(ConditionalSkippingController, "login") assert !@controller.instance_variable_defined?("@ran_after_action") test_process(ConditionalSkippingController, "change_password") assert_equal %w( clean_up ), @controller.instance_variable_get("@ran_after_action") end def test_conditional_skipping_of_actions_when_parent_action_is_also_conditional test_process(ChildOfConditionalParentController) assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter'] test_process(ChildOfConditionalParentController, 'another_action') assert_nil assigns['ran_filter'] end def test_condition_skipping_of_actions_when_siblings_also_have_conditions test_process(ChildOfConditionalParentController) assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter'] test_process(AnotherChildOfConditionalParentController) assert_equal %w( conditional_in_parent_after ), assigns['ran_filter'] test_process(ChildOfConditionalParentController) assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter'] end def test_changing_the_requirements test_process(ChangingTheRequirementsController, "go_wild") assert_nil assigns['ran_filter'] end def test_a_rescuing_around_action response = nil assert_nothing_raised do response = test_process(RescuedController) end assert response.success? assert_equal("I rescued this: #", response.body) end def test_actions_obey_only_and_except_for_implicit_actions test_process(ImplicitActionsController, 'show') assert_equal 'Except', assigns(:except) assert_nil assigns(:only) assert_equal 'show', response.body test_process(ImplicitActionsController, 'edit') assert_equal 'Only', assigns(:only) assert_nil assigns(:except) assert_equal 'edit', response.body end private def test_process(controller, action = "show") @controller = controller.is_a?(Class) ? controller.new : controller @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new process(action) end end class PostsController < ActionController::Base module AroundExceptions class Error < StandardError ; end class Before < Error ; end class After < Error ; end end include AroundExceptions class DefaultFilter include AroundExceptions end module_eval %w(raises_before raises_after raises_both no_raise no_action).map { |action| "def #{action}; default_action end" }.join("\n") private def default_action render :inline => "#{action_name} called" end end class ControllerWithSymbolAsFilter < PostsController around_action :raise_before, :only => :raises_before around_action :raise_after, :only => :raises_after around_action :without_exception, :only => :no_raise private def raise_before raise Before yield end def raise_after yield raise After end def without_exception # Do stuff... wtf = 1 + 1 yield # Do stuff... wtf += 1 end end class ControllerWithFilterClass < PostsController class YieldingFilter < DefaultFilter def self.around(controller) yield raise After end end around_action YieldingFilter, :only => :raises_after end class ControllerWithFilterInstance < PostsController class YieldingFilter < DefaultFilter def around(controller) yield raise After end end around_action YieldingFilter.new, :only => :raises_after end class ControllerWithProcFilter < PostsController around_action(:only => :no_raise) do |c,b| c.instance_variable_set(:"@before", true) b.call c.instance_variable_set(:"@after", true) end end class ControllerWithNestedFilters < ControllerWithSymbolAsFilter around_action :raise_before, :raise_after, :without_exception, :only => :raises_both end class ControllerWithAllTypesOfFilters < PostsController before_action :before around_action :around after_action :after around_action :around_again private def before @ran_filter ||= [] @ran_filter << 'before' end def around @ran_filter << 'around (before yield)' yield @ran_filter << 'around (after yield)' end def after @ran_filter << 'after' end def around_again @ran_filter << 'around_again (before yield)' yield @ran_filter << 'around_again (after yield)' end end class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters skip_action_callback :around_again skip_action_callback :after end class YieldingAroundFiltersTest < ActionController::TestCase include PostsController::AroundExceptions def test_base controller = PostsController assert_nothing_raised { test_process(controller,'no_raise') } assert_nothing_raised { test_process(controller,'raises_before') } assert_nothing_raised { test_process(controller,'raises_after') } assert_nothing_raised { test_process(controller,'no_action') } end def test_with_symbol controller = ControllerWithSymbolAsFilter assert_nothing_raised { test_process(controller,'no_raise') } assert_raise(Before) { test_process(controller,'raises_before') } assert_raise(After) { test_process(controller,'raises_after') } assert_nothing_raised { test_process(controller,'no_raise') } end def test_with_class controller = ControllerWithFilterClass assert_nothing_raised { test_process(controller,'no_raise') } assert_raise(After) { test_process(controller,'raises_after') } end def test_with_instance controller = ControllerWithFilterInstance assert_nothing_raised { test_process(controller,'no_raise') } assert_raise(After) { test_process(controller,'raises_after') } end def test_with_proc test_process(ControllerWithProcFilter,'no_raise') assert assigns['before'] assert assigns['after'] end def test_nested_actions controller = ControllerWithNestedFilters assert_nothing_raised do begin test_process(controller,'raises_both') rescue Before, After end end assert_raise Before do begin test_process(controller,'raises_both') rescue After end end end def test_action_order_with_all_action_types test_process(ControllerWithAllTypesOfFilters,'no_raise') assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)', assigns['ran_filter'].join(' ') end def test_action_order_with_skip_action_method test_process(ControllerWithTwoLessFilters,'no_raise') assert_equal 'before around (before yield) around (after yield)', assigns['ran_filter'].join(' ') end def test_first_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new response = test_process(controller, 'fail_1') assert_equal '', response.body assert_equal 1, controller.instance_variable_get(:@try) end def test_second_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new response = test_process(controller, 'fail_2') assert_equal '', response.body assert_equal 2, controller.instance_variable_get(:@try) end def test_last_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new response = test_process(controller, 'fail_3') assert_equal '', response.body assert_equal 3, controller.instance_variable_get(:@try) end protected def test_process(controller, action = "show") @controller = controller.is_a?(Class) ? controller.new : controller process(action) end end rails-4.2.6/actionpack/test/controller/flash_hash_test.rb000066400000000000000000000125331266740050600236110ustar00rootroot00000000000000require 'abstract_unit' module ActionDispatch class FlashHashTest < ActiveSupport::TestCase def setup @hash = Flash::FlashHash.new end def test_set_get @hash[:foo] = 'zomg' assert_equal 'zomg', @hash[:foo] end def test_keys assert_equal [], @hash.keys @hash['foo'] = 'zomg' assert_equal ['foo'], @hash.keys @hash['bar'] = 'zomg' assert_equal ['foo', 'bar'].sort, @hash.keys.sort end def test_update @hash['foo'] = 'bar' @hash.update('foo' => 'baz', 'hello' => 'world') assert_equal 'baz', @hash['foo'] assert_equal 'world', @hash['hello'] end def test_key @hash['foo'] = 'bar' assert @hash.key?('foo') assert @hash.key?(:foo) assert_not @hash.key?('bar') assert_not @hash.key?(:bar) end def test_delete @hash['foo'] = 'bar' @hash.delete 'foo' assert !@hash.key?('foo') assert_nil @hash['foo'] end def test_to_hash @hash['foo'] = 'bar' assert_equal({'foo' => 'bar'}, @hash.to_hash) @hash.to_hash['zomg'] = 'aaron' assert !@hash.key?('zomg') assert_equal({'foo' => 'bar'}, @hash.to_hash) end def test_to_session_value @hash['foo'] = 'bar' assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => []}, @hash.to_session_value) @hash.discard('foo') assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => %w[foo]}, @hash.to_session_value) @hash.now['qux'] = 1 assert_equal({'flashes' => {'foo' => 'bar', 'qux' => 1}, 'discard' => %w[foo qux]}, @hash.to_session_value) @hash.sweep assert_equal(nil, @hash.to_session_value) end def test_from_session_value rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoNQGZsYXNoZXN7BkkiDG1lc3NhZ2UGOwBGSSIKSGVsbG8GOwBGOglAbm93MA==' session = Marshal.load(Base64.decode64(rails_3_2_cookie)) hash = Flash::FlashHash.from_session_value(session['flash']) assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value) end def test_from_session_value_on_json_serializer decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }" session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data) hash = Flash::FlashHash.from_session_value(session['flash']) assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value) assert_equal "hey you", hash[:message] assert_equal "hey you", hash["message"] end def test_empty? assert @hash.empty? @hash['zomg'] = 'bears' assert !@hash.empty? @hash.clear assert @hash.empty? end def test_each @hash['hello'] = 'world' @hash['foo'] = 'bar' things = [] @hash.each do |k,v| things << [k,v] end assert_equal([%w{ hello world }, %w{ foo bar }].sort, things.sort) end def test_replace @hash['hello'] = 'world' @hash.replace('omg' => 'aaron') assert_equal({'omg' => 'aaron'}, @hash.to_hash) end def test_discard_no_args @hash['hello'] = 'world' @hash.discard @hash.sweep assert_equal({}, @hash.to_hash) end def test_discard_one_arg @hash['hello'] = 'world' @hash['omg'] = 'world' @hash.discard 'hello' @hash.sweep assert_equal({'omg' => 'world'}, @hash.to_hash) end def test_keep_sweep @hash['hello'] = 'world' @hash.sweep assert_equal({'hello' => 'world'}, @hash.to_hash) end def test_update_sweep @hash['hello'] = 'world' @hash.update({'hi' => 'mom'}) @hash.sweep assert_equal({'hello' => 'world', 'hi' => 'mom'}, @hash.to_hash) end def test_update_delete_sweep @hash['hello'] = 'world' @hash.delete 'hello' @hash.update({'hello' => 'mom'}) @hash.sweep assert_equal({'hello' => 'mom'}, @hash.to_hash) end def test_delete_sweep @hash['hello'] = 'world' @hash['hi'] = 'mom' @hash.delete 'hi' @hash.sweep assert_equal({'hello' => 'world'}, @hash.to_hash) end def test_clear_sweep @hash['hello'] = 'world' @hash.clear @hash.sweep assert_equal({}, @hash.to_hash) end def test_replace_sweep @hash['hello'] = 'world' @hash.replace({'hi' => 'mom'}) @hash.sweep assert_equal({'hi' => 'mom'}, @hash.to_hash) end def test_discard_then_add @hash['hello'] = 'world' @hash['omg'] = 'world' @hash.discard 'hello' @hash['hello'] = 'world' @hash.sweep assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash) end def test_keep_all_sweep @hash['hello'] = 'world' @hash['omg'] = 'world' @hash.discard 'hello' @hash.keep @hash.sweep assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash) end def test_double_sweep @hash['hello'] = 'world' @hash.sweep assert_equal({'hello' => 'world'}, @hash.to_hash) @hash.sweep assert_equal({}, @hash.to_hash) end end end rails-4.2.6/actionpack/test/controller/flash_test.rb000066400000000000000000000225001266740050600226010ustar00rootroot00000000000000require 'abstract_unit' require 'active_support/key_generator' class FlashTest < ActionController::TestCase class TestController < ActionController::Base def set_flash flash["that"] = "hello" render :inline => "hello" end def set_flash_now flash.now["that"] = "hello" flash.now["foo"] ||= "bar" flash.now["foo"] ||= "err" @flashy = flash.now["that"] @flash_copy = {}.update flash render :inline => "hello" end def attempt_to_use_flash_now @flash_copy = {}.update flash @flashy = flash["that"] render :inline => "hello" end def use_flash @flash_copy = {}.update flash @flashy = flash["that"] render :inline => "hello" end def use_flash_and_keep_it @flash_copy = {}.update flash @flashy = flash["that"] flash.keep render :inline => "hello" end def use_flash_and_update_it flash.update("this" => "hello again") @flash_copy = {}.update flash render :inline => "hello" end def use_flash_after_reset_session flash["that"] = "hello" @flashy_that = flash["that"] reset_session @flashy_that_reset = flash["that"] flash["this"] = "good-bye" @flashy_this = flash["this"] render :inline => "hello" end # methods for test_sweep_after_halted_action_chain before_action :halt_and_redir, only: 'filter_halting_action' def std_action @flash_copy = {}.update(flash) render :nothing => true end def filter_halting_action @flash_copy = {}.update(flash) end def halt_and_redir flash["foo"] = "bar" redirect_to :action => "std_action" @flash_copy = {}.update(flash) end def redirect_with_alert redirect_to '/nowhere', :alert => "Beware the nowheres!" end def redirect_with_notice redirect_to '/somewhere', :notice => "Good luck in the somewheres!" end def render_with_flash_now_alert flash.now.alert = "Beware the nowheres now!" render :inline => "hello" end def render_with_flash_now_notice flash.now.notice = "Good luck in the somewheres now!" render :inline => "hello" end def redirect_with_other_flashes redirect_to '/wonderland', :flash => { :joyride => "Horses!" } end def redirect_with_foo_flash redirect_to "/wonderland", :foo => 'for great justice' end end tests TestController def test_flash get :set_flash get :use_flash assert_equal "hello", assigns["flash_copy"]["that"] assert_equal "hello", assigns["flashy"] get :use_flash assert_nil assigns["flash_copy"]["that"], "On second flash" end def test_keep_flash get :set_flash get :use_flash_and_keep_it assert_equal "hello", assigns["flash_copy"]["that"] assert_equal "hello", assigns["flashy"] get :use_flash assert_equal "hello", assigns["flash_copy"]["that"], "On second flash" get :use_flash assert_nil assigns["flash_copy"]["that"], "On third flash" end def test_flash_now get :set_flash_now assert_equal "hello", assigns["flash_copy"]["that"] assert_equal "bar" , assigns["flash_copy"]["foo"] assert_equal "hello", assigns["flashy"] get :attempt_to_use_flash_now assert_nil assigns["flash_copy"]["that"] assert_nil assigns["flash_copy"]["foo"] assert_nil assigns["flashy"] end def test_update_flash get :set_flash get :use_flash_and_update_it assert_equal "hello", assigns["flash_copy"]["that"] assert_equal "hello again", assigns["flash_copy"]["this"] get :use_flash assert_nil assigns["flash_copy"]["that"], "On second flash" assert_equal "hello again", assigns["flash_copy"]["this"], "On second flash" end def test_flash_after_reset_session get :use_flash_after_reset_session assert_equal "hello", assigns["flashy_that"] assert_equal "good-bye", assigns["flashy_this"] assert_nil assigns["flashy_that_reset"] end def test_does_not_set_the_session_if_the_flash_is_empty get :std_action assert_nil session["flash"] end def test_sweep_after_halted_action_chain get :std_action assert_nil assigns["flash_copy"]["foo"] get :filter_halting_action assert_equal "bar", assigns["flash_copy"]["foo"] get :std_action # follow redirection assert_equal "bar", assigns["flash_copy"]["foo"] get :std_action assert_nil assigns["flash_copy"]["foo"] end def test_keep_and_discard_return_values flash = ActionDispatch::Flash::FlashHash.new flash.update(:foo => :foo_indeed, :bar => :bar_indeed) assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed assert_nil flash.discard(:unknown) # non existent key passed assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed assert_nil flash.keep(:unknown) # non existent key passed assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed end def test_redirect_to_with_alert get :redirect_with_alert assert_equal "Beware the nowheres!", @controller.send(:flash)[:alert] end def test_redirect_to_with_notice get :redirect_with_notice assert_equal "Good luck in the somewheres!", @controller.send(:flash)[:notice] end def test_render_with_flash_now_alert get :render_with_flash_now_alert assert_equal "Beware the nowheres now!", @controller.send(:flash)[:alert] end def test_render_with_flash_now_notice get :render_with_flash_now_notice assert_equal "Good luck in the somewheres now!", @controller.send(:flash)[:notice] end def test_redirect_to_with_other_flashes get :redirect_with_other_flashes assert_equal "Horses!", @controller.send(:flash)[:joyride] end def test_redirect_to_with_adding_flash_types original_controller = @controller test_controller_with_flash_type_foo = Class.new(TestController) do add_flash_types :foo end @controller = test_controller_with_flash_type_foo.new get :redirect_with_foo_flash assert_equal "for great justice", @controller.send(:flash)[:foo] ensure @controller = original_controller end def test_add_flash_type_to_subclasses test_controller_with_flash_type_foo = Class.new(TestController) do add_flash_types :foo end subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo) assert subclass_controller_with_no_flash_type._flash_types.include?(:foo) end def test_does_not_add_flash_type_to_parent_class Class.new(TestController) do add_flash_types :bar end assert_not TestController._flash_types.include?(:bar) end end class FlashIntegrationTest < ActionDispatch::IntegrationTest SessionKey = '_myapp_session' Generator = ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') class TestController < ActionController::Base add_flash_types :bar def set_flash flash["that"] = "hello" head :ok end def set_flash_now flash.now["that"] = "hello" head :ok end def use_flash render :inline => "flash: #{flash["that"]}" end def set_bar flash[:bar] = "for great justice" head :ok end end def test_flash with_test_route_set do get '/set_flash' assert_response :success assert_equal "hello", @request.flash["that"] get '/use_flash' assert_response :success assert_equal "flash: hello", @response.body end end def test_just_using_flash_does_not_stream_a_cookie_back with_test_route_set do get '/use_flash' assert_response :success assert_nil @response.headers["Set-Cookie"] assert_equal "flash: ", @response.body end end def test_setting_flash_does_not_raise_in_following_requests with_test_route_set do env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } get '/set_flash', nil, env get '/set_flash', nil, env end end def test_setting_flash_now_does_not_raise_in_following_requests with_test_route_set do env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } get '/set_flash_now', nil, env get '/set_flash_now', nil, env end end def test_added_flash_types_method with_test_route_set do get '/set_bar' assert_response :success assert_equal 'for great justice', @controller.bar end end private # Overwrite get to send SessionSecret in env hash def get(path, parameters = nil, env = {}) env["action_dispatch.key_generator"] ||= Generator super end def with_test_route_set with_routing do |set| set.draw do get ':action', :to => FlashIntegrationTest::TestController end @app = self.class.build_app(set) do |middleware| middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey middleware.use ActionDispatch::Flash middleware.delete "ActionDispatch::ShowExceptions" end yield end end end rails-4.2.6/actionpack/test/controller/force_ssl_test.rb000066400000000000000000000215031266740050600234650ustar00rootroot00000000000000require 'abstract_unit' class ForceSSLController < ActionController::Base def banana render :text => "monkey" end def cheeseburger render :text => "sikachu" end end class ForceSSLControllerLevel < ForceSSLController force_ssl end class ForceSSLCustomOptions < ForceSSLController force_ssl :host => "secure.example.com", :only => :redirect_host force_ssl :port => 8443, :only => :redirect_port force_ssl :subdomain => 'secure', :only => :redirect_subdomain force_ssl :domain => 'secure.com', :only => :redirect_domain force_ssl :path => '/foo', :only => :redirect_path force_ssl :status => :found, :only => :redirect_status force_ssl :flash => { :message => 'Foo, Bar!' }, :only => :redirect_flash force_ssl :alert => 'Foo, Bar!', :only => :redirect_alert force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice def force_ssl_action render :text => action_name end alias_method :redirect_host, :force_ssl_action alias_method :redirect_port, :force_ssl_action alias_method :redirect_subdomain, :force_ssl_action alias_method :redirect_domain, :force_ssl_action alias_method :redirect_path, :force_ssl_action alias_method :redirect_status, :force_ssl_action alias_method :redirect_flash, :force_ssl_action alias_method :redirect_alert, :force_ssl_action alias_method :redirect_notice, :force_ssl_action def use_flash render :text => flash[:message] end def use_alert render :text => flash[:alert] end def use_notice render :text => flash[:notice] end end class ForceSSLOnlyAction < ForceSSLController force_ssl :only => :cheeseburger end class ForceSSLExceptAction < ForceSSLController force_ssl :except => :banana end class ForceSSLIfCondition < ForceSSLController force_ssl :if => :use_force_ssl? def use_force_ssl? action_name == 'cheeseburger' end end class ForceSSLFlash < ForceSSLController force_ssl :except => [:banana, :set_flash, :use_flash] def set_flash flash["that"] = "hello" redirect_to '/force_ssl_flash/cheeseburger' end def use_flash @flash_copy = {}.update flash @flashy = flash["that"] render :inline => "hello" end end class RedirectToSSL < ForceSSLController def banana force_ssl_redirect || render(:text => 'monkey') end def cheeseburger force_ssl_redirect('secure.cheeseburger.host') || render(:text => 'ihaz') end end class ForceSSLControllerLevelTest < ActionController::TestCase def test_banana_redirects_to_https get :banana assert_response 301 assert_equal "https://test.host/force_ssl_controller_level/banana", redirect_to_url end def test_banana_redirects_to_https_with_extra_params get :banana, :token => "secret" assert_response 301 assert_equal "https://test.host/force_ssl_controller_level/banana?token=secret", redirect_to_url end def test_cheeseburger_redirects_to_https get :cheeseburger assert_response 301 assert_equal "https://test.host/force_ssl_controller_level/cheeseburger", redirect_to_url end end class ForceSSLCustomOptionsTest < ActionController::TestCase def setup @request.env['HTTP_HOST'] = 'www.example.com:80' end def test_redirect_to_custom_host get :redirect_host assert_response 301 assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_host", redirect_to_url end def test_redirect_to_custom_port get :redirect_port assert_response 301 assert_equal "https://www.example.com:8443/force_ssl_custom_options/redirect_port", redirect_to_url end def test_redirect_to_custom_subdomain get :redirect_subdomain assert_response 301 assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_subdomain", redirect_to_url end def test_redirect_to_custom_domain get :redirect_domain assert_response 301 assert_equal "https://www.secure.com/force_ssl_custom_options/redirect_domain", redirect_to_url end def test_redirect_to_custom_path get :redirect_path assert_response 301 assert_equal "https://www.example.com/foo", redirect_to_url end def test_redirect_to_custom_status get :redirect_status assert_response 302 assert_equal "https://www.example.com/force_ssl_custom_options/redirect_status", redirect_to_url end def test_redirect_to_custom_flash get :redirect_flash assert_response 301 assert_equal "https://www.example.com/force_ssl_custom_options/redirect_flash", redirect_to_url get :use_flash assert_response 200 assert_equal "Foo, Bar!", @response.body end def test_redirect_to_custom_alert get :redirect_alert assert_response 301 assert_equal "https://www.example.com/force_ssl_custom_options/redirect_alert", redirect_to_url get :use_alert assert_response 200 assert_equal "Foo, Bar!", @response.body end def test_redirect_to_custom_notice get :redirect_notice assert_response 301 assert_equal "https://www.example.com/force_ssl_custom_options/redirect_notice", redirect_to_url get :use_notice assert_response 200 assert_equal "Foo, Bar!", @response.body end end class ForceSSLOnlyActionTest < ActionController::TestCase def test_banana_not_redirects_to_https get :banana assert_response 200 end def test_cheeseburger_redirects_to_https get :cheeseburger assert_response 301 assert_equal "https://test.host/force_ssl_only_action/cheeseburger", redirect_to_url end end class ForceSSLExceptActionTest < ActionController::TestCase def test_banana_not_redirects_to_https get :banana assert_response 200 end def test_cheeseburger_redirects_to_https get :cheeseburger assert_response 301 assert_equal "https://test.host/force_ssl_except_action/cheeseburger", redirect_to_url end end class ForceSSLIfConditionTest < ActionController::TestCase def test_banana_not_redirects_to_https get :banana assert_response 200 end def test_cheeseburger_redirects_to_https get :cheeseburger assert_response 301 assert_equal "https://test.host/force_ssl_if_condition/cheeseburger", redirect_to_url end end class ForceSSLFlashTest < ActionController::TestCase def test_cheeseburger_redirects_to_https get :set_flash assert_response 302 assert_equal "http://test.host/force_ssl_flash/cheeseburger", redirect_to_url # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists @request.env.delete('PATH_INFO') get :cheeseburger assert_response 301 assert_equal "https://test.host/force_ssl_flash/cheeseburger", redirect_to_url # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists @request.env.delete('PATH_INFO') get :use_flash assert_equal "hello", assigns["flash_copy"]["that"] assert_equal "hello", assigns["flashy"] end end class ForceSSLDuplicateRoutesTest < ActionController::TestCase tests ForceSSLControllerLevel def test_force_ssl_redirects_to_same_path with_routing do |set| set.draw do get '/foo', :to => 'force_ssl_controller_level#banana' get '/bar', :to => 'force_ssl_controller_level#banana' end @request.env['PATH_INFO'] = '/bar' get :banana assert_response 301 assert_equal 'https://test.host/bar', redirect_to_url end end end class ForceSSLFormatTest < ActionController::TestCase tests ForceSSLControllerLevel def test_force_ssl_redirects_to_same_format with_routing do |set| set.draw do get '/foo', :to => 'force_ssl_controller_level#banana' end get :banana, :format => :json assert_response 301 assert_equal 'https://test.host/foo.json', redirect_to_url end end end class ForceSSLOptionalSegmentsTest < ActionController::TestCase tests ForceSSLControllerLevel def test_force_ssl_redirects_to_same_format with_routing do |set| set.draw do scope '(:locale)' do defaults :locale => 'en' do get '/foo', :to => 'force_ssl_controller_level#banana' end end end @request.env['PATH_INFO'] = '/en/foo' get :banana, :locale => 'en' assert_equal 'en', @controller.params[:locale] assert_response 301 assert_equal 'https://test.host/en/foo', redirect_to_url end end end class RedirectToSSLTest < ActionController::TestCase def test_banana_redirects_to_https_if_not_https get :banana assert_response 301 assert_equal "https://test.host/redirect_to_ssl/banana", redirect_to_url end def test_cheeseburgers_redirects_to_https_with_new_host_if_not_https get :cheeseburger assert_response 301 assert_equal "https://secure.cheeseburger.host/redirect_to_ssl/cheeseburger", redirect_to_url end def test_banana_does_not_redirect_if_already_https request.env['HTTPS'] = 'on' get :cheeseburger assert_response 200 assert_equal 'ihaz', response.body end end rails-4.2.6/actionpack/test/controller/helper_test.rb000066400000000000000000000166641266740050600230010ustar00rootroot00000000000000require 'abstract_unit' ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__) module Fun class GamesController < ActionController::Base def render_hello_world render :inline => "hello: <%= stratego %>" end end class PdfController < ActionController::Base def test render :inline => "test: <%= foobar %>" end end end class AllHelpersController < ActionController::Base helper :all end module ImpressiveLibrary extend ActiveSupport::Concern included do helper_method :useful_function end def useful_function() end end ActionController::Base.send :include, ImpressiveLibrary class JustMeController < ActionController::Base clear_helpers def flash render :inline => "

<%= notice %>

" end def lib render :inline => '<%= useful_function %>' end end class MeTooController < JustMeController end class HelpersPathsController < ActionController::Base paths = ["helpers2_pack", "helpers1_pack"].map do |path| File.join(File.expand_path('../../fixtures', __FILE__), path) end $:.unshift(*paths) self.helpers_path = paths helper :all def index render :inline => "<%= conflicting_helper %>" end end class HelpersTypoController < ActionController::Base path = File.expand_path('../../fixtures/helpers_typo', __FILE__) $:.unshift(path) self.helpers_path = path end module LocalAbcHelper def a() end def b() end def c() end end class HelperPathsTest < ActiveSupport::TestCase def setup @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end def test_helpers_paths_priority request = ActionController::TestRequest.new responses = HelpersPathsController.action(:index).call(request.env) # helpers1_pack was given as a second path, so pack1_helper should be # included as the second one assert_equal "pack1", responses.last.body end end class HelpersTypoControllerTest < ActiveSupport::TestCase def setup @autoload_paths = ActiveSupport::Dependencies.autoload_paths ActiveSupport::Dependencies.autoload_paths = Array(HelpersTypoController.helpers_path) end def test_helper_typo_error_message e = assert_raise(NameError) { HelpersTypoController.helper 'admin/users' } assert_equal "Couldn't find Admin::UsersHelper, expected it to be defined in helpers/admin/users_helper.rb", e.message end def teardown ActiveSupport::Dependencies.autoload_paths = @autoload_paths end end class HelperTest < ActiveSupport::TestCase class TestController < ActionController::Base attr_accessor :delegate_attr def delegate_method() end end def setup # Increment symbol counter. @symbol = (@@counter ||= 'A0').succ!.dup # Generate new controller class. controller_class_name = "Helper#{@symbol}Controller" eval("class #{controller_class_name} < TestController; end") @controller_class = self.class.const_get(controller_class_name) # Set default test helper. self.test_helper = LocalAbcHelper end def test_helper assert_equal expected_helper_methods, missing_methods assert_nothing_raised { @controller_class.helper TestHelper } assert_equal [], missing_methods end def test_helper_method assert_nothing_raised { @controller_class.helper_method :delegate_method } assert master_helper_methods.include?(:delegate_method) end def test_helper_attr assert_nothing_raised { @controller_class.helper_attr :delegate_attr } assert master_helper_methods.include?(:delegate_attr) assert master_helper_methods.include?(:delegate_attr=) end def call_controller(klass, action) request = ActionController::TestRequest.new klass.action(action).call(request.env) end def test_helper_for_nested_controller assert_equal 'hello: Iz guuut!', call_controller(Fun::GamesController, "render_hello_world").last.body # request = ActionController::TestRequest.new # # resp = Fun::GamesController.action(:render_hello_world).call(request.env) # assert_equal 'hello: Iz guuut!', resp.last.body end def test_helper_for_acronym_controller assert_equal "test: baz", call_controller(Fun::PdfController, "test").last.body # # request = ActionController::TestRequest.new # response = ActionController::TestResponse.new # request.action = 'test' # # assert_equal 'test: baz', Fun::PdfController.process(request, response).body end def test_default_helpers_only assert_equal [JustMeHelper], JustMeController._helpers.ancestors.reject(&:anonymous?) assert_equal [MeTooHelper, JustMeHelper], MeTooController._helpers.ancestors.reject(&:anonymous?) end def test_base_helper_methods_after_clear_helpers assert_nothing_raised do call_controller(JustMeController, "flash") end end def test_lib_helper_methods_after_clear_helpers assert_nothing_raised do call_controller(JustMeController, "lib") end end def test_all_helpers methods = AllHelpersController._helpers.instance_methods # abc_helper.rb assert methods.include?(:bare_a) # fun/games_helper.rb assert methods.include?(:stratego) # fun/pdf_helper.rb assert methods.include?(:foobar) end def test_all_helpers_with_alternate_helper_dir @controller_class.helpers_path = File.expand_path('../../fixtures/alternate_helpers', __FILE__) # Reload helpers @controller_class._helpers = Module.new @controller_class.helper :all # helpers/abc_helper.rb should not be included assert !master_helper_methods.include?(:bare_a) # alternate_helpers/foo_helper.rb assert master_helper_methods.include?(:baz) end def test_helper_proxy methods = AllHelpersController.helpers.methods # Action View assert methods.include?(:pluralize) # abc_helper.rb assert methods.include?(:bare_a) # fun/games_helper.rb assert methods.include?(:stratego) # fun/pdf_helper.rb assert methods.include?(:foobar) end def test_helper_proxy_config AllHelpersController.config.my_var = 'smth' assert_equal 'smth', AllHelpersController.helpers.config.my_var end private def expected_helper_methods TestHelper.instance_methods end def master_helper_methods @controller_class._helpers.instance_methods end def missing_methods expected_helper_methods - master_helper_methods end def test_helper=(helper_module) silence_warnings { self.class.const_set('TestHelper', helper_module) } end end class IsolatedHelpersTest < ActiveSupport::TestCase class A < ActionController::Base def index render :inline => '<%= shout %>' end end class B < A helper { def shout; 'B' end } def index render :inline => '<%= shout %>' end end class C < A helper { def shout; 'C' end } def index render :inline => '<%= shout %>' end end def call_controller(klass, action) request = ActionController::TestRequest.new klass.action(action).call(request.env) end def setup @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @request.action = 'index' end def test_helper_in_a assert_raise(ActionView::Template::Error) { call_controller(A, "index") } end def test_helper_in_b assert_equal 'B', call_controller(B, "index").last.body end def test_helper_in_c assert_equal 'C', call_controller(C, "index").last.body end end rails-4.2.6/actionpack/test/controller/http_basic_authentication_test.rb000066400000000000000000000114521266740050600267270ustar00rootroot00000000000000require 'abstract_unit' class HttpBasicAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base before_action :authenticate, only: :index before_action :authenticate_with_request, only: :display before_action :authenticate_long_credentials, only: :show http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search def index render :text => "Hello Secret" end def display render :text => 'Definitely Maybe' end def show render :text => 'Only for loooooong credentials' end def search render :text => 'All inline' end private def authenticate authenticate_or_request_with_http_basic do |username, password| username == 'lifo' && password == 'world' end end def authenticate_with_request if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' } @logged_in = true else request_http_basic_authentication("SuperSecret") end end def authenticate_long_credentials authenticate_or_request_with_http_basic do |username, password| username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890' end end end AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] tests DummyController AUTH_HEADERS.each do |header| test "successful authentication with #{header.downcase}" do @request.env[header] = encode_credentials('lifo', 'world') get :index assert_response :success assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" end test "successful authentication with #{header.downcase} and long credentials" do @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890') get :show assert_response :success assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials" end end AUTH_HEADERS.each do |header| test "unsuccessful authentication with #{header.downcase}" do @request.env[header] = encode_credentials('h4x0r', 'world') get :index assert_response :unauthorized assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" end test "unsuccessful authentication with #{header.downcase} and long credentials" do @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r', 'worldworldworldworldworldworldworldworld') get :show assert_response :unauthorized assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials" end end def test_encode_credentials_has_no_newline username = 'laskjdfhalksdjfhalkjdsfhalksdjfhklsdjhalksdjfhalksdjfhlakdsjfh' password = 'kjfhueyt9485osdfasdkljfh4lkjhakldjfhalkdsjf' result = ActionController::HttpAuthentication::Basic.encode_credentials( username, password) assert_no_match(/\n/, result) end test "authentication request without credential" do get :display assert_response :unauthorized assert_equal "HTTP Basic: Access denied.\n", @response.body assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate'] end test "authentication request with invalid credential" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'foo') get :display assert_response :unauthorized assert_equal "HTTP Basic: Access denied.\n", @response.body assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate'] end test "authentication request with valid credential" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'please') get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "authenticate with class method" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath') get :search assert_response :success @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'WRONG!') get :search assert_response :unauthorized end test "authentication request with wrong scheme" do header = 'Bearer ' + encode_credentials('David', 'Goliath').split(' ', 2)[1] @request.env['HTTP_AUTHORIZATION'] = header get :search assert_response :unauthorized end private def encode_credentials(username, password) "Basic #{::Base64.encode64("#{username}:#{password}")}" end end rails-4.2.6/actionpack/test/controller/http_digest_authentication_test.rb000066400000000000000000000245411266740050600271300ustar00rootroot00000000000000require 'abstract_unit' require 'active_support/key_generator' class HttpDigestAuthenticationTest < ActionController::TestCase class DummyDigestController < ActionController::Base before_action :authenticate, only: :index before_action :authenticate_with_request, only: :display USERS = { 'lifo' => 'world', 'pretty' => 'please', 'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))} def index render :text => "Hello Secret" end def display render :text => 'Definitely Maybe' end private def authenticate authenticate_or_request_with_http_digest("SuperSecret") do |username| # Returns the password USERS[username] end end def authenticate_with_request if authenticate_with_http_digest("SuperSecret") { |username| USERS[username] } @logged_in = true else request_http_digest_authentication("SuperSecret", "Authentication Failed") end end end AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] tests DummyDigestController setup do # Used as secret in generating nonce to prevent tampering of timestamp @secret = "4fb45da9e4ab4ddeb7580d6a35503d99" @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(@secret) end teardown do # ActionController::Base.session_options[:secret] = @old_secret end AUTH_HEADERS.each do |header| test "successful authentication with #{header.downcase}" do @request.env[header] = encode_credentials(:username => 'lifo', :password => 'world') get :index assert_response :success assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" end end AUTH_HEADERS.each do |header| test "unsuccessful authentication with #{header.downcase}" do @request.env[header] = encode_credentials(:username => 'h4x0r', :password => 'world') get :index assert_response :unauthorized assert_equal "HTTP Digest: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" end end test "authentication request without credential" do get :display assert_response :unauthorized assert_equal "Authentication Failed", @response.body credentials = decode_credentials(@response.headers['WWW-Authenticate']) assert_equal 'SuperSecret', credentials[:realm] end test "authentication request with nil credentials" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => nil, :password => nil) get :index assert_response :unauthorized assert_equal "HTTP Digest: Access denied.\n", @response.body, "Authentication didn't fail for request" assert_not_equal 'Hello Secret', @response.body, "Authentication didn't fail for request" end test "authentication request with invalid password" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo') get :display assert_response :unauthorized assert_equal "Authentication Failed", @response.body end test "authentication request with invalid nonce" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :nonce => "xxyyzz") get :display assert_response :unauthorized assert_equal "Authentication Failed", @response.body end test "authentication request with invalid opaque" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :opaque => "xxyyzz") get :display assert_response :unauthorized assert_equal "Authentication Failed", @response.body end test "authentication request with invalid realm" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :realm => "NotSecret") get :display assert_response :unauthorized assert_equal "Authentication Failed", @response.body end test "authentication request with valid credential" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "authentication request with valid credential and nil session" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "authentication request with request-uri that doesn't match credentials digest-uri" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') @request.env['PATH_INFO'] = "/proxied/uri" get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "authentication request with absolute request uri (as in webrick)" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') @request.env["SERVER_NAME"] = "test.host" @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "authentication request with absolute uri in credentials (as in IE)" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", :username => 'pretty', :password => 'please') get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", :username => 'pretty', :password => 'please') @request.env['SERVER_NAME'] = "test.host" @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "authentication request with password stored as ha1 digest hash" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'dhh', :password => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":")), :password_is_ha1 => true) get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "authentication request with _method" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :method => :post) @request.env['rack.methodoverride.original_method'] = 'POST' put :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "validate_digest_response should fail with nil returning password_procedure" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => nil, :password => nil) assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil} end test "authentication request with request-uri ending in '/'" do @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/" @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') # simulate normalizing PATH_INFO @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success assert_equal 'Definitely Maybe', @response.body end test "authentication request with request-uri ending in '?'" do @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/?" @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') # simulate normalizing PATH_INFO @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success assert_equal 'Definitely Maybe', @response.body end test "authentication request with absolute uri in credentials (as in IE) ending with /" do @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/" @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/", :username => 'pretty', :password => 'please') # simulate normalizing PATH_INFO @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end test "when sent a basic auth header, returns Unauthorized" do @request.env['HTTP_AUTHORIZATION'] = 'Basic Gwf2aXq8ZLF3Hxq=' get :display assert_response :unauthorized end private def encode_credentials(options) options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b", :password_is_ha1 => false) password = options.delete(:password) # Perform unauthenticated request to retrieve digest parameters to use on subsequent request method = options.delete(:method) || 'GET' case method.to_s.upcase when 'GET' get :index when 'POST' post :index end assert_response :unauthorized credentials = decode_credentials(@response.headers['WWW-Authenticate']) credentials.merge!(options) path_info = @request.env['PATH_INFO'].to_s uri = options[:uri] || path_info credentials.merge!(:uri => uri) @request.env["ORIGINAL_FULLPATH"] = path_info ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) end def decode_credentials(header) ActionController::HttpAuthentication::Digest.decode_credentials(header) end end rails-4.2.6/actionpack/test/controller/http_token_authentication_test.rb000066400000000000000000000155161266740050600267730ustar00rootroot00000000000000require 'abstract_unit' class HttpTokenAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base before_action :authenticate, only: :index before_action :authenticate_with_request, only: :display before_action :authenticate_long_credentials, only: :show def index render :text => "Hello Secret" end def display render :text => 'Definitely Maybe' end def show render :text => 'Only for loooooong credentials' end private def authenticate authenticate_or_request_with_http_token do |token, _| token == 'lifo' end end def authenticate_with_request if authenticate_with_http_token { |token, options| token == '"quote" pretty' && options[:algorithm] == 'test' } @logged_in = true else request_http_token_authentication("SuperSecret") end end def authenticate_long_credentials authenticate_or_request_with_http_token do |token, options| token == '1234567890123456789012345678901234567890' && options[:algorithm] == 'test' end end end AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] tests DummyController AUTH_HEADERS.each do |header| test "successful authentication with #{header.downcase}" do @request.env[header] = encode_credentials('lifo') get :index assert_response :success assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" end test "successful authentication with #{header.downcase} and long credentials" do @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', :algorithm => 'test') get :show assert_response :success assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials" end end AUTH_HEADERS.each do |header| test "unsuccessful authentication with #{header.downcase}" do @request.env[header] = encode_credentials('h4x0r') get :index assert_response :unauthorized assert_equal "HTTP Token: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" end test "unsuccessful authentication with #{header.downcase} and long credentials" do @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r') get :show assert_response :unauthorized assert_equal "HTTP Token: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials" end end test "authentication request with badly formatted header" do @request.env['HTTP_AUTHORIZATION'] = "Token foobar" get :index assert_response :unauthorized assert_equal "HTTP Token: Access denied.\n", @response.body, "Authentication header was not properly parsed" end test "authentication request without credential" do get :display assert_response :unauthorized assert_equal "HTTP Token: Access denied.\n", @response.body assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate'] end test "authentication request with invalid credential" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials('"quote" pretty') get :display assert_response :unauthorized assert_equal "HTTP Token: Access denied.\n", @response.body assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate'] end test "token_and_options returns correct token" do token = "rcHu+HzSFw89Ypyhn/896A==" actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) end test "token_and_options returns correct token with value after the equal sign" do token = 'rcHu+=HzSFw89Ypyhn/896A==f34' actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) end test "token_and_options returns correct token with slashes" do token = 'rcHu+\\\\"/896A' actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) end test "token_and_options returns correct token with quotes" do token = '\"quote\" pretty' actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) end test "token_and_options returns empty string with empty token" do token = '' actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) end test "token_and_options returns correct token with nounce option" do token = "rcHu+HzSFw89Ypyhn/896A=" nonce_hash = {nonce: "123abc"} actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token, nonce_hash)) expected_token = token expected_nonce = {"nonce" => nonce_hash[:nonce]} assert_equal(expected_token, actual.first) assert_equal(expected_nonce, actual.last) end test "token_and_options returns nil with no value after the equal sign" do actual = ActionController::HttpAuthentication::Token.token_and_options(malformed_request).first expected = nil assert_equal(expected, actual) end test "raw_params returns a tuple of two key value pair strings" do auth = sample_request("rcHu+HzSFw89Ypyhn/896A=").authorization.to_s actual = ActionController::HttpAuthentication::Token.raw_params(auth) expected = ["token=\"rcHu+HzSFw89Ypyhn/896A=\"", "nonce=\"def\""] assert_equal(expected, actual) end test "token_and_options returns right token when token key is not specified in header" do token = "rcHu+HzSFw89Ypyhn/896A=" actual = ActionController::HttpAuthentication::Token.token_and_options( sample_request_without_token_key(token) ).first expected = token assert_equal(expected, actual) end private def sample_request(token, options = {nonce: "def"}) authorization = options.inject([%{Token token="#{token}"}]) do |arr, (k, v)| arr << "#{k}=\"#{v}\"" end.join(", ") mock_authorization_request(authorization) end def malformed_request mock_authorization_request(%{Token token=}) end def sample_request_without_token_key(token) mock_authorization_request(%{Token #{token}}) end def mock_authorization_request(authorization) OpenStruct.new(authorization: authorization) end def encode_credentials(token, options = {}) ActionController::HttpAuthentication::Token.encode_credentials(token, options) end end rails-4.2.6/actionpack/test/controller/integration_test.rb000066400000000000000000000634451266740050600240440ustar00rootroot00000000000000require 'abstract_unit' require 'controller/fake_controllers' require 'rails/engine' class SessionTest < ActiveSupport::TestCase StubApp = lambda { |env| [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, ["Hello, World!"]] } def setup @session = ActionDispatch::Integration::Session.new(StubApp) end def test_https_bang_works_and_sets_truth_by_default assert !@session.https? @session.https! assert @session.https? @session.https! false assert !@session.https? end def test_host! assert_not_equal "glu.ttono.us", @session.host @session.host! "rubyonrails.com" assert_equal "rubyonrails.com", @session.host end def test_follow_redirect_raises_when_no_redirect @session.stubs(:redirect?).returns(false) assert_raise(RuntimeError) { @session.follow_redirect! } end def test_request_via_redirect_uses_given_method path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} @session.expects(:process).with(:put, path, args, headers) @session.stubs(:redirect?).returns(false) @session.request_via_redirect(:put, path, args, headers) end def test_request_via_redirect_follows_redirects path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} @session.stubs(:redirect?).returns(true, true, false) @session.expects(:follow_redirect!).times(2) @session.request_via_redirect(:get, path, args, headers) end def test_request_via_redirect_returns_status path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} @session.stubs(:redirect?).returns(false) @session.stubs(:status).returns(200) assert_equal 200, @session.request_via_redirect(:get, path, args, headers) end def test_get_via_redirect path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:get, path, args, headers) @session.get_via_redirect(path, args, headers) end def test_post_via_redirect path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:post, path, args, headers) @session.post_via_redirect(path, args, headers) end def test_patch_via_redirect path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:patch, path, args, headers) @session.patch_via_redirect(path, args, headers) end def test_put_via_redirect path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:put, path, args, headers) @session.put_via_redirect(path, args, headers) end def test_delete_via_redirect path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:delete, path, args, headers) @session.delete_via_redirect(path, args, headers) end def test_get path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:get,path,params,headers) @session.get(path,params,headers) end def test_post path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:post,path,params,headers) @session.post(path,params,headers) end def test_patch path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:patch,path,params,headers) @session.patch(path,params,headers) end def test_put path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:put,path,params,headers) @session.put(path,params,headers) end def test_delete path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:delete,path,params,headers) @session.delete(path,params,headers) end def test_head path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:head,path,params,headers) @session.head(path,params,headers) end def test_xml_http_request_get path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) @session.expects(:process).with(:get,path,params,headers_after_xhr) @session.xml_http_request(:get,path,params,headers) end def test_xml_http_request_post path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) @session.expects(:process).with(:post,path,params,headers_after_xhr) @session.xml_http_request(:post,path,params,headers) end def test_xml_http_request_patch path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) @session.expects(:process).with(:patch,path,params,headers_after_xhr) @session.xml_http_request(:patch,path,params,headers) end def test_xml_http_request_put path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) @session.expects(:process).with(:put,path,params,headers_after_xhr) @session.xml_http_request(:put,path,params,headers) end def test_xml_http_request_delete path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) @session.expects(:process).with(:delete,path,params,headers_after_xhr) @session.xml_http_request(:delete,path,params,headers) end def test_xml_http_request_head path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) @session.expects(:process).with(:head,path,params,headers_after_xhr) @session.xml_http_request(:head,path,params,headers) end def test_xml_http_request_override_accept path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"} headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" ) @session.expects(:process).with(:post,path,params,headers_after_xhr) @session.xml_http_request(:post,path,params,headers) end end class IntegrationTestTest < ActiveSupport::TestCase def setup @test = ::ActionDispatch::IntegrationTest.new(:app) @test.class.stubs(:fixture_table_names).returns([]) @session = @test.open_session end def test_opens_new_session session1 = @test.open_session { |sess| } session2 = @test.open_session # implicit session assert_respond_to session1, :assert_template, "open_session makes assert_template available" assert_respond_to session2, :assert_template, "open_session makes assert_template available" assert !session1.equal?(session2) end # RSpec mixes Matchers (which has a #method_missing) into # IntegrationTest's superclass. Make sure IntegrationTest does not # try to delegate these methods to the session object. def test_does_not_prevent_method_missing_passing_up_to_ancestors mixin = Module.new do def method_missing(name, *args) name.to_s == 'foo' ? 'pass' : super end end @test.class.superclass.__send__(:include, mixin) begin assert_equal 'pass', @test.foo ensure # leave other tests as unaffected as possible mixin.__send__(:remove_method, :method_missing) end end end # Tests that integration tests don't call Controller test methods for processing. # Integration tests have their own setup and teardown. class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest def self.fixture_table_names [] end def test_integration_methods_called reset! @integration_session.stubs(:generic_url_rewriter) @integration_session.stubs(:process) %w( get post head patch put delete ).each do |verb| assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') } end end end class IntegrationProcessTest < ActionDispatch::IntegrationTest class IntegrationController < ActionController::Base def get respond_to do |format| format.html { render :text => "OK", :status => 200 } format.js { render :text => "JS OK", :status => 200 } format.xml { render :xml => "", :status => 200 } format.rss { render :xml => "", :status => 200 } format.atom { render :xml => "", :status => 200 } end end def get_with_params render :text => "foo: #{params[:foo]}", :status => 200 end def post render :text => "Created", :status => 201 end def method render :text => "method: #{request.method.downcase}" end def cookie_monster cookies["cookie_1"] = nil cookies["cookie_3"] = "chocolate" render :text => "Gone", :status => 410 end def set_cookie cookies["foo"] = 'bar' head :ok end def get_cookie render :text => cookies["foo"] end def redirect redirect_to action_url('get') end def remove_header response.headers.delete params[:header] head :ok, 'c' => '3' end end def test_get with_test_route_set do get '/get' assert_equal 200, status assert_equal "OK", status_message assert_response 200 assert_response :success assert_response :ok assert_equal({}, cookies.to_hash) assert_equal "OK", body assert_equal "OK", response.body assert_kind_of Nokogiri::HTML::Document, html_document assert_equal 1, request_count end end def test_get_xml_rss_atom %w[ application/xml application/rss+xml application/atom+xml ].each do |mime_string| with_test_route_set do get "/get", {}, {"HTTP_ACCEPT" => mime_string} assert_equal 200, status assert_equal "OK", status_message assert_response 200 assert_response :success assert_response :ok assert_equal({}, cookies.to_hash) assert_equal "", body assert_equal "", response.body assert_instance_of Nokogiri::XML::Document, html_document assert_equal 1, request_count end end end def test_post with_test_route_set do post '/post' assert_equal 201, status assert_equal "Created", status_message assert_response 201 assert_response :success assert_response :created assert_equal({}, cookies.to_hash) assert_equal "Created", body assert_equal "Created", response.body assert_kind_of Nokogiri::HTML::Document, html_document assert_equal 1, request_count end end test 'response cookies are added to the cookie jar for the next request' do with_test_route_set do self.cookies['cookie_1'] = "sugar" self.cookies['cookie_2'] = "oatmeal" get '/cookie_monster' assert_equal "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"] assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies.to_hash) end end test 'cookie persist to next request' do with_test_route_set do get '/set_cookie' assert_response :success assert_equal "foo=bar; path=/", headers["Set-Cookie"] assert_equal({"foo"=>"bar"}, cookies.to_hash) get '/get_cookie' assert_response :success assert_equal "bar", body assert_equal nil, headers["Set-Cookie"] assert_equal({"foo"=>"bar"}, cookies.to_hash) end end test 'cookie persist to next request on another domain' do with_test_route_set do host! "37s.backpack.test" get '/set_cookie' assert_response :success assert_equal "foo=bar; path=/", headers["Set-Cookie"] assert_equal({"foo"=>"bar"}, cookies.to_hash) get '/get_cookie' assert_response :success assert_equal "bar", body assert_equal nil, headers["Set-Cookie"] assert_equal({"foo"=>"bar"}, cookies.to_hash) end end def test_redirect with_test_route_set do get '/redirect' assert_equal 302, status assert_equal "Found", status_message assert_response 302 assert_response :redirect assert_response :found assert_equal "You are being redirected.", response.body assert_kind_of Nokogiri::HTML::Document, html_document assert_equal 1, request_count follow_redirect! assert_response :success assert_equal "/get", path get '/moved' assert_response :redirect assert_redirected_to '/method' end end def test_xml_http_request_get with_test_route_set do xhr :get, '/get' assert_equal 200, status assert_equal "OK", status_message assert_response 200 assert_response :success assert_response :ok assert_equal "JS OK", response.body end end def test_request_with_bad_format with_test_route_set do xhr :get, '/get.php' assert_equal 406, status assert_response 406 assert_response :not_acceptable end end def test_get_with_query_string with_test_route_set do get '/get_with_params?foo=bar' assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"] assert_equal '/get_with_params?foo=bar', request.fullpath assert_equal "foo=bar", request.env["QUERY_STRING"] assert_equal 'foo=bar', request.query_string assert_equal 'bar', request.parameters['foo'] assert_equal 200, status assert_equal "foo: bar", response.body end end def test_get_with_parameters with_test_route_set do get '/get_with_params', :foo => "bar" assert_equal '/get_with_params', request.env["PATH_INFO"] assert_equal '/get_with_params', request.path_info assert_equal 'foo=bar', request.env["QUERY_STRING"] assert_equal 'foo=bar', request.query_string assert_equal 'bar', request.parameters['foo'] assert_equal 200, status assert_equal "foo: bar", response.body end end def test_head with_test_route_set do head '/get' assert_equal 200, status assert_equal "", body head '/post' assert_equal 201, status assert_equal "", body get '/get/method' assert_equal 200, status assert_equal "method: get", body head '/get/method' assert_equal 200, status assert_equal "", body end end def test_generate_url_with_controller assert_equal 'http://www.example.com/foo', url_for(:controller => "foo") end def test_port_via_host! with_test_route_set do host! 'www.example.com:8080' get '/get' assert_equal 8080, request.port end end def test_port_via_process with_test_route_set do get 'http://www.example.com:8080/get' assert_equal 8080, request.port end end def test_https_and_port_via_host_and_https! with_test_route_set do host! 'www.example.com' https! true get '/get' assert_equal 443, request.port assert_equal true, request.ssl? host! 'www.example.com:443' https! true get '/get' assert_equal 443, request.port assert_equal true, request.ssl? host! 'www.example.com:8443' https! true get '/get' assert_equal 8443, request.port assert_equal true, request.ssl? end end def test_https_and_port_via_process with_test_route_set do get 'https://www.example.com/get' assert_equal 443, request.port assert_equal true, request.ssl? get 'https://www.example.com:8443/get' assert_equal 8443, request.port assert_equal true, request.ssl? end end def test_respect_removal_of_default_headers_by_a_controller_action with_test_route_set do with_default_headers 'a' => '1', 'b' => '2' do get '/remove_header', header: 'a' end end assert_not_includes @response.headers, 'a', 'Response should not include default header removed by the controller action' assert_includes @response.headers, 'b' assert_includes @response.headers, 'c' end private def with_default_headers(headers) original = ActionDispatch::Response.default_headers ActionDispatch::Response.default_headers = headers yield ensure ActionDispatch::Response.default_headers = original end def with_test_route_set with_routing do |set| controller = ::IntegrationProcessTest::IntegrationController.clone controller.class_eval do include set.url_helpers end set.draw do get 'moved' => redirect('/method') match ':action', :to => controller, :via => [:get, :post], :as => :action get 'get/:action', :to => controller, :as => :get_action end self.singleton_class.send(:include, set.url_helpers) yield end end end class MetalIntegrationTest < ActionDispatch::IntegrationTest include SharedTestRoutes.url_helpers class Poller def self.call(env) if env["PATH_INFO"] =~ /^\/success/ [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, ["Hello World!"]] else [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []] end end end def setup @app = Poller end def test_successful_get get "/success" assert_response 200 assert_response :success assert_response :ok assert_equal "Hello World!", response.body end def test_failed_get get "/failure" assert_response 404 assert_response :not_found assert_equal '', response.body end def test_generate_url_without_controller assert_equal 'http://www.example.com/foo', url_for(:controller => "foo") end def test_pass_headers get "/success", {}, "Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com" assert_equal "http://nohost.com", @request.env["HTTP_HOST"] assert_equal "http://www.example.com/foo", @request.env["HTTP_REFERER"] end def test_pass_env get "/success", {}, "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com" assert_equal "http://test.com", @request.env["HTTP_HOST"] assert_equal "http://test.com/", @request.env["HTTP_REFERER"] end end class ApplicationIntegrationTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base def index render :text => "index" end end def self.call(env) routes.call(env) end def self.routes @routes ||= ActionDispatch::Routing::RouteSet.new end class MountedApp < Rails::Engine def self.routes @routes ||= ActionDispatch::Routing::RouteSet.new end routes.draw do get 'baz', :to => 'application_integration_test/test#index', :as => :baz end def self.call(*) end end routes.draw do get '', :to => 'application_integration_test/test#index', :as => :empty_string get 'foo', :to => 'application_integration_test/test#index', :as => :foo get 'bar', :to => 'application_integration_test/test#index', :as => :bar mount MountedApp => '/mounted', :as => "mounted" get 'fooz' => proc { |env| [ 200, {'X-Cascade' => 'pass'}, [ "omg" ] ] }, :anchor => false get 'fooz', :to => 'application_integration_test/test#index' end def app self.class end test "includes route helpers" do assert_equal '/', empty_string_path assert_equal '/foo', foo_path assert_equal '/bar', bar_path end test "includes mounted helpers" do assert_equal '/mounted/baz', mounted.baz_path end test "path after cascade pass" do get '/fooz' assert_equal 'index', response.body assert_equal '/fooz', path end test "route helpers after controller access" do get '/' assert_equal '/', empty_string_path get '/foo' assert_equal '/foo', foo_path get '/bar' assert_equal '/bar', bar_path end test "missing route helper before controller access" do assert_raise(NameError) { missing_path } end test "missing route helper after controller access" do get '/foo' assert_raise(NameError) { missing_path } end test "process do not modify the env passed as argument" do env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' } old_env = env.dup get '/foo', nil, env assert_equal old_env, env end end class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base def post render :text => "Created", :status => 201 end end def self.call(env) env["action_dispatch.parameter_filter"] = [:password] routes.call(env) end def self.routes @routes ||= ActionDispatch::Routing::RouteSet.new end routes.draw do match '/post', :to => 'environment_filter_integration_test/test#post', :via => :post end def app self.class end test "filters rack request form vars" do post "/post", :username => 'cjolly', :password => 'secret' assert_equal 'cjolly', request.filtered_parameters['username'] assert_equal '[FILTERED]', request.filtered_parameters['password'] assert_equal '[FILTERED]', request.filtered_env['rack.request.form_vars'] end end class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest class FooController < ActionController::Base def index render :text => "foo#index" end def show render :text => "foo#show" end def edit render :text => "foo#show" end end class BarController < ActionController::Base def default_url_options { :host => "bar.com" } end def index render :text => "foo#index" end end def self.routes @routes ||= ActionDispatch::Routing::RouteSet.new end def self.call(env) routes.call(env) end def app self.class end routes.draw do default_url_options :host => "foo.com" scope :module => "url_options_integration_test" do get "/foo" => "foo#index", :as => :foos get "/foo/:id" => "foo#show", :as => :foo get "/foo/:id/edit" => "foo#edit", :as => :edit_foo get "/bar" => "bar#index", :as => :bars end end test "session uses default url options from routes" do assert_equal "http://foo.com/foo", foos_url end test "current host overrides default url options from routes" do get "/foo" assert_response :success assert_equal "http://www.example.com/foo", foos_url end test "controller can override default url options from request" do get "/bar" assert_response :success assert_equal "http://bar.com/foo", foos_url end def test_can_override_default_url_options original_host = default_url_options.dup default_url_options[:host] = "foobar.com" assert_equal "http://foobar.com/foo", foos_url get "/bar" assert_response :success assert_equal "http://foobar.com/foo", foos_url ensure ActionDispatch::Integration::Session.default_url_options = self.default_url_options = original_host end test "current request path parameters are recalled" do get "/foo/1" assert_response :success assert_equal "/foo/1/edit", url_for(:action => 'edit', :only_path => true) end end class HeadWithStatusActionIntegrationTest < ActionDispatch::IntegrationTest class FooController < ActionController::Base def status head :ok end end def self.routes @routes ||= ActionDispatch::Routing::RouteSet.new end def self.call(env) routes.call(env) end def app self.class end routes.draw do get "/foo/status" => 'head_with_status_action_integration_test/foo#status' end test "get /foo/status with head result does not cause stack overflow error" do assert_nothing_raised do get '/foo/status' end assert_response :ok end end class IntegrationWithRoutingTest < ActionDispatch::IntegrationTest class FooController < ActionController::Base def index render plain: 'ok' end end def test_with_routing_resets_session klass_namespace = self.class.name.underscore with_routing do |routes| routes.draw do namespace klass_namespace do resources :foo, path: '/with' end end get '/integration_with_routing_test/with' assert_response 200 assert_equal 'ok', response.body end with_routing do |routes| routes.draw do namespace klass_namespace do resources :foo, path: '/routing' end end get '/integration_with_routing_test/routing' assert_response 200 assert_equal 'ok', response.body end end end # to work in contexts like rspec before(:all) class IntegrationRequestsWithoutSetup < ActionDispatch::IntegrationTest self._setup_callbacks = [] self._teardown_callbacks = [] class FooController < ActionController::Base def ok cookies[:key] = 'ok' render plain: 'ok' end end def test_request with_routing do |routes| routes.draw { get ':action' => FooController } get '/ok' assert_response 200 assert_equal 'ok', response.body assert_equal 'ok', cookies['key'] end end end rails-4.2.6/actionpack/test/controller/live_stream_test.rb000066400000000000000000000305331266740050600240230ustar00rootroot00000000000000require 'abstract_unit' require 'active_support/concurrency/latch' Thread.abort_on_exception = true module ActionController class SSETest < ActionController::TestCase class SSETestController < ActionController::Base include ActionController::Live def basic_sse response.headers['Content-Type'] = 'text/event-stream' sse = SSE.new(response.stream) sse.write("{\"name\":\"John\"}") sse.write({ name: "Ryan" }) ensure sse.close end def sse_with_event sse = SSE.new(response.stream, event: "send-name") sse.write("{\"name\":\"John\"}") sse.write({ name: "Ryan" }) ensure sse.close end def sse_with_retry sse = SSE.new(response.stream, retry: 1000) sse.write("{\"name\":\"John\"}") sse.write({ name: "Ryan" }, retry: 1500) ensure sse.close end def sse_with_id sse = SSE.new(response.stream) sse.write("{\"name\":\"John\"}", id: 1) sse.write({ name: "Ryan" }, id: 2) ensure sse.close end def sse_with_multiple_line_message sse = SSE.new(response.stream) sse.write("first line.\nsecond line.") ensure sse.close end end tests SSETestController def wait_for_response_stream_close response.body end def test_basic_sse get :basic_sse wait_for_response_stream_close assert_match(/data: {\"name\":\"John\"}/, response.body) assert_match(/data: {\"name\":\"Ryan\"}/, response.body) end def test_sse_with_event_name get :sse_with_event wait_for_response_stream_close assert_match(/data: {\"name\":\"John\"}/, response.body) assert_match(/data: {\"name\":\"Ryan\"}/, response.body) assert_match(/event: send-name/, response.body) end def test_sse_with_retry get :sse_with_retry wait_for_response_stream_close first_response, second_response = response.body.split("\n\n") assert_match(/data: {\"name\":\"John\"}/, first_response) assert_match(/retry: 1000/, first_response) assert_match(/data: {\"name\":\"Ryan\"}/, second_response) assert_match(/retry: 1500/, second_response) end def test_sse_with_id get :sse_with_id wait_for_response_stream_close first_response, second_response = response.body.split("\n\n") assert_match(/data: {\"name\":\"John\"}/, first_response) assert_match(/id: 1/, first_response) assert_match(/data: {\"name\":\"Ryan\"}/, second_response) assert_match(/id: 2/, second_response) end def test_sse_with_multiple_line_message get :sse_with_multiple_line_message wait_for_response_stream_close first_response, second_response = response.body.split("\n") assert_match(/data: first line/, first_response) assert_match(/data: second line/, second_response) end end class LiveStreamTest < ActionController::TestCase class Exception < StandardError end class TestController < ActionController::Base include ActionController::Live attr_accessor :latch, :tc def self.controller_path 'test' end def set_cookie cookies[:hello] = "world" response.stream.write "hello world" response.close end def render_text render :text => 'zomg' end def default_header response.stream.write "hi" response.stream.close end def basic_stream response.headers['Content-Type'] = 'text/event-stream' %w{ hello world }.each do |word| response.stream.write word end response.stream.close end def blocking_stream response.headers['Content-Type'] = 'text/event-stream' %w{ hello world }.each do |word| response.stream.write word latch.await end response.stream.close end def thread_locals tc.assert_equal 'aaron', Thread.current[:setting] tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread] response.headers['Content-Type'] = 'text/event-stream' %w{ hello world }.each do |word| response.stream.write word end response.stream.close end def with_stale render text: 'stale' if stale?(etag: "123", template: false) end def exception_in_view render 'doesntexist' end def exception_in_view_after_commit response.stream.write "" render 'doesntexist' end def exception_with_callback response.headers['Content-Type'] = 'text/event-stream' response.stream.on_error do response.stream.write %(data: "500 Internal Server Error"\n\n) response.stream.close end response.stream.write "" # make sure the response is committed raise 'An exception occurred...' end def exception_in_controller raise Exception, 'Exception in controller' end def bad_request_error raise ActionController::BadRequest end def exception_in_exception_callback response.headers['Content-Type'] = 'text/event-stream' response.stream.on_error do raise 'We need to go deeper.' end response.stream.write '' response.stream.write params[:widget][:didnt_check_for_nil] end def overfill_buffer_and_die # Write until the buffer is full. It doesn't expose that # information directly, so we must hard-code its size: 10.times do response.stream.write '.' end # .. plus one more, because the #each frees up a slot: response.stream.write '.' latch.release # This write will block, and eventually raise response.stream.write 'x' 20.times do response.stream.write '.' end end def ignore_client_disconnect response.stream.ignore_disconnect = true response.stream.write '' # commit # These writes will be ignored 15.times do response.stream.write 'x' end logger.info 'Work complete' latch.release end end tests TestController def assert_stream_closed assert response.stream.closed?, 'stream should be closed' assert response.sent?, 'stream should be sent' end def capture_log_output output = StringIO.new old_logger, ActionController::Base.logger = ActionController::Base.logger, ActiveSupport::Logger.new(output) begin yield output ensure ActionController::Base.logger = old_logger end end def test_set_cookie @controller = TestController.new get :set_cookie assert_equal({'hello' => 'world'}, @response.cookies) assert_equal "hello world", @response.body end def test_set_response! @controller.set_response!(@request) assert_kind_of(Live::Response, @controller.response) assert_equal @request, @controller.response.request end def test_write_to_stream @controller = TestController.new get :basic_stream assert_equal "helloworld", @response.body assert_equal 'text/event-stream', @response.headers['Content-Type'] end def test_async_stream @controller.latch = ActiveSupport::Concurrency::Latch.new parts = ['hello', 'world'] @controller.request = @request @controller.response = @response t = Thread.new(@response) { |resp| resp.await_commit resp.stream.each do |part| assert_equal parts.shift, part ol = @controller.latch @controller.latch = ActiveSupport::Concurrency::Latch.new ol.release end } @controller.process :blocking_stream assert t.join(3), 'timeout expired before the thread terminated' end def test_abort_with_full_buffer @controller.latch = ActiveSupport::Concurrency::Latch.new @request.parameters[:format] = 'plain' @controller.request = @request @controller.response = @response got_error = ActiveSupport::Concurrency::Latch.new @response.stream.on_error do ActionController::Base.logger.warn 'Error while streaming' got_error.release end t = Thread.new(@response) { |resp| resp.await_commit _, _, body = resp.to_a body.each do |part| @controller.latch.await body.close break end } capture_log_output do |output| @controller.process :overfill_buffer_and_die t.join got_error.await assert_match 'Error while streaming', output.rewind && output.read end end def test_ignore_client_disconnect @controller.latch = ActiveSupport::Concurrency::Latch.new @controller.request = @request @controller.response = @response t = Thread.new(@response) { |resp| resp.await_commit _, _, body = resp.to_a body.each do |part| body.close break end } capture_log_output do |output| @controller.process :ignore_client_disconnect t.join Timeout.timeout(3) do @controller.latch.await end assert_match 'Work complete', output.rewind && output.read end end def test_thread_locals_get_copied @controller.tc = self Thread.current[:originating_thread] = Thread.current.object_id Thread.current[:setting] = 'aaron' get :thread_locals end def test_live_stream_default_header @controller.request = @request @controller.response = @response @controller.process :default_header _, headers, _ = @response.prepare! assert headers['Content-Type'] end def test_render_text get :render_text assert_equal 'zomg', response.body assert_stream_closed end def test_exception_handling_html assert_raises(ActionView::MissingTemplate) do get :exception_in_view end capture_log_output do |output| get :exception_in_view_after_commit assert_match %r((window\.location = "/500\.html")$), response.body assert_match 'Missing template test/doesntexist', output.rewind && output.read assert_stream_closed end assert response.body assert_stream_closed end def test_exception_handling_plain_text assert_raises(ActionView::MissingTemplate) do get :exception_in_view, format: :json end capture_log_output do |output| get :exception_in_view_after_commit, format: :json assert_equal '', response.body assert_match 'Missing template test/doesntexist', output.rewind && output.read assert_stream_closed end end def test_exception_callback_when_committed capture_log_output do |output| get :exception_with_callback, format: 'text/event-stream' assert_equal %(data: "500 Internal Server Error"\n\n), response.body assert_match 'An exception occurred...', output.rewind && output.read assert_stream_closed end end def test_exception_in_controller_before_streaming assert_raises(ActionController::LiveStreamTest::Exception) do get :exception_in_controller, format: 'text/event-stream' end end def test_bad_request_in_controller_before_streaming assert_raises(ActionController::BadRequest) do get :bad_request_error, format: 'text/event-stream' end end def test_exceptions_raised_handling_exceptions_and_committed capture_log_output do |output| get :exception_in_exception_callback, format: 'text/event-stream' assert_equal '', response.body assert_match 'We need to go deeper', output.rewind && output.read assert_stream_closed end end def test_stale_without_etag get :with_stale assert_equal 200, @response.status.to_i end def test_stale_with_etag @request.if_none_match = Digest::MD5.hexdigest("123") get :with_stale assert_equal 304, @response.status.to_i end end class BufferTest < ActionController::TestCase def test_nil_callback buf = ActionController::Live::Buffer.new nil assert buf.call_on_error end end end rails-4.2.6/actionpack/test/controller/localized_templates_test.rb000066400000000000000000000020411266740050600255260ustar00rootroot00000000000000require 'abstract_unit' class LocalizedController < ActionController::Base def hello_world end end class LocalizedTemplatesTest < ActionController::TestCase tests LocalizedController setup do @old_locale = I18n.locale end teardown do I18n.locale = @old_locale end def test_localized_template_is_used I18n.locale = :de get :hello_world assert_equal "Gutten Tag", @response.body end def test_default_locale_template_is_used_when_locale_is_missing I18n.locale = :dk get :hello_world assert_equal "Hello World", @response.body end def test_use_fallback_locales I18n.locale = :"de-AT" I18n.backend.class.send(:include, I18n::Backend::Fallbacks) I18n.fallbacks[:"de-AT"] = [:de] get :hello_world assert_equal "Gutten Tag", @response.body end def test_localized_template_has_correct_header_with_no_format_in_template_name I18n.locale = :it get :hello_world assert_equal "Ciao Mondo", @response.body assert_equal "text/html", @response.content_type end end rails-4.2.6/actionpack/test/controller/log_subscriber_test.rb000066400000000000000000000204611266740050600245140ustar00rootroot00000000000000require "abstract_unit" require "active_support/log_subscriber/test_helper" require "action_controller/log_subscriber" module Another class LogSubscribersController < ActionController::Base wrap_parameters :person, :include => :name, :format => :json class SpecialException < Exception end rescue_from SpecialException do head :status => 406 end before_action :redirector, only: :never_executed def never_executed end def show render :nothing => true end def redirector redirect_to "http://foo.bar/" end def filterable_redirector redirect_to "http://secret.foo.bar/" end def data_sender send_data "cool data", :filename => "file.txt" end def file_sender send_file File.expand_path("company.rb", FIXTURE_LOAD_PATH) end def with_fragment_cache render :inline => "<%= cache('foo'){ 'bar' } %>" end def with_fragment_cache_and_percent_in_key render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>" end def with_fragment_cache_if_with_true_condition render :inline => "<%= cache_if(true, 'foo') { 'bar' } %>" end def with_fragment_cache_if_with_false_condition render :inline => "<%= cache_if(false, 'foo') { 'bar' } %>" end def with_fragment_cache_unless_with_false_condition render :inline => "<%= cache_unless(false, 'foo') { 'bar' } %>" end def with_fragment_cache_unless_with_true_condition render :inline => "<%= cache_unless(true, 'foo') { 'bar' } %>" end def with_exception raise Exception end def with_rescued_exception raise SpecialException end def with_action_not_found raise AbstractController::ActionNotFound end def append_info_to_payload(payload) super payload[:test_key] = "test_value" @last_payload = payload end def last_payload @last_payload end end end class ACLogSubscriberTest < ActionController::TestCase tests Another::LogSubscribersController include ActiveSupport::LogSubscriber::TestHelper def setup super @old_logger = ActionController::Base.logger @cache_path = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('tmp', 'cache') @controller.cache_store = :file_store, @cache_path ActionController::LogSubscriber.attach_to :action_controller end def teardown super ActiveSupport::LogSubscriber.log_subscribers.clear FileUtils.rm_rf(@cache_path) ActionController::Base.logger = @old_logger end def set_logger(logger) ActionController::Base.logger = logger end def test_start_processing get :show wait assert_equal 2, logs.size assert_equal "Processing by Another::LogSubscribersController#show as HTML", logs.first end def test_halted_callback get :never_executed wait assert_equal 4, logs.size assert_equal "Filter chain halted as :redirector rendered or redirected", logs.third end def test_process_action get :show wait assert_equal 2, logs.size assert_match(/Completed/, logs.last) assert_match(/200 OK/, logs.last) end def test_process_action_without_parameters get :show wait assert_nil logs.detect {|l| l =~ /Parameters/ } end def test_process_action_with_parameters get :show, :id => '10' wait assert_equal 3, logs.size assert_equal 'Parameters: {"id"=>"10"}', logs[1] end def test_multiple_process_with_parameters get :show, :id => '10' get :show, :id => '20' wait assert_equal 6, logs.size assert_equal 'Parameters: {"id"=>"10"}', logs[1] assert_equal 'Parameters: {"id"=>"20"}', logs[4] end def test_process_action_with_wrapped_parameters @request.env['CONTENT_TYPE'] = 'application/json' post :show, :id => '10', :name => 'jose' wait assert_equal 3, logs.size assert_match '"person"=>{"name"=>"jose"}', logs[1] end def test_process_action_with_view_runtime get :show wait assert_match(/\(Views: [\d.]+ms\)/, logs[1]) end def test_append_info_to_payload_is_called_even_with_exception begin get :with_exception wait rescue Exception end assert_equal "test_value", @controller.last_payload[:test_key] end def test_process_action_with_filter_parameters @request.env["action_dispatch.parameter_filter"] = [:lifo, :amount] get :show, :lifo => 'Pratik', :amount => '420', :step => '1' wait params = logs[1] assert_match(/"amount"=>"\[FILTERED\]"/, params) assert_match(/"lifo"=>"\[FILTERED\]"/, params) assert_match(/"step"=>"1"/, params) end def test_redirect_to get :redirector wait assert_equal 3, logs.size assert_equal "Redirected to http://foo.bar/", logs[1] end def test_filter_redirect_url_by_string @request.env['action_dispatch.redirect_filter'] = ['secret'] get :filterable_redirector wait assert_equal 3, logs.size assert_equal "Redirected to [FILTERED]", logs[1] end def test_filter_redirect_url_by_regexp @request.env['action_dispatch.redirect_filter'] = [/secret\.foo.+/] get :filterable_redirector wait assert_equal 3, logs.size assert_equal "Redirected to [FILTERED]", logs[1] end def test_send_data get :data_sender wait assert_equal 3, logs.size assert_match(/Sent data file\.txt/, logs[1]) end def test_send_file get :file_sender wait assert_equal 3, logs.size assert_match(/Sent file/, logs[1]) assert_match(/test\/fixtures\/company\.rb/, logs[1]) end def test_with_fragment_cache @controller.config.perform_caching = true get :with_fragment_cache wait assert_equal 4, logs.size assert_match(/Read fragment views\/foo/, logs[1]) assert_match(/Write fragment views\/foo/, logs[2]) ensure @controller.config.perform_caching = true end def test_with_fragment_cache_if_with_true @controller.config.perform_caching = true get :with_fragment_cache_if_with_true_condition wait assert_equal 4, logs.size assert_match(/Read fragment views\/foo/, logs[1]) assert_match(/Write fragment views\/foo/, logs[2]) ensure @controller.config.perform_caching = true end def test_with_fragment_cache_if_with_false @controller.config.perform_caching = true get :with_fragment_cache_if_with_false_condition wait assert_equal 2, logs.size assert_no_match(/Read fragment views\/foo/, logs[1]) assert_no_match(/Write fragment views\/foo/, logs[2]) ensure @controller.config.perform_caching = true end def test_with_fragment_cache_unless_with_true @controller.config.perform_caching = true get :with_fragment_cache_unless_with_true_condition wait assert_equal 2, logs.size assert_no_match(/Read fragment views\/foo/, logs[1]) assert_no_match(/Write fragment views\/foo/, logs[2]) ensure @controller.config.perform_caching = true end def test_with_fragment_cache_unless_with_false @controller.config.perform_caching = true get :with_fragment_cache_unless_with_false_condition wait assert_equal 4, logs.size assert_match(/Read fragment views\/foo/, logs[1]) assert_match(/Write fragment views\/foo/, logs[2]) ensure @controller.config.perform_caching = true end def test_with_fragment_cache_and_percent_in_key @controller.config.perform_caching = true get :with_fragment_cache_and_percent_in_key wait assert_equal 4, logs.size assert_match(/Read fragment views\/foo/, logs[1]) assert_match(/Write fragment views\/foo/, logs[2]) ensure @controller.config.perform_caching = true end def test_process_action_with_exception_includes_http_status_code begin get :with_exception wait rescue Exception end assert_equal 2, logs.size assert_match(/Completed 500/, logs.last) end def test_process_action_with_rescued_exception_includes_http_status_code get :with_rescued_exception wait assert_equal 2, logs.size assert_match(/Completed 406/, logs.last) end def test_process_action_with_with_action_not_found_logs_404 begin get :with_action_not_found wait rescue AbstractController::ActionNotFound end assert_equal 2, logs.size assert_match(/Completed 404/, logs.last) end def logs @logs ||= @logger.logged(:info) end end rails-4.2.6/actionpack/test/controller/mime/000077500000000000000000000000001266740050600210505ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/mime/accept_format_test.rb000066400000000000000000000043111266740050600252420ustar00rootroot00000000000000require 'abstract_unit' class StarStarMimeController < ActionController::Base layout nil def index render end end class StarStarMimeControllerTest < ActionController::TestCase def test_javascript_with_format @request.accept = "text/javascript" get :index, :format => 'js' assert_match "function addition(a,b){ return a+b; }", @response.body end def test_javascript_with_no_format @request.accept = "text/javascript" get :index assert_match "function addition(a,b){ return a+b; }", @response.body end def test_javascript_with_no_format_only_star_star @request.accept = "*/*" get :index assert_match "function addition(a,b){ return a+b; }", @response.body end end class AbstractPostController < ActionController::Base self.view_paths = File.dirname(__FILE__) + "/../../fixtures/post_test/" end # For testing layouts which are set automatically class PostController < AbstractPostController around_action :with_iphone def index respond_to(:html, :iphone, :js) end protected def with_iphone request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" yield end end class SuperPostController < PostController end class MimeControllerLayoutsTest < ActionController::TestCase tests PostController def setup super @request.host = "www.example.com" Mime::Type.register_alias("text/html", :iphone) end def teardown super Mime::Type.unregister(:iphone) end def test_missing_layout_renders_properly get :index assert_equal '
Hello Firefox
', @response.body @request.accept = "text/iphone" get :index assert_equal 'Hello iPhone', @response.body end def test_format_with_inherited_layouts @controller = SuperPostController.new get :index assert_equal '
Super Firefox
', @response.body @request.accept = "text/iphone" get :index assert_equal '
Super iPhone
', @response.body end def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout get :index, :format => :js assert_equal "Hello Firefox", @response.body end end rails-4.2.6/actionpack/test/controller/mime/respond_to_test.rb000066400000000000000000000504121266740050600246120ustar00rootroot00000000000000require 'abstract_unit' class RespondToController < ActionController::Base layout :set_layout def html_xml_or_rss respond_to do |type| type.html { render :text => "HTML" } type.xml { render :text => "XML" } type.rss { render :text => "RSS" } type.all { render :text => "Nothing" } end end def js_or_html respond_to do |type| type.html { render :text => "HTML" } type.js { render :text => "JS" } type.all { render :text => "Nothing" } end end def json_or_yaml respond_to do |type| type.json { render :text => "JSON" } type.yaml { render :text => "YAML" } end end def html_or_xml respond_to do |type| type.html { render :text => "HTML" } type.xml { render :text => "XML" } type.all { render :text => "Nothing" } end end def json_xml_or_html respond_to do |type| type.json { render :text => 'JSON' } type.xml { render :xml => 'XML' } type.html { render :text => 'HTML' } end end def forced_xml request.format = :xml respond_to do |type| type.html { render :text => "HTML" } type.xml { render :text => "XML" } end end def just_xml respond_to do |type| type.xml { render :text => "XML" } end end def using_defaults respond_to do |type| type.html type.xml end end def using_defaults_with_type_list respond_to(:html, :xml) end def using_defaults_with_all respond_to do |type| type.html type.all{ render text: "ALL" } end end def made_for_content_type respond_to do |type| type.rss { render :text => "RSS" } type.atom { render :text => "ATOM" } type.all { render :text => "Nothing" } end end def custom_type_handling respond_to do |type| type.html { render :text => "HTML" } type.custom("application/crazy-xml") { render :text => "Crazy XML" } type.all { render :text => "Nothing" } end end def custom_constant_handling respond_to do |type| type.html { render :text => "HTML" } type.mobile { render :text => "Mobile" } end end def custom_constant_handling_without_block respond_to do |type| type.html { render :text => "HTML" } type.mobile end end def handle_any respond_to do |type| type.html { render :text => "HTML" } type.any(:js, :xml) { render :text => "Either JS or XML" } end end def handle_any_any respond_to do |type| type.html { render :text => 'HTML' } type.any { render :text => 'Whatever you ask for, I got it' } end end def all_types_with_layout respond_to do |type| type.html end end def json_with_callback respond_to do |type| type.json { render :json => 'JS', :callback => 'alert' } end end def iphone_with_html_response_type request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone" respond_to do |type| type.html { @type = "Firefox" } type.iphone { @type = "iPhone" } end end def iphone_with_html_response_type_without_layout request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" respond_to do |type| type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" } type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" } end end def variant_with_implicit_rendering end def variant_with_format_and_custom_render request.variant = :mobile respond_to do |type| type.html { render text: "mobile" } end end def multiple_variants_for_format respond_to do |type| type.html do |html| html.tablet { render text: "tablet" } html.phone { render text: "phone" } end end end def variant_plus_none_for_format respond_to do |format| format.html do |variant| variant.phone { render text: "phone" } variant.none end end end def variant_inline_syntax respond_to do |format| format.js { render text: "js" } format.html.none { render text: "none" } format.html.phone { render text: "phone" } end end def variant_inline_syntax_without_block respond_to do |format| format.js format.html.none format.html.phone end end def variant_any respond_to do |format| format.html do |variant| variant.any(:tablet, :phablet){ render text: "any" } variant.phone { render text: "phone" } end end end def variant_any_any respond_to do |format| format.html do |variant| variant.any { render text: "any" } variant.phone { render text: "phone" } end end end def variant_inline_any respond_to do |format| format.html.any(:tablet, :phablet){ render text: "any" } format.html.phone { render text: "phone" } end end def variant_inline_any_any respond_to do |format| format.html.phone { render text: "phone" } format.html.any { render text: "any" } end end def variant_any_implicit_render respond_to do |format| format.html.phone format.html.any(:tablet, :phablet) end end def variant_any_with_none respond_to do |format| format.html.any(:none, :phone){ render text: "none or phone" } end end def format_any_variant_any respond_to do |format| format.html { render text: "HTML" } format.any(:js, :xml) do |variant| variant.phone{ render text: "phone" } variant.any(:tablet, :phablet){ render text: "tablet" } end end end protected def set_layout case action_name when "all_types_with_layout", "iphone_with_html_response_type" "respond_to/layouts/standard" when "iphone_with_html_response_type_without_layout" "respond_to/layouts/missing" end end end class RespondToControllerTest < ActionController::TestCase def setup super @request.host = "www.example.com" Mime::Type.register_alias("text/html", :iphone) Mime::Type.register("text/x-mobile", :mobile) end def teardown super Mime::Type.unregister(:iphone) Mime::Type.unregister(:mobile) end def test_html @request.accept = "text/html" get :js_or_html assert_equal 'HTML', @response.body get :html_or_xml assert_equal 'HTML', @response.body assert_raises(ActionController::UnknownFormat) do get :just_xml end end def test_all @request.accept = "*/*" get :js_or_html assert_equal 'HTML', @response.body # js is not part of all get :html_or_xml assert_equal 'HTML', @response.body get :just_xml assert_equal 'XML', @response.body end def test_xml @request.accept = "application/xml" get :html_xml_or_rss assert_equal 'XML', @response.body end def test_js_or_html @request.accept = "text/javascript, text/html" xhr :get, :js_or_html assert_equal 'JS', @response.body @request.accept = "text/javascript, text/html" xhr :get, :html_or_xml assert_equal 'HTML', @response.body @request.accept = "text/javascript, text/html" assert_raises(ActionController::UnknownFormat) do xhr :get, :just_xml end end def test_json_or_yaml_with_leading_star_star @request.accept = "*/*, application/json" get :json_xml_or_html assert_equal 'HTML', @response.body @request.accept = "*/* , application/json" get :json_xml_or_html assert_equal 'HTML', @response.body end def test_json_or_yaml xhr :get, :json_or_yaml assert_equal 'JSON', @response.body get :json_or_yaml, :format => 'json' assert_equal 'JSON', @response.body get :json_or_yaml, :format => 'yaml' assert_equal 'YAML', @response.body { 'YAML' => %w(text/yaml), 'JSON' => %w(application/json text/x-json) }.each do |body, content_types| content_types.each do |content_type| @request.accept = content_type get :json_or_yaml assert_equal body, @response.body end end end def test_js_or_anything @request.accept = "text/javascript, */*" xhr :get, :js_or_html assert_equal 'JS', @response.body xhr :get, :html_or_xml assert_equal 'HTML', @response.body xhr :get, :just_xml assert_equal 'XML', @response.body end def test_using_defaults @request.accept = "*/*" get :using_defaults assert_equal "text/html", @response.content_type assert_equal 'Hello world!', @response.body @request.accept = "application/xml" get :using_defaults assert_equal "application/xml", @response.content_type assert_equal "

Hello world!

\n", @response.body end def test_using_defaults_with_all @request.accept = "*/*" get :using_defaults_with_all assert_equal "HTML!", @response.body.strip @request.accept = "text/html" get :using_defaults_with_all assert_equal "HTML!", @response.body.strip @request.accept = "application/json" get :using_defaults_with_all assert_equal "ALL", @response.body end def test_using_defaults_with_type_list @request.accept = "*/*" get :using_defaults_with_type_list assert_equal "text/html", @response.content_type assert_equal 'Hello world!', @response.body @request.accept = "application/xml" get :using_defaults_with_type_list assert_equal "application/xml", @response.content_type assert_equal "

Hello world!

\n", @response.body end def test_with_atom_content_type @request.accept = "" @request.env["CONTENT_TYPE"] = "application/atom+xml" xhr :get, :made_for_content_type assert_equal "ATOM", @response.body end def test_with_rss_content_type @request.accept = "" @request.env["CONTENT_TYPE"] = "application/rss+xml" xhr :get, :made_for_content_type assert_equal "RSS", @response.body end def test_synonyms @request.accept = "application/javascript" get :js_or_html assert_equal 'JS', @response.body @request.accept = "application/x-xml" get :html_xml_or_rss assert_equal "XML", @response.body end def test_custom_types @request.accept = "application/crazy-xml" get :custom_type_handling assert_equal "application/crazy-xml", @response.content_type assert_equal 'Crazy XML', @response.body @request.accept = "text/html" get :custom_type_handling assert_equal "text/html", @response.content_type assert_equal 'HTML', @response.body end def test_xhtml_alias @request.accept = "application/xhtml+xml,application/xml" get :html_or_xml assert_equal 'HTML', @response.body end def test_firefox_simulation @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" get :html_or_xml assert_equal 'HTML', @response.body end def test_handle_any @request.accept = "*/*" get :handle_any assert_equal 'HTML', @response.body @request.accept = "text/javascript" get :handle_any assert_equal 'Either JS or XML', @response.body @request.accept = "text/xml" get :handle_any assert_equal 'Either JS or XML', @response.body end def test_handle_any_any @request.accept = "*/*" get :handle_any_any assert_equal 'HTML', @response.body end def test_handle_any_any_parameter_format get :handle_any_any, {:format=>'html'} assert_equal 'HTML', @response.body end def test_handle_any_any_explicit_html @request.accept = "text/html" get :handle_any_any assert_equal 'HTML', @response.body end def test_handle_any_any_javascript @request.accept = "text/javascript" get :handle_any_any assert_equal 'Whatever you ask for, I got it', @response.body end def test_handle_any_any_xml @request.accept = "text/xml" get :handle_any_any assert_equal 'Whatever you ask for, I got it', @response.body end def test_handle_any_any_unkown_format get :handle_any_any, { format: 'php' } assert_equal 'Whatever you ask for, I got it', @response.body end def test_browser_check_with_any_any @request.accept = "application/json, application/xml" get :json_xml_or_html assert_equal 'JSON', @response.body @request.accept = "application/json, application/xml, */*" get :json_xml_or_html assert_equal 'HTML', @response.body end def test_html_type_with_layout @request.accept = "text/html" get :all_types_with_layout assert_equal '
HTML for all_types_with_layout
', @response.body end def test_json_with_callback_sets_javascript_content_type @request.accept = 'application/json' get :json_with_callback assert_equal '/**/alert(JS)', @response.body assert_equal 'text/javascript', @response.content_type end def test_xhr xhr :get, :js_or_html assert_equal 'JS', @response.body end def test_custom_constant get :custom_constant_handling, :format => "mobile" assert_equal "text/x-mobile", @response.content_type assert_equal "Mobile", @response.body end def test_custom_constant_handling_without_block get :custom_constant_handling_without_block, :format => "mobile" assert_equal "text/x-mobile", @response.content_type assert_equal "Mobile", @response.body end def test_forced_format get :html_xml_or_rss assert_equal "HTML", @response.body get :html_xml_or_rss, :format => "html" assert_equal "HTML", @response.body get :html_xml_or_rss, :format => "xml" assert_equal "XML", @response.body get :html_xml_or_rss, :format => "rss" assert_equal "RSS", @response.body end def test_internally_forced_format get :forced_xml assert_equal "XML", @response.body get :forced_xml, :format => "html" assert_equal "XML", @response.body end def test_extension_synonyms get :html_xml_or_rss, :format => "xhtml" assert_equal "HTML", @response.body end def test_render_action_for_html @controller.instance_eval do def render(*args) @action = args.first[:action] unless args.empty? @action ||= action_name response.body = "#{@action} - #{formats}" end end get :using_defaults assert_equal "using_defaults - #{[:html]}", @response.body get :using_defaults, :format => "xml" assert_equal "using_defaults - #{[:xml]}", @response.body end def test_format_with_custom_response_type get :iphone_with_html_response_type assert_equal '
Hello future from Firefox!
', @response.body get :iphone_with_html_response_type, :format => "iphone" assert_equal "text/html", @response.content_type assert_equal '
Hello iPhone future from iPhone!
', @response.body end def test_format_with_custom_response_type_and_request_headers @request.accept = "text/iphone" get :iphone_with_html_response_type assert_equal '
Hello iPhone future from iPhone!
', @response.body assert_equal "text/html", @response.content_type end def test_invalid_format assert_raises(ActionController::UnknownFormat) do get :using_defaults, :format => "invalidformat" end end def test_invalid_variant @request.variant = :invalid assert_raises(ActionView::MissingTemplate) do get :variant_with_implicit_rendering end end def test_variant_not_set_regular_template_missing assert_raises(ActionView::MissingTemplate) do get :variant_with_implicit_rendering end end def test_variant_with_implicit_rendering @request.variant = :mobile get :variant_with_implicit_rendering assert_equal "text/html", @response.content_type assert_equal "mobile", @response.body end def test_variant_with_format_and_custom_render @request.variant = :phone get :variant_with_format_and_custom_render assert_equal "text/html", @response.content_type assert_equal "mobile", @response.body end def test_multiple_variants_for_format @request.variant = :tablet get :multiple_variants_for_format assert_equal "text/html", @response.content_type assert_equal "tablet", @response.body end def test_no_variant_in_variant_setup get :variant_plus_none_for_format assert_equal "text/html", @response.content_type assert_equal "none", @response.body end def test_variant_inline_syntax get :variant_inline_syntax, format: :js assert_equal "text/javascript", @response.content_type assert_equal "js", @response.body get :variant_inline_syntax assert_equal "text/html", @response.content_type assert_equal "none", @response.body @request.variant = :phone get :variant_inline_syntax assert_equal "text/html", @response.content_type assert_equal "phone", @response.body end def test_variant_inline_syntax_without_block @request.variant = :phone get :variant_inline_syntax_without_block assert_equal "text/html", @response.content_type assert_equal "phone", @response.body end def test_variant_any @request.variant = :phone get :variant_any assert_equal "text/html", @response.content_type assert_equal "phone", @response.body @request.variant = :tablet get :variant_any assert_equal "text/html", @response.content_type assert_equal "any", @response.body @request.variant = :phablet get :variant_any assert_equal "text/html", @response.content_type assert_equal "any", @response.body end def test_variant_any_any get :variant_any_any assert_equal "text/html", @response.content_type assert_equal "any", @response.body @request.variant = :phone get :variant_any_any assert_equal "text/html", @response.content_type assert_equal "phone", @response.body @request.variant = :yolo get :variant_any_any assert_equal "text/html", @response.content_type assert_equal "any", @response.body end def test_variant_inline_any @request.variant = :phone get :variant_any assert_equal "text/html", @response.content_type assert_equal "phone", @response.body @request.variant = :tablet get :variant_inline_any assert_equal "text/html", @response.content_type assert_equal "any", @response.body @request.variant = :phablet get :variant_inline_any assert_equal "text/html", @response.content_type assert_equal "any", @response.body end def test_variant_inline_any_any @request.variant = :phone get :variant_inline_any_any assert_equal "text/html", @response.content_type assert_equal "phone", @response.body @request.variant = :yolo get :variant_inline_any_any assert_equal "text/html", @response.content_type assert_equal "any", @response.body end def test_variant_any_implicit_render @request.variant = :tablet get :variant_any_implicit_render assert_equal "text/html", @response.content_type assert_equal "tablet", @response.body @request.variant = :phablet get :variant_any_implicit_render assert_equal "text/html", @response.content_type assert_equal "phablet", @response.body end def test_variant_any_with_none get :variant_any_with_none assert_equal "text/html", @response.content_type assert_equal "none or phone", @response.body @request.variant = :phone get :variant_any_with_none assert_equal "text/html", @response.content_type assert_equal "none or phone", @response.body end def test_format_any_variant_any @request.variant = :tablet get :format_any_variant_any, format: :js assert_equal "text/javascript", @response.content_type assert_equal "tablet", @response.body end def test_variant_negotiation_inline_syntax @request.variant = [:tablet, :phone] get :variant_inline_syntax_without_block assert_equal "text/html", @response.content_type assert_equal "phone", @response.body end def test_variant_negotiation_block_syntax @request.variant = [:tablet, :phone] get :variant_plus_none_for_format assert_equal "text/html", @response.content_type assert_equal "phone", @response.body end def test_variant_negotiation_without_block @request.variant = [:tablet, :phone] get :variant_inline_syntax_without_block assert_equal "text/html", @response.content_type assert_equal "phone", @response.body end end rails-4.2.6/actionpack/test/controller/mime/responders_test.rb000066400000000000000000000013131266740050600246160ustar00rootroot00000000000000require 'abstract_unit' require 'controller/fake_models' class ResponderTest < ActionController::TestCase def test_class_level_respond_to e = assert_raises(NoMethodError) do Class.new(ActionController::Base) do respond_to :json end end assert_includes e.message, '`responders` gem' assert_includes e.message, '~> 2.0' end def test_respond_with klass = Class.new(ActionController::Base) do def index respond_with Customer.new("david", 13) end end @controller = klass.new e = assert_raises(NoMethodError) do get :index end assert_includes e.message, '`responders` gem' assert_includes e.message, '~> 2.0' end end rails-4.2.6/actionpack/test/controller/new_base/000077500000000000000000000000001266740050600217045ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/new_base/bare_metal_test.rb000066400000000000000000000113431266740050600253650ustar00rootroot00000000000000require "abstract_unit" module BareMetalTest class BareController < ActionController::Metal include ActionController::RackDelegation def index self.response_body = "Hello world" end end class BareTest < ActiveSupport::TestCase test "response body is a Rack-compatible response" do status, headers, body = BareController.action(:index).call(Rack::MockRequest.env_for("/")) assert_equal 200, status string = "" body.each do |part| assert part.is_a?(String), "Each part of the body must be a String" string << part end assert_kind_of Hash, headers, "Headers must be a Hash" assert headers["Content-Type"], "Content-Type must exist" assert_equal "Hello world", string end test "response_body value is wrapped in an array when the value is a String" do controller = BareController.new controller.index assert_equal ["Hello world"], controller.response_body end end class HeadController < ActionController::Metal include ActionController::Head def index head :not_found end def continue self.content_type = "text/html" head 100 end def switching_protocols self.content_type = "text/html" head 101 end def processing self.content_type = "text/html" head 102 end def no_content self.content_type = "text/html" head 204 end def reset_content self.content_type = "text/html" head 205 end def not_modified self.content_type = "text/html" head 304 end end class HeadTest < ActiveSupport::TestCase test "head works on its own" do status = HeadController.action(:index).call(Rack::MockRequest.env_for("/")).first assert_equal 404, status end test "head :continue (100) does not return a content-type header" do headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second assert_nil headers['Content-Type'] assert_nil headers['Content-Length'] end test "head :switching_protocols (101) does not return a content-type header" do headers = HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/")).second assert_nil headers['Content-Type'] assert_nil headers['Content-Length'] end test "head :processing (102) does not return a content-type header" do headers = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).second assert_nil headers['Content-Type'] assert_nil headers['Content-Length'] end test "head :no_content (204) does not return a content-type header" do headers = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).second assert_nil headers['Content-Type'] assert_nil headers['Content-Length'] end test "head :reset_content (205) does not return a content-type header" do headers = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).second assert_nil headers['Content-Type'] assert_nil headers['Content-Length'] end test "head :not_modified (304) does not return a content-type header" do headers = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).second assert_nil headers['Content-Type'] assert_nil headers['Content-Length'] end test "head :no_content (204) does not return any content" do content = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).third.first assert_empty content end test "head :reset_content (205) does not return any content" do content = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).third.first assert_empty content end test "head :not_modified (304) does not return any content" do content = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).third.first assert_empty content end test "head :continue (100) does not return any content" do content = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).third.first assert_empty content end test "head :switching_protocols (101) does not return any content" do content = HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/")).third.first assert_empty content end test "head :processing (102) does not return any content" do content = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).third.first assert_empty content end end class BareControllerTest < ActionController::TestCase test "GET index" do get :index assert_equal "Hello world", @response.body end end end rails-4.2.6/actionpack/test/controller/new_base/base_test.rb000066400000000000000000000106141266740050600242040ustar00rootroot00000000000000require 'abstract_unit' # Tests the controller dispatching happy path module Dispatching class SimpleController < ActionController::Base before_action :authenticate def index render :text => "success" end def modify_response_body self.response_body = "success" end def modify_response_body_twice ret = (self.response_body = "success") self.response_body = "#{ret}!" end def modify_response_headers end def show_actions render :text => "actions: #{action_methods.to_a.sort.join(', ')}" end protected def authenticate end end class EmptyController < ActionController::Base ; end class SubEmptyController < EmptyController ; end class NonDefaultPathController < ActionController::Base def self.controller_path; "i_am_not_default"; end end module Submodule class ContainedEmptyController < ActionController::Base ; end class ContainedSubEmptyController < ContainedEmptyController ; end class ContainedNonDefaultPathController < ActionController::Base def self.controller_path; "i_am_extremely_not_default"; end end end class BaseTest < Rack::TestCase # :api: plugin test "simple dispatching" do get "/dispatching/simple/index" assert_body "success" assert_status 200 assert_content_type "text/html; charset=utf-8" end # :api: plugin test "directly modifying response body" do get "/dispatching/simple/modify_response_body" assert_body "success" end # :api: plugin test "directly modifying response body twice" do get "/dispatching/simple/modify_response_body_twice" assert_body "success!" end test "controller path" do assert_equal 'dispatching/empty', EmptyController.controller_path assert_equal EmptyController.controller_path, EmptyController.new.controller_path end test "non-default controller path" do assert_equal 'i_am_not_default', NonDefaultPathController.controller_path assert_equal NonDefaultPathController.controller_path, NonDefaultPathController.new.controller_path end test "sub controller path" do assert_equal 'dispatching/sub_empty', SubEmptyController.controller_path assert_equal SubEmptyController.controller_path, SubEmptyController.new.controller_path end test "namespaced controller path" do assert_equal 'dispatching/submodule/contained_empty', Submodule::ContainedEmptyController.controller_path assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path end test "namespaced non-default controller path" do assert_equal 'i_am_extremely_not_default', Submodule::ContainedNonDefaultPathController.controller_path assert_equal Submodule::ContainedNonDefaultPathController.controller_path, Submodule::ContainedNonDefaultPathController.new.controller_path end test "namespaced sub controller path" do assert_equal 'dispatching/submodule/contained_sub_empty', Submodule::ContainedSubEmptyController.controller_path assert_equal Submodule::ContainedSubEmptyController.controller_path, Submodule::ContainedSubEmptyController.new.controller_path end test "controller name" do assert_equal 'empty', EmptyController.controller_name assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name end test "non-default path controller name" do assert_equal 'non_default_path', NonDefaultPathController.controller_name assert_equal 'contained_non_default_path', Submodule::ContainedNonDefaultPathController.controller_name end test "sub controller name" do assert_equal 'sub_empty', SubEmptyController.controller_name assert_equal 'contained_sub_empty', Submodule::ContainedSubEmptyController.controller_name end test "action methods" do assert_equal Set.new(%w( index modify_response_headers modify_response_body_twice modify_response_body show_actions )), SimpleController.action_methods assert_equal Set.new, EmptyController.action_methods assert_equal Set.new, Submodule::ContainedEmptyController.action_methods get "/dispatching/simple/show_actions" assert_body "actions: index, modify_response_body, modify_response_body_twice, modify_response_headers, show_actions" end end end rails-4.2.6/actionpack/test/controller/new_base/content_negotiation_test.rb000066400000000000000000000014341266740050600273440ustar00rootroot00000000000000require 'abstract_unit' module ContentNegotiation # This has no layout and it works class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "content_negotiation/basic/hello.html.erb" => "Hello world <%= request.formats.first.to_s %>!" )] def all render :text => self.formats.inspect end end class TestContentNegotiation < Rack::TestCase test "A */* Accept header will return HTML" do get "/content_negotiation/basic/hello", {}, "HTTP_ACCEPT" => "*/*" assert_body "Hello world */*!" end test "Not all mimes are converted to symbol" do get "/content_negotiation/basic/all", {}, "HTTP_ACCEPT" => "text/plain, mime/another" assert_body '[:text, "mime/another"]' end end end rails-4.2.6/actionpack/test/controller/new_base/content_type_test.rb000066400000000000000000000066251266740050600260140ustar00rootroot00000000000000require 'abstract_unit' module ContentType class BaseController < ActionController::Base def index render :text => "Hello world!" end def set_on_response_obj response.content_type = Mime::RSS render :text => "Hello world!" end def set_on_render render :text => "Hello world!", :content_type => Mime::RSS end end class ImpliedController < ActionController::Base # Template's mime type is used if no content_type is specified self.view_paths = [ActionView::FixtureResolver.new( "content_type/implied/i_am_html_erb.html.erb" => "Hello world!", "content_type/implied/i_am_xml_erb.xml.erb" => "Hello world!", "content_type/implied/i_am_html_builder.html.builder" => "xml.p 'Hello'", "content_type/implied/i_am_xml_builder.xml.builder" => "xml.awesome 'Hello'" )] end class CharsetController < ActionController::Base def set_on_response_obj response.charset = "utf-16" render :text => "Hello world!" end def set_as_nil_on_response_obj response.charset = nil render :text => "Hello world!" end end class ExplicitContentTypeTest < Rack::TestCase test "default response is HTML and UTF8" do with_routing do |set| set.draw do get ':controller', :action => 'index' end get "/content_type/base" assert_body "Hello world!" assert_header "Content-Type", "text/html; charset=utf-8" end end test "setting the content type of the response directly on the response object" do get "/content_type/base/set_on_response_obj" assert_body "Hello world!" assert_header "Content-Type", "application/rss+xml; charset=utf-8" end test "setting the content type of the response as an option to render" do get "/content_type/base/set_on_render" assert_body "Hello world!" assert_header "Content-Type", "application/rss+xml; charset=utf-8" end end class ImpliedContentTypeTest < Rack::TestCase test "sets Content-Type as text/html when rendering *.html.erb" do get "/content_type/implied/i_am_html_erb" assert_header "Content-Type", "text/html; charset=utf-8" end test "sets Content-Type as application/xml when rendering *.xml.erb" do get "/content_type/implied/i_am_xml_erb", "format" => "xml" assert_header "Content-Type", "application/xml; charset=utf-8" end test "sets Content-Type as text/html when rendering *.html.builder" do get "/content_type/implied/i_am_html_builder" assert_header "Content-Type", "text/html; charset=utf-8" end test "sets Content-Type as application/xml when rendering *.xml.builder" do get "/content_type/implied/i_am_xml_builder", "format" => "xml" assert_header "Content-Type", "application/xml; charset=utf-8" end end class ExplicitCharsetTest < Rack::TestCase test "setting the charset of the response directly on the response object" do get "/content_type/charset/set_on_response_obj" assert_body "Hello world!" assert_header "Content-Type", "text/html; charset=utf-16" end test "setting the charset of the response as nil directly on the response object" do get "/content_type/charset/set_as_nil_on_response_obj" assert_body "Hello world!" assert_header "Content-Type", "text/html; charset=utf-8" end end end rails-4.2.6/actionpack/test/controller/new_base/metal_test.rb000066400000000000000000000020061266740050600243700ustar00rootroot00000000000000require 'abstract_unit' module MetalTest class MetalMiddleware < ActionController::Middleware def call(env) if env["PATH_INFO"] =~ /authed/ app.call(env) else [401, headers, "Not authed!"] end end end class Endpoint def call(env) [200, {}, "Hello World"] end end class TestMiddleware < ActiveSupport::TestCase include RackTestUtils def setup @app = Rack::Builder.new do use MetalTest::MetalMiddleware run MetalTest::Endpoint.new end.to_app end test "it can call the next app by using @app" do env = Rack::MockRequest.env_for("/authed") response = @app.call(env) assert_equal "Hello World", body_to_string(response[2]) end test "it can return a response using the normal AC::Metal techniques" do env = Rack::MockRequest.env_for("/") response = @app.call(env) assert_equal "Not authed!", body_to_string(response[2]) assert_equal 401, response[0] end end end rails-4.2.6/actionpack/test/controller/new_base/middleware_test.rb000066400000000000000000000051761266740050600254160ustar00rootroot00000000000000require 'abstract_unit' module MiddlewareTest class MyMiddleware def initialize(app) @app = app end def call(env) result = @app.call(env) result[1]["Middleware-Test"] = "Success" result[1]["Middleware-Order"] = "First" result end end class ExclaimerMiddleware def initialize(app) @app = app end def call(env) result = @app.call(env) result[1]["Middleware-Order"] << "!" result end end class BlockMiddleware attr_accessor :configurable_message def initialize(app, &block) @app = app yield(self) if block_given? end def call(env) result = @app.call(env) result[1]["Configurable-Message"] = configurable_message result end end class MyController < ActionController::Metal use BlockMiddleware do |config| config.configurable_message = "Configured by block." end use MyMiddleware middleware.insert_before MyMiddleware, ExclaimerMiddleware def index self.response_body = "Hello World" end end class InheritedController < MyController end class ActionsController < ActionController::Metal use MyMiddleware, :only => :show middleware.insert_before MyMiddleware, ExclaimerMiddleware, :except => :index def index self.response_body = "index" end def show self.response_body = "show" end end class TestMiddleware < ActiveSupport::TestCase def setup @app = MyController.action(:index) end test "middleware that is 'use'd is called as part of the Rack application" do result = @app.call(env_for("/")) assert_equal "Hello World", RackTestUtils.body_to_string(result[2]) assert_equal "Success", result[1]["Middleware-Test"] end test "the middleware stack is exposed as 'middleware' in the controller" do result = @app.call(env_for("/")) assert_equal "First!", result[1]["Middleware-Order"] end test "middleware stack accepts block arguments" do result = @app.call(env_for("/")) assert_equal "Configured by block.", result[1]["Configurable-Message"] end test "middleware stack accepts only and except as options" do result = ActionsController.action(:show).call(env_for("/")) assert_equal "First!", result[1]["Middleware-Order"] result = ActionsController.action(:index).call(env_for("/")) assert_nil result[1]["Middleware-Order"] end def env_for(url) Rack::MockRequest.env_for(url) end end class TestInheritedMiddleware < TestMiddleware def setup @app = InheritedController.action(:index) end end end rails-4.2.6/actionpack/test/controller/new_base/render_action_test.rb000066400000000000000000000214461266740050600261130ustar00rootroot00000000000000require 'abstract_unit' module RenderAction # This has no layout and it works class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "render_action/basic/hello_world.html.erb" => "Hello world!" )] def hello_world render :action => "hello_world" end def hello_world_as_string render "hello_world" end def hello_world_as_string_with_options render "hello_world", :status => 404 end def hello_world_as_symbol render :hello_world end def hello_world_with_symbol render :action => :hello_world end def hello_world_with_layout render :action => "hello_world", :layout => true end def hello_world_with_layout_false render :action => "hello_world", :layout => false end def hello_world_with_layout_nil render :action => "hello_world", :layout => nil end def hello_world_with_custom_layout render :action => "hello_world", :layout => "greetings" end end class RenderActionTest < Rack::TestCase test "rendering an action using :action => " do get "/render_action/basic/hello_world" assert_body "Hello world!" assert_status 200 end test "rendering an action using ''" do get "/render_action/basic/hello_world_as_string" assert_body "Hello world!" assert_status 200 end test "rendering an action using '' and options" do get "/render_action/basic/hello_world_as_string_with_options" assert_body "Hello world!" assert_status 404 end test "rendering an action using :action" do get "/render_action/basic/hello_world_as_symbol" assert_body "Hello world!" assert_status 200 end test "rendering an action using :action => :hello_world" do get "/render_action/basic/hello_world_with_symbol" assert_body "Hello world!" assert_status 200 end end class RenderLayoutTest < Rack::TestCase def setup end test "rendering with layout => true" do assert_raise(ArgumentError) do get "/render_action/basic/hello_world_with_layout", {}, "action_dispatch.show_exceptions" => false end end test "rendering with layout => false" do get "/render_action/basic/hello_world_with_layout_false" assert_body "Hello world!" assert_status 200 end test "rendering with layout => :nil" do get "/render_action/basic/hello_world_with_layout_nil" assert_body "Hello world!" assert_status 200 end test "rendering with layout => 'greetings'" do assert_raise(ActionView::MissingTemplate) do get "/render_action/basic/hello_world_with_custom_layout", {}, "action_dispatch.show_exceptions" => false end end end end module RenderActionWithApplicationLayout # # ==== Render actions with layouts ==== class BasicController < ::ApplicationController # Set the view path to an application view structure with layouts self.view_paths = [ActionView::FixtureResolver.new( "render_action_with_application_layout/basic/hello_world.html.erb" => "Hello World!", "render_action_with_application_layout/basic/hello.html.builder" => "xml.p 'Hello'", "layouts/application.html.erb" => "Hi <%= yield %> OK, Bye", "layouts/greetings.html.erb" => "Greetings <%= yield %> Bye", "layouts/builder.html.builder" => "xml.html do\n xml << yield\nend" )] def hello_world render :action => "hello_world" end def hello_world_with_layout render :action => "hello_world", :layout => true end def hello_world_with_layout_false render :action => "hello_world", :layout => false end def hello_world_with_layout_nil render :action => "hello_world", :layout => nil end def hello_world_with_custom_layout render :action => "hello_world", :layout => "greetings" end def with_builder_and_layout render :action => "hello", :layout => "builder" end end class LayoutTest < Rack::TestCase test "rendering implicit application.html.erb as layout" do get "/render_action_with_application_layout/basic/hello_world" assert_body "Hi Hello World! OK, Bye" assert_status 200 end test "rendering with layout => true" do get "/render_action_with_application_layout/basic/hello_world_with_layout" assert_body "Hi Hello World! OK, Bye" assert_status 200 end test "rendering with layout => false" do get "/render_action_with_application_layout/basic/hello_world_with_layout_false" assert_body "Hello World!" assert_status 200 end test "rendering with layout => :nil" do get "/render_action_with_application_layout/basic/hello_world_with_layout_nil" assert_body "Hello World!" assert_status 200 end test "rendering with layout => 'greetings'" do get "/render_action_with_application_layout/basic/hello_world_with_custom_layout" assert_body "Greetings Hello World! Bye" assert_status 200 end end class TestLayout < Rack::TestCase testing BasicController test "builder works with layouts" do get :with_builder_and_layout assert_response "\n

Hello

\n\n" end end end module RenderActionWithControllerLayout class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "render_action_with_controller_layout/basic/hello_world.html.erb" => "Hello World!", "layouts/render_action_with_controller_layout/basic.html.erb" => "With Controller Layout! <%= yield %> Bye" )] def hello_world render :action => "hello_world" end def hello_world_with_layout render :action => "hello_world", :layout => true end def hello_world_with_layout_false render :action => "hello_world", :layout => false end def hello_world_with_layout_nil render :action => "hello_world", :layout => nil end def hello_world_with_custom_layout render :action => "hello_world", :layout => "greetings" end end class ControllerLayoutTest < Rack::TestCase test "render hello_world and implicitly use .html.erb as a layout." do get "/render_action_with_controller_layout/basic/hello_world" assert_body "With Controller Layout! Hello World! Bye" assert_status 200 end test "rendering with layout => true" do get "/render_action_with_controller_layout/basic/hello_world_with_layout" assert_body "With Controller Layout! Hello World! Bye" assert_status 200 end test "rendering with layout => false" do get "/render_action_with_controller_layout/basic/hello_world_with_layout_false" assert_body "Hello World!" assert_status 200 end test "rendering with layout => :nil" do get "/render_action_with_controller_layout/basic/hello_world_with_layout_nil" assert_body "Hello World!" assert_status 200 end end end module RenderActionWithBothLayouts class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new({ "render_action_with_both_layouts/basic/hello_world.html.erb" => "Hello World!", "layouts/application.html.erb" => "Oh Hi <%= yield %> Bye", "layouts/render_action_with_both_layouts/basic.html.erb" => "With Controller Layout! <%= yield %> Bye" })] def hello_world render :action => "hello_world" end def hello_world_with_layout render :action => "hello_world", :layout => true end def hello_world_with_layout_false render :action => "hello_world", :layout => false end def hello_world_with_layout_nil render :action => "hello_world", :layout => nil end end class ControllerLayoutTest < Rack::TestCase test "rendering implicitly use .html.erb over application.html.erb as a layout" do get "/render_action_with_both_layouts/basic/hello_world" assert_body "With Controller Layout! Hello World! Bye" assert_status 200 end test "rendering with layout => true" do get "/render_action_with_both_layouts/basic/hello_world_with_layout" assert_body "With Controller Layout! Hello World! Bye" assert_status 200 end test "rendering with layout => false" do get "/render_action_with_both_layouts/basic/hello_world_with_layout_false" assert_body "Hello World!" assert_status 200 end test "rendering with layout => :nil" do get "/render_action_with_both_layouts/basic/hello_world_with_layout_nil" assert_body "Hello World!" assert_status 200 end end end rails-4.2.6/actionpack/test/controller/new_base/render_body_test.rb000066400000000000000000000102221266740050600255610ustar00rootroot00000000000000require 'abstract_unit' module RenderBody class MinimalController < ActionController::Metal include AbstractController::Rendering include ActionController::Rendering def index render body: "Hello World!" end end class SimpleController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new] def index render body: "hello david" end end class WithLayoutController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "layouts/application.erb" => "<%= yield %>, I'm here!", "layouts/greetings.erb" => "<%= yield %>, I wish thee well.", "layouts/ivar.erb" => "<%= yield %>, <%= @ivar %>" )] def index render body: "hello david" end def custom_code render body: "hello world", status: 404 end def with_custom_code_as_string render body: "hello world", status: "404 Not Found" end def with_nil render body: nil end def with_nil_and_status render body: nil, status: 403 end def with_false render body: false end def with_layout_true render body: "hello world", layout: true end def with_layout_false render body: "hello world", layout: false end def with_layout_nil render body: "hello world", layout: nil end def with_custom_layout render body: "hello world", layout: "greetings" end def with_custom_content_type response.headers['Content-Type'] = 'application/json' render body: '["troll","face"]' end def with_ivar_in_layout @ivar = "hello world" render body: "hello world", layout: "ivar" end end class RenderBodyTest < Rack::TestCase test "rendering body from a minimal controller" do get "/render_body/minimal/index" assert_body "Hello World!" assert_status 200 end test "rendering body from an action with default options renders the body with the layout" do with_routing do |set| set.draw { get ':controller', action: 'index' } get "/render_body/simple" assert_body "hello david" assert_status 200 end end test "rendering body from an action with default options renders the body without the layout" do with_routing do |set| set.draw { get ':controller', action: 'index' } get "/render_body/with_layout" assert_body "hello david" assert_status 200 end end test "rendering body, while also providing a custom status code" do get "/render_body/with_layout/custom_code" assert_body "hello world" assert_status 404 end test "rendering body with nil returns an empty body" do get "/render_body/with_layout/with_nil" assert_body "" assert_status 200 end test "Rendering body with nil and custom status code returns an empty body and the status" do get "/render_body/with_layout/with_nil_and_status" assert_body "" assert_status 403 end test "rendering body with false returns the string 'false'" do get "/render_body/with_layout/with_false" assert_body "false" assert_status 200 end test "rendering body with layout: true" do get "/render_body/with_layout/with_layout_true" assert_body "hello world, I'm here!" assert_status 200 end test "rendering body with layout: 'greetings'" do get "/render_body/with_layout/with_custom_layout" assert_body "hello world, I wish thee well." assert_status 200 end test "specified content type should not be removed" do get "/render_body/with_layout/with_custom_content_type" assert_equal %w{ troll face }, JSON.parse(response.body) assert_equal 'application/json', response.headers['Content-Type'] end test "rendering body with layout: false" do get "/render_body/with_layout/with_layout_false" assert_body "hello world" assert_status 200 end test "rendering body with layout: nil" do get "/render_body/with_layout/with_layout_nil" assert_body "hello world" assert_status 200 end end end rails-4.2.6/actionpack/test/controller/new_base/render_context_test.rb000066400000000000000000000027741266740050600263250ustar00rootroot00000000000000require 'abstract_unit' # This is testing the decoupling of view renderer and view context # by allowing the controller to be used as view context. This is # similar to the way sinatra renders templates. module RenderContext class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "render_context/basic/hello_world.html.erb" => "<%= @value %> from <%= self.__controller_method__ %>", "layouts/basic.html.erb" => "?<%= yield %>?" )] # 1) Include ActionView::Context to bring the required dependencies include ActionView::Context # 2) Call _prepare_context that will do the required initialization before_action :_prepare_context def hello_world @value = "Hello" render :action => "hello_world", :layout => false end def with_layout @value = "Hello" render :action => "hello_world", :layout => "basic" end protected # 3) Set view_context to self def view_context self end def __controller_method__ "controller context!" end end class RenderContextTest < Rack::TestCase test "rendering using the controller as context" do get "/render_context/basic/hello_world" assert_body "Hello from controller context!" assert_status 200 end test "rendering using the controller as context with layout" do get "/render_context/basic/with_layout" assert_body "?Hello from controller context!?" assert_status 200 end end end rails-4.2.6/actionpack/test/controller/new_base/render_file_test.rb000066400000000000000000000036111266740050600255470ustar00rootroot00000000000000require 'abstract_unit' module RenderFile class BasicController < ActionController::Base self.view_paths = File.dirname(__FILE__) def index render :file => File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world]) end def with_instance_variables @secret = 'in the sauce' render :file => File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar') end def relative_path @secret = 'in the sauce' render :file => '../../fixtures/test/render_file_with_ivar' end def relative_path_with_dot @secret = 'in the sauce' render :file => '../../fixtures/test/dot.directory/render_file_with_ivar' end def pathname @secret = 'in the sauce' render :file => Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar]) end def with_locals path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals') render :file => path, :locals => {:secret => 'in the sauce'} end end class TestBasic < Rack::TestCase testing RenderFile::BasicController test "rendering simple template" do get :index assert_response "Hello world!" end test "rendering template with ivar" do get :with_instance_variables assert_response "The secret is in the sauce\n" end test "rendering a relative path" do get :relative_path assert_response "The secret is in the sauce\n" end test "rendering a relative path with dot" do get :relative_path_with_dot assert_response "The secret is in the sauce\n" end test "rendering a Pathname" do get :pathname assert_response "The secret is in the sauce\n" end test "rendering file with locals" do get :with_locals assert_response "The secret is in the sauce\n" end end end rails-4.2.6/actionpack/test/controller/new_base/render_html_test.rb000066400000000000000000000113461266740050600256000ustar00rootroot00000000000000require 'abstract_unit' module RenderHtml class MinimalController < ActionController::Metal include AbstractController::Rendering include ActionController::Rendering def index render html: "Hello World!" end end class SimpleController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new] def index render html: "hello david" end end class WithLayoutController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "layouts/application.html.erb" => "<%= yield %>, I'm here!", "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.", "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>" )] def index render html: "hello david" end def custom_code render html: "hello world", status: 404 end def with_custom_code_as_string render html: "hello world", status: "404 Not Found" end def with_nil render html: nil end def with_nil_and_status render html: nil, status: 403 end def with_false render html: false end def with_layout_true render html: "hello world", layout: true end def with_layout_false render html: "hello world", layout: false end def with_layout_nil render html: "hello world", layout: nil end def with_custom_layout render html: "hello world", layout: "greetings" end def with_ivar_in_layout @ivar = "hello world" render html: "hello world", layout: "ivar" end def with_unsafe_html_tag render html: "

hello world

", layout: nil end def with_safe_html_tag render html: "

hello world

".html_safe, layout: nil end end class RenderHtmlTest < Rack::TestCase test "rendering text from a minimal controller" do get "/render_html/minimal/index" assert_body "Hello World!" assert_status 200 end test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| set.draw { get ':controller', action: 'index' } get "/render_html/simple" assert_body "hello david" assert_status 200 end end test "rendering text from an action with default options renders the text without the layout" do with_routing do |set| set.draw { get ':controller', action: 'index' } get "/render_html/with_layout" assert_body "hello david" assert_status 200 end end test "rendering text, while also providing a custom status code" do get "/render_html/with_layout/custom_code" assert_body "hello world" assert_status 404 end test "rendering text with nil returns an empty body" do get "/render_html/with_layout/with_nil" assert_body "" assert_status 200 end test "Rendering text with nil and custom status code returns an empty body and the status" do get "/render_html/with_layout/with_nil_and_status" assert_body "" assert_status 403 end test "rendering text with false returns the string 'false'" do get "/render_html/with_layout/with_false" assert_body "false" assert_status 200 end test "rendering text with layout: true" do get "/render_html/with_layout/with_layout_true" assert_body "hello world, I'm here!" assert_status 200 end test "rendering text with layout: 'greetings'" do get "/render_html/with_layout/with_custom_layout" assert_body "hello world, I wish thee well." assert_status 200 end test "rendering text with layout: false" do get "/render_html/with_layout/with_layout_false" assert_body "hello world" assert_status 200 end test "rendering text with layout: nil" do get "/render_html/with_layout/with_layout_nil" assert_body "hello world" assert_status 200 end test "rendering html should escape the string if it is not html safe" do get "/render_html/with_layout/with_unsafe_html_tag" assert_body "<p>hello world</p>" assert_status 200 end test "rendering html should not escape the string if it is html safe" do get "/render_html/with_layout/with_safe_html_tag" assert_body "

hello world

" assert_status 200 end test "rendering from minimal controller returns response with text/html content type" do get "/render_html/minimal/index" assert_content_type "text/html" end test "rendering from normal controller returns response with text/html content type" do get "/render_html/simple/index" assert_content_type "text/html; charset=utf-8" end end end rails-4.2.6/actionpack/test/controller/new_base/render_implicit_action_test.rb000066400000000000000000000041171266740050600300010ustar00rootroot00000000000000require 'abstract_unit' module RenderImplicitAction class SimpleController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "render_implicit_action/simple/hello_world.html.erb" => "Hello world!", "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!", "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented" ), ActionView::FileSystemResolver.new(File.expand_path('../../../controller', __FILE__))] def hello_world() end end class RenderImplicitActionTest < Rack::TestCase test "render a simple action with new explicit call to render" do get "/render_implicit_action/simple/hello_world" assert_body "Hello world!" assert_status 200 end test "render an action with a missing method and has special characters" do get "/render_implicit_action/simple/hyphen-ated" assert_body "Hello hyphen-ated!" assert_status 200 end test "render an action called not_implemented" do get "/render_implicit_action/simple/not_implemented" assert_body "Not Implemented" assert_status 200 end test "render does not traverse the file system" do assert_raises(AbstractController::ActionNotFound) do action_name = %w(.. .. fixtures shared).join(File::SEPARATOR) SimpleController.action(action_name).call(Rack::MockRequest.env_for("/")) end end test "available_action? returns true for implicit actions" do assert SimpleController.new.available_action?(:hello_world) assert SimpleController.new.available_action?(:"hyphen-ated") assert SimpleController.new.available_action?(:not_implemented) end test "available_action? does not allow File::SEPARATOR on the name" do action_name = %w(evil .. .. path).join(File::SEPARATOR) assert_equal false, SimpleController.new.available_action?(action_name.to_sym) action_name = %w(evil path).join(File::SEPARATOR) assert_equal false, SimpleController.new.available_action?(action_name.to_sym) end end end rails-4.2.6/actionpack/test/controller/new_base/render_layout_test.rb000066400000000000000000000070161266740050600261500ustar00rootroot00000000000000require 'abstract_unit' module ControllerLayouts class ImplicitController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "layouts/application.html.erb" => "Main <%= yield %> Layout", "layouts/override.html.erb" => "Override! <%= yield %>", "basic.html.erb" => "Hello world!", "controller_layouts/implicit/layout_false.html.erb" => "hi(layout_false.html.erb)" )] def index render :template => "basic" end def override render :template => "basic", :layout => "override" end def layout_false render :layout => false end def builder_override end end class ImplicitNameController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "layouts/controller_layouts/implicit_name.html.erb" => "Implicit <%= yield %> Layout", "basic.html.erb" => "Hello world!" )] def index render :template => "basic" end end class RenderLayoutTest < Rack::TestCase test "rendering a normal template, but using the implicit layout" do get "/controller_layouts/implicit/index" assert_body "Main Hello world! Layout" assert_status 200 end test "rendering a normal template, but using an implicit NAMED layout" do get "/controller_layouts/implicit_name/index" assert_body "Implicit Hello world! Layout" assert_status 200 end test "overriding an implicit layout with render :layout option" do get "/controller_layouts/implicit/override" assert_body "Override! Hello world!" end end class LayoutOptionsTest < Rack::TestCase testing ControllerLayouts::ImplicitController test "rendering with :layout => false leaves out the implicit layout" do get :layout_false assert_response "hi(layout_false.html.erb)" end end class MismatchFormatController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "layouts/application.html.erb" => "<%= yield %>", "controller_layouts/mismatch_format/index.xml.builder" => "xml.instruct!", "controller_layouts/mismatch_format/implicit.builder" => "xml.instruct!", "controller_layouts/mismatch_format/explicit.js.erb" => "alert('foo');" )] def explicit render :layout => "application" end end class MismatchFormatTest < Rack::TestCase testing ControllerLayouts::MismatchFormatController XML_INSTRUCT = %Q(\n) test "if XML is selected, an HTML template is not also selected" do get :index, :format => "xml" assert_response XML_INSTRUCT end test "if XML is implicitly selected, an HTML template is not also selected" do get :implicit assert_response XML_INSTRUCT end test "a layout for JS is ignored even if explicitly provided for HTML" do get :explicit, { :format => "js" } assert_response "alert('foo');" end end class FalseLayoutMethodController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "controller_layouts/false_layout_method/index.js.erb" => "alert('foo');" )] layout :which_layout? def which_layout? false end def index end end class FalseLayoutMethodTest < Rack::TestCase testing ControllerLayouts::FalseLayoutMethodController test "access false layout returned by a method/proc" do get :index, :format => "js" assert_response "alert('foo');" end end end rails-4.2.6/actionpack/test/controller/new_base/render_partial_test.rb000066400000000000000000000041671266740050600262730ustar00rootroot00000000000000require 'abstract_unit' module RenderPartial class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "render_partial/basic/_basic.html.erb" => "BasicPartial!", "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>", "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>", "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>", "render_partial/basic/_final.json.erb" => "{ final: json }", "render_partial/basic/overridden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overridden' %><%= @test_unchanged %>", "render_partial/basic/_overridden.html.erb" => "ParentPartial!", "render_partial/child/_overridden.html.erb" => "OverriddenPartial!" )] def html_with_json_inside_json render :action => "with_json" end def changing @test_unchanged = 'hello' render :action => "basic" end def overridden @test_unchanged = 'hello' end end class ChildController < BasicController; end class TestPartial < Rack::TestCase testing BasicController test "rendering a partial in ActionView doesn't pull the ivars again from the controller" do get :changing assert_response("goodbyeBasicPartial!goodbye") end test "rendering a template with renders another partial with other format that renders other partial in the same format" do get :html_with_json_inside_json assert_content_type "text/html; charset=utf-8" assert_response "{ final: json }" end end class TestInheritedPartial < Rack::TestCase testing ChildController test "partial from parent controller gets picked if missing in child one" do get :changing assert_response("goodbyeBasicPartial!goodbye") end test "partial from child controller gets picked" do get :overridden assert_response("goodbyeOverriddenPartial!goodbye") end end end rails-4.2.6/actionpack/test/controller/new_base/render_plain_test.rb000066400000000000000000000102361266740050600257340ustar00rootroot00000000000000require 'abstract_unit' module RenderPlain class MinimalController < ActionController::Metal include AbstractController::Rendering include ActionController::Rendering def index render plain: "Hello World!" end end class SimpleController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new] def index render plain: "hello david" end end class WithLayoutController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "layouts/application.text.erb" => "<%= yield %>, I'm here!", "layouts/greetings.text.erb" => "<%= yield %>, I wish thee well.", "layouts/ivar.text.erb" => "<%= yield %>, <%= @ivar %>" )] def index render plain: "hello david" end def custom_code render plain: "hello world", status: 404 end def with_custom_code_as_string render plain: "hello world", status: "404 Not Found" end def with_nil render plain: nil end def with_nil_and_status render plain: nil, status: 403 end def with_false render plain: false end def with_layout_true render plain: "hello world", layout: true end def with_layout_false render plain: "hello world", layout: false end def with_layout_nil render plain: "hello world", layout: nil end def with_custom_layout render plain: "hello world", layout: "greetings" end def with_ivar_in_layout @ivar = "hello world" render plain: "hello world", layout: "ivar" end end class RenderPlainTest < Rack::TestCase test "rendering text from a minimal controller" do get "/render_plain/minimal/index" assert_body "Hello World!" assert_status 200 end test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| set.draw { get ':controller', action: 'index' } get "/render_plain/simple" assert_body "hello david" assert_status 200 end end test "rendering text from an action with default options renders the text without the layout" do with_routing do |set| set.draw { get ':controller', action: 'index' } get "/render_plain/with_layout" assert_body "hello david" assert_status 200 end end test "rendering text, while also providing a custom status code" do get "/render_plain/with_layout/custom_code" assert_body "hello world" assert_status 404 end test "rendering text with nil returns an empty body" do get "/render_plain/with_layout/with_nil" assert_body "" assert_status 200 end test "Rendering text with nil and custom status code returns an empty body and the status" do get "/render_plain/with_layout/with_nil_and_status" assert_body "" assert_status 403 end test "rendering text with false returns the string 'false'" do get "/render_plain/with_layout/with_false" assert_body "false" assert_status 200 end test "rendering text with layout: true" do get "/render_plain/with_layout/with_layout_true" assert_body "hello world, I'm here!" assert_status 200 end test "rendering text with layout: 'greetings'" do get "/render_plain/with_layout/with_custom_layout" assert_body "hello world, I wish thee well." assert_status 200 end test "rendering text with layout: false" do get "/render_plain/with_layout/with_layout_false" assert_body "hello world" assert_status 200 end test "rendering text with layout: nil" do get "/render_plain/with_layout/with_layout_nil" assert_body "hello world" assert_status 200 end test "rendering from minimal controller returns response with text/plain content type" do get "/render_plain/minimal/index" assert_content_type "text/plain" end test "rendering from normal controller returns response with text/plain content type" do get "/render_plain/simple/index" assert_content_type "text/plain; charset=utf-8" end end end rails-4.2.6/actionpack/test/controller/new_base/render_streaming_test.rb000066400000000000000000000070361266740050600266260ustar00rootroot00000000000000require 'abstract_unit' module RenderStreaming class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "render_streaming/basic/hello_world.html.erb" => "Hello world", "render_streaming/basic/boom.html.erb" => "<%= raise 'Ruby was here!' %>", "layouts/application.html.erb" => "<%= yield %>, I'm here!", "layouts/boom.html.erb" => "\"<%= yield %>" )] layout "application" def hello_world render :stream => true end def layout_exception render :action => "hello_world", :stream => true, :layout => "boom" end def template_exception render :action => "boom", :stream => true end def skip render :action => "hello_world", :stream => false end def explicit render :action => "hello_world", :stream => true end def no_layout render :action => "hello_world", :stream => true, :layout => false end def explicit_cache headers["Cache-Control"] = "private" render :action => "hello_world", :stream => true end end class StreamingTest < Rack::TestCase test "rendering with streaming enabled at the class level" do get "/render_streaming/basic/hello_world" assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" assert_streaming! end test "rendering with streaming given to render" do get "/render_streaming/basic/explicit" assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" assert_streaming! end test "rendering with streaming do not override explicit cache control given to render" do get "/render_streaming/basic/explicit_cache" assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" assert_streaming! "private" end test "rendering with streaming no layout" do get "/render_streaming/basic/no_layout" assert_body "b\r\nHello world\r\n0\r\n\r\n" assert_streaming! end test "skip rendering with streaming at render level" do get "/render_streaming/basic/skip" assert_body "Hello world, I'm here!" end test "rendering with layout exception" do get "/render_streaming/basic/layout_exception" assert_body "d\r\n\r\n0\r\n\r\n" assert_streaming! end test "rendering with template exception" do get "/render_streaming/basic/template_exception" assert_body "37\r\n\">\r\n0\r\n\r\n" assert_streaming! end test "rendering with template exception logs the exception" do io = StringIO.new _old, ActionView::Base.logger = ActionView::Base.logger, ActiveSupport::Logger.new(io) begin get "/render_streaming/basic/template_exception" io.rewind assert_match "Ruby was here!", io.read ensure ActionView::Base.logger = _old end end test "do not stream on HTTP/1.0" do get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0" assert_body "Hello world, I'm here!" assert_status 200 assert_equal "22", headers["Content-Length"] assert_equal nil, headers["Transfer-Encoding"] end def assert_streaming!(cache="no-cache") assert_status 200 assert_equal nil, headers["Content-Length"] assert_equal "chunked", headers["Transfer-Encoding"] assert_equal cache, headers["Cache-Control"] end end end rails-4.2.6/actionpack/test/controller/new_base/render_template_test.rb000066400000000000000000000153521266740050600264500ustar00rootroot00000000000000require 'abstract_unit' module RenderTemplate class WithoutLayoutController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "test/basic.html.erb" => "Hello from basic.html.erb", "shared.html.erb" => "Elastica", "locals.html.erb" => "The secret is <%= secret %>", "xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend", "with_raw.html.erb" => "Hello <%=raw 'this is raw' %>", "with_implicit_raw.html.erb" => "Hello <%== 'this is also raw' %> in an html template", "with_implicit_raw.text.erb" => "Hello <%== 'this is also raw' %> in a text template", "test/with_json.html.erb" => "<%= render :template => 'test/with_json', :formats => [:json] %>", "test/with_json.json.erb" => "<%= render :template => 'test/final', :formats => [:json] %>", "test/final.json.erb" => "{ final: json }", "test/with_error.html.erb" => "<%= raise 'i do not exist' %>" )] def index render :template => "test/basic" end def html_with_json_inside_json render :template => "test/with_json" end def index_without_key render "test/basic" end def in_top_directory render :template => 'shared' end def in_top_directory_with_slash render :template => '/shared' end def in_top_directory_with_slash_without_key render '/shared' end def with_locals render :template => "locals", :locals => { :secret => 'area51' } end def with_locals_without_key render "locals", :locals => { :secret => 'area51' } end def builder_template render :template => "xml_template" end def with_raw render :template => "with_raw" end def with_implicit_raw render :template => "with_implicit_raw" end def with_error render :template => "test/with_error" end private def show_detailed_exceptions? request.local? end end class TestWithoutLayout < Rack::TestCase testing RenderTemplate::WithoutLayoutController test "rendering a normal template with full path without layout" do get :index assert_response "Hello from basic.html.erb" end test "rendering a normal template with full path without layout without key" do get :index_without_key assert_response "Hello from basic.html.erb" end test "rendering a template not in a subdirectory" do get :in_top_directory assert_response "Elastica" end test "rendering a template not in a subdirectory with a leading slash" do get :in_top_directory_with_slash assert_response "Elastica" end test "rendering a template not in a subdirectory with a leading slash without key" do get :in_top_directory_with_slash_without_key assert_response "Elastica" end test "rendering a template with local variables" do get :with_locals assert_response "The secret is area51" end test "rendering a template with local variables without key" do get :with_locals assert_response "The secret is area51" end test "rendering a builder template" do get :builder_template, "format" => "xml" assert_response "\n

Hello

\n\n" end test "rendering a template with <%=raw stuff %>" do get :with_raw assert_body "Hello this is raw" assert_status 200 get :with_implicit_raw assert_body "Hello this is also raw in an html template" assert_status 200 get :with_implicit_raw, format: 'text' assert_body "Hello this is also raw in a text template" assert_status 200 end test "rendering a template with renders another template with other format that renders other template in the same format" do get :html_with_json_inside_json assert_content_type "text/html; charset=utf-8" assert_response "{ final: json }" end test "rendering a template with error properly excerts the code" do get :with_error assert_status 500 assert_match "i do not exist", response.body end end class WithLayoutController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "test/basic.html.erb" => "Hello from basic.html.erb", "shared.html.erb" => "Elastica", "layouts/application.html.erb" => "<%= yield %>, I'm here!", "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well." )] def index render :template => "test/basic" end def with_layout render :template => "test/basic", :layout => true end def with_layout_false render :template => "test/basic", :layout => false end def with_layout_nil render :template => "test/basic", :layout => nil end def with_custom_layout render :template => "test/basic", :layout => "greetings" end end class TestWithLayout < Rack::TestCase test "rendering with implicit layout" do with_routing do |set| set.draw { get ':controller', :action => :index } get "/render_template/with_layout" assert_body "Hello from basic.html.erb, I'm here!" assert_status 200 end end test "rendering with layout => :true" do get "/render_template/with_layout/with_layout" assert_body "Hello from basic.html.erb, I'm here!" assert_status 200 end test "rendering with layout => :false" do get "/render_template/with_layout/with_layout_false" assert_body "Hello from basic.html.erb" assert_status 200 end test "rendering with layout => :nil" do get "/render_template/with_layout/with_layout_nil" assert_body "Hello from basic.html.erb" assert_status 200 end test "rendering layout => 'greetings'" do get "/render_template/with_layout/with_custom_layout" assert_body "Hello from basic.html.erb, I wish thee well." assert_status 200 end end module Compatibility class WithoutLayoutController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "test/basic.html.erb" => "Hello from basic.html.erb", "shared.html.erb" => "Elastica" )] def with_forward_slash render :template => "/test/basic" end end class TestTemplateRenderWithForwardSlash < Rack::TestCase test "rendering a normal template with full path starting with a leading slash" do get "/render_template/compatibility/without_layout/with_forward_slash" assert_body "Hello from basic.html.erb" assert_status 200 end end end end rails-4.2.6/actionpack/test/controller/new_base/render_test.rb000066400000000000000000000105511266740050600245510ustar00rootroot00000000000000require 'abstract_unit' module Render class BlankRenderController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "render/blank_render/index.html.erb" => "Hello world!", "render/blank_render/access_request.html.erb" => "The request: <%= request.method.to_s.upcase %>", "render/blank_render/access_action_name.html.erb" => "Action Name: <%= action_name %>", "render/blank_render/access_controller_name.html.erb" => "Controller Name: <%= controller_name %>", "render/blank_render/overridden_with_own_view_paths_appended.html.erb" => "parent content", "render/blank_render/overridden_with_own_view_paths_prepended.html.erb" => "parent content", "render/blank_render/overridden.html.erb" => "parent content", "render/child_render/overridden.html.erb" => "child content" )] def index render end def access_request render :action => "access_request" end def render_action_name render :action => "access_action_name" end def overridden_with_own_view_paths_appended end def overridden_with_own_view_paths_prepended end def overridden end private def secretz render :text => "FAIL WHALE!" end end class DoubleRenderController < ActionController::Base def index render :text => "hello" render :text => "world" end end class ChildRenderController < BlankRenderController append_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_appended.html.erb" => "child content") prepend_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_prepended.html.erb" => "child content") end class RenderTest < Rack::TestCase test "render with blank" do with_routing do |set| set.draw do get ":controller", :action => 'index' end get "/render/blank_render" assert_body "Hello world!" assert_status 200 end end test "rendering more than once raises an exception" do with_routing do |set| set.draw do get ":controller", :action => 'index' end assert_raises(AbstractController::DoubleRenderError) do get "/render/double_render", {}, "action_dispatch.show_exceptions" => false end end end end class TestOnlyRenderPublicActions < Rack::TestCase # Only public methods on actual controllers are callable actions test "raises an exception when a method of Object is called" do assert_raises(AbstractController::ActionNotFound) do get "/render/blank_render/clone", {}, "action_dispatch.show_exceptions" => false end end test "raises an exception when a private method is called" do assert_raises(AbstractController::ActionNotFound) do get "/render/blank_render/secretz", {}, "action_dispatch.show_exceptions" => false end end end class TestVariousObjectsAvailableInView < Rack::TestCase test "The request object is accessible in the view" do get "/render/blank_render/access_request" assert_body "The request: GET" end test "The action_name is accessible in the view" do get "/render/blank_render/render_action_name" assert_body "Action Name: render_action_name" end test "The controller_name is accessible in the view" do get "/render/blank_render/access_controller_name" assert_body "Controller Name: blank_render" end end class TestViewInheritance < Rack::TestCase test "Template from child controller gets picked over parent one" do get "/render/child_render/overridden" assert_body "child content" end test "Template from child controller with custom view_paths prepended gets picked over parent one" do get "/render/child_render/overridden_with_own_view_paths_prepended" assert_body "child content" end test "Template from child controller with custom view_paths appended gets picked over parent one" do get "/render/child_render/overridden_with_own_view_paths_appended" assert_body "child content" end test "Template from parent controller gets picked if missing in child controller" do get "/render/child_render/index" assert_body "Hello world!" end end end rails-4.2.6/actionpack/test/controller/new_base/render_text_test.rb000066400000000000000000000074131266740050600256200ustar00rootroot00000000000000require 'abstract_unit' module RenderText class MinimalController < ActionController::Metal include AbstractController::Rendering include ActionController::Rendering def index render text: "Hello World!" end end class SimpleController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new] def index render text: "hello david" end end class WithLayoutController < ::ApplicationController self.view_paths = [ActionView::FixtureResolver.new( "layouts/application.html.erb" => "<%= yield %>, I'm here!", "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.", "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>" )] def index render text: "hello david" end def custom_code render text: "hello world", status: 404 end def with_custom_code_as_string render text: "hello world", status: "404 Not Found" end def with_nil render text: nil end def with_nil_and_status render text: nil, status: 403 end def with_false render text: false end def with_layout_true render text: "hello world", layout: true end def with_layout_false render text: "hello world", layout: false end def with_layout_nil render text: "hello world", layout: nil end def with_custom_layout render text: "hello world", layout: "greetings" end def with_ivar_in_layout @ivar = "hello world" render text: "hello world", layout: "ivar" end end class RenderTextTest < Rack::TestCase test "rendering text from a minimal controller" do get "/render_text/minimal/index" assert_body "Hello World!" assert_status 200 end test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| set.draw { get ':controller', action: 'index' } get "/render_text/simple" assert_body "hello david" assert_status 200 end end test "rendering text from an action with default options renders the text without the layout" do with_routing do |set| set.draw { get ':controller', action: 'index' } get "/render_text/with_layout" assert_body "hello david" assert_status 200 end end test "rendering text, while also providing a custom status code" do get "/render_text/with_layout/custom_code" assert_body "hello world" assert_status 404 end test "rendering text with nil returns an empty body" do get "/render_text/with_layout/with_nil" assert_body "" assert_status 200 end test "Rendering text with nil and custom status code returns an empty body and the status" do get "/render_text/with_layout/with_nil_and_status" assert_body "" assert_status 403 end test "rendering text with false returns the string 'false'" do get "/render_text/with_layout/with_false" assert_body "false" assert_status 200 end test "rendering text with layout: true" do get "/render_text/with_layout/with_layout_true" assert_body "hello world, I'm here!" assert_status 200 end test "rendering text with layout: 'greetings'" do get "/render_text/with_layout/with_custom_layout" assert_body "hello world, I wish thee well." assert_status 200 end test "rendering text with layout: false" do get "/render_text/with_layout/with_layout_false" assert_body "hello world" assert_status 200 end test "rendering text with layout: nil" do get "/render_text/with_layout/with_layout_nil" assert_body "hello world" assert_status 200 end end end rails-4.2.6/actionpack/test/controller/new_base/render_xml_test.rb000066400000000000000000000004041266740050600254250ustar00rootroot00000000000000require 'abstract_unit' module RenderXml # This has no layout and it works class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "render_xml/basic/with_render_erb" => "Hello world!" )] end end rails-4.2.6/actionpack/test/controller/output_escaping_test.rb000066400000000000000000000006231266740050600247170ustar00rootroot00000000000000require 'abstract_unit' class OutputEscapingTest < ActiveSupport::TestCase test "escape_html shouldn't die when passed nil" do assert ERB::Util.h(nil).blank? end test "escapeHTML should escape strings" do assert_equal "<>"", ERB::Util.h("<>\"") end test "escapeHTML shouldn't touch explicitly safe strings" do assert_equal "<", ERB::Util.h("<".html_safe) end end rails-4.2.6/actionpack/test/controller/parameters/000077500000000000000000000000001266740050600222645ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/parameters/accessors_test.rb000066400000000000000000000067731266740050600256520ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller/metal/strong_parameters' require 'active_support/core_ext/hash/transform_values' class ParametersAccessorsTest < ActiveSupport::TestCase setup do @params = ActionController::Parameters.new( person: { age: '32', name: { first: 'David', last: 'Heinemeier Hansson' }, addresses: [{city: 'Chicago', state: 'Illinois'}] } ) end test "[] retains permitted status" do @params.permit! assert @params[:person].permitted? assert @params[:person][:name].permitted? end test "[] retains unpermitted status" do assert_not @params[:person].permitted? assert_not @params[:person][:name].permitted? end test "each carries permitted status" do @params.permit! @params.each { |key, value| assert(value.permitted?) if key == "person" } end test "each carries unpermitted status" do @params.each { |key, value| assert_not(value.permitted?) if key == "person" } end test "each_pair carries permitted status" do @params.permit! @params.each_pair { |key, value| assert(value.permitted?) if key == "person" } end test "each_pair carries unpermitted status" do @params.each_pair { |key, value| assert_not(value.permitted?) if key == "person" } end test "except retains permitted status" do @params.permit! assert @params.except(:person).permitted? assert @params[:person].except(:name).permitted? end test "except retains unpermitted status" do assert_not @params.except(:person).permitted? assert_not @params[:person].except(:name).permitted? end test "fetch retains permitted status" do @params.permit! assert @params.fetch(:person).permitted? assert @params[:person].fetch(:name).permitted? end test "fetch retains unpermitted status" do assert_not @params.fetch(:person).permitted? assert_not @params[:person].fetch(:name).permitted? end test "reject retains permitted status" do assert_not @params.reject { |k| k == "person" }.permitted? end test "reject retains unpermitted status" do @params.permit! assert @params.reject { |k| k == "person" }.permitted? end test "select retains permitted status" do @params.permit! assert @params.select { |k| k == "person" }.permitted? end test "select retains unpermitted status" do assert_not @params.select { |k| k == "person" }.permitted? end test "slice retains permitted status" do @params.permit! assert @params.slice(:person).permitted? end test "slice retains unpermitted status" do assert_not @params.slice(:person).permitted? end test "transform_keys retains permitted status" do @params.permit! assert @params.transform_keys { |k| k }.permitted? end test "transform_keys retains unpermitted status" do assert_not @params.transform_keys { |k| k }.permitted? end test "transform_values retains permitted status" do @params.permit! assert @params.transform_values { |v| v }.permitted? end test "transform_values retains unpermitted status" do assert_not @params.transform_values { |v| v }.permitted? end test "values_at retains permitted status" do @params.permit! assert @params.values_at(:person).first.permitted? assert @params[:person].values_at(:name).first.permitted? end test "values_at retains unpermitted status" do assert_not @params.values_at(:person).first.permitted? assert_not @params[:person].values_at(:name).first.permitted? end end rails-4.2.6/actionpack/test/controller/parameters/always_permitted_parameters_test.rb000066400000000000000000000016311266740050600314510ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller/metal/strong_parameters' class AlwaysPermittedParametersTest < ActiveSupport::TestCase def setup ActionController::Parameters.action_on_unpermitted_parameters = :raise ActionController::Parameters.always_permitted_parameters = %w( controller action format ) end def teardown ActionController::Parameters.action_on_unpermitted_parameters = false ActionController::Parameters.always_permitted_parameters = %w( controller action ) end test "shows deprecations warning on NEVER_UNPERMITTED_PARAMS" do assert_deprecated do ActionController::Parameters::NEVER_UNPERMITTED_PARAMS end end test "permits parameters that are whitelisted" do params = ActionController::Parameters.new({ book: { pages: 65 }, format: "json" }) permitted = params.permit book: [:pages] assert permitted.permitted? end end rails-4.2.6/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb000066400000000000000000000033331266740050600311120ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller/metal/strong_parameters' class LogOnUnpermittedParamsTest < ActiveSupport::TestCase def setup ActionController::Parameters.action_on_unpermitted_parameters = :log end def teardown ActionController::Parameters.action_on_unpermitted_parameters = false end test "logs on unexpected param" do params = ActionController::Parameters.new({ book: { pages: 65 }, fishing: "Turnips" }) assert_logged("Unpermitted parameter: fishing") do params.permit(book: [:pages]) end end test "logs on unexpected params" do params = ActionController::Parameters.new({ book: { pages: 65 }, fishing: "Turnips", car: "Mersedes" }) assert_logged("Unpermitted parameters: fishing, car") do params.permit(book: [:pages]) end end test "logs on unexpected nested param" do params = ActionController::Parameters.new({ book: { pages: 65, title: "Green Cats and where to find then." } }) assert_logged("Unpermitted parameter: title") do params.permit(book: [:pages]) end end test "logs on unexpected nested params" do params = ActionController::Parameters.new({ book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" } }) assert_logged("Unpermitted parameters: title, author") do params.permit(book: [:pages]) end end private def assert_logged(message) old_logger = ActionController::Base.logger log = StringIO.new ActionController::Base.logger = Logger.new(log) begin yield log.rewind assert_match message, log.read ensure ActionController::Base.logger = old_logger end end end rails-4.2.6/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb000066400000000000000000000024131266740050600313100ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller/metal/strong_parameters' class MultiParameterAttributesTest < ActiveSupport::TestCase test "permitted multi-parameter attribute keys" do params = ActionController::Parameters.new({ book: { "shipped_at(1i)" => "2012", "shipped_at(2i)" => "3", "shipped_at(3i)" => "25", "shipped_at(4i)" => "10", "shipped_at(5i)" => "15", "published_at(1i)" => "1999", "published_at(2i)" => "2", "published_at(3i)" => "5", "price(1)" => "R$", "price(2f)" => "2.02" } }) permitted = params.permit book: [ :shipped_at, :price ] assert permitted.permitted? assert_equal "2012", permitted[:book]["shipped_at(1i)"] assert_equal "3", permitted[:book]["shipped_at(2i)"] assert_equal "25", permitted[:book]["shipped_at(3i)"] assert_equal "10", permitted[:book]["shipped_at(4i)"] assert_equal "15", permitted[:book]["shipped_at(5i)"] assert_equal "R$", permitted[:book]["price(1)"] assert_equal "2.02", permitted[:book]["price(2f)"] assert_nil permitted[:book]["published_at(1i)"] assert_nil permitted[:book]["published_at(2i)"] assert_nil permitted[:book]["published_at(3i)"] end end rails-4.2.6/actionpack/test/controller/parameters/mutators_test.rb000066400000000000000000000051421266740050600255300ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller/metal/strong_parameters' require 'active_support/core_ext/hash/transform_values' class ParametersMutatorsTest < ActiveSupport::TestCase setup do @params = ActionController::Parameters.new( person: { age: '32', name: { first: 'David', last: 'Heinemeier Hansson' }, addresses: [{city: 'Chicago', state: 'Illinois'}] } ) end test "delete retains permitted status" do @params.permit! assert @params.delete(:person).permitted? end test "delete retains unpermitted status" do assert_not @params.delete(:person).permitted? end test "delete_if retains permitted status" do @params.permit! assert @params.delete_if { |k| k == "person" }.permitted? end test "delete_if retains unpermitted status" do assert_not @params.delete_if { |k| k == "person" }.permitted? end test "extract! retains permitted status" do @params.permit! assert @params.extract!(:person).permitted? end test "extract! retains unpermitted status" do assert_not @params.extract!(:person).permitted? end test "keep_if retains permitted status" do @params.permit! assert @params.keep_if { |k,v| k == "person" }.permitted? end test "keep_if retains unpermitted status" do assert_not @params.keep_if { |k,v| k == "person" }.permitted? end test "reject! retains permitted status" do @params.permit! assert @params.reject! { |k| k == "person" }.permitted? end test "reject! retains unpermitted status" do assert_not @params.reject! { |k| k == "person" }.permitted? end test "select! retains permitted status" do @params.permit! assert @params.select! { |k| k != "person" }.permitted? end test "select! retains unpermitted status" do assert_not @params.select! { |k| k != "person" }.permitted? end test "slice! retains permitted status" do @params.permit! assert @params.slice!(:person).permitted? end test "slice! retains unpermitted status" do assert_not @params.slice!(:person).permitted? end test "transform_keys! retains permitted status" do @params.permit! assert @params.transform_keys! { |k| k }.permitted? end test "transform_keys! retains unpermitted status" do assert_not @params.transform_keys! { |k| k }.permitted? end test "transform_values! retains permitted status" do @params.permit! assert @params.transform_values! { |v| v }.permitted? end test "transform_values! retains unpermitted status" do assert_not @params.transform_values! { |v| v }.permitted? end end rails-4.2.6/actionpack/test/controller/parameters/nested_parameters_test.rb000066400000000000000000000141501266740050600273560ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller/metal/strong_parameters' class NestedParametersTest < ActiveSupport::TestCase def assert_filtered_out(params, key) assert !params.has_key?(key), "key #{key.inspect} has not been filtered out" end test "permitted nested parameters" do params = ActionController::Parameters.new({ book: { title: "Romeo and Juliet", authors: [{ name: "William Shakespeare", born: "1564-04-26" }, { name: "Christopher Marlowe" }, { name: %w(malicious injected names) }], details: { pages: 200, genre: "Tragedy" }, id: { isbn: 'x' } }, magazine: "Mjallo!" }) permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages }, :id ] assert permitted.permitted? assert_equal "Romeo and Juliet", permitted[:book][:title] assert_equal "William Shakespeare", permitted[:book][:authors][0][:name] assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name] assert_equal 200, permitted[:book][:details][:pages] assert_filtered_out permitted, :magazine assert_filtered_out permitted[:book], :id assert_filtered_out permitted[:book][:details], :genre assert_filtered_out permitted[:book][:authors][0], :born assert_filtered_out permitted[:book][:authors][2], :name end test "permitted nested parameters with a string or a symbol as a key" do params = ActionController::Parameters.new({ book: { 'authors' => [ { name: 'William Shakespeare', born: '1564-04-26' }, { name: 'Christopher Marlowe' } ] } }) permitted = params.permit book: [ { 'authors' => [ :name ] } ] assert_equal 'William Shakespeare', permitted[:book]['authors'][0][:name] assert_equal 'William Shakespeare', permitted[:book][:authors][0][:name] assert_equal 'Christopher Marlowe', permitted[:book]['authors'][1][:name] assert_equal 'Christopher Marlowe', permitted[:book][:authors][1][:name] permitted = params.permit book: [ { authors: [ :name ] } ] assert_equal 'William Shakespeare', permitted[:book]['authors'][0][:name] assert_equal 'William Shakespeare', permitted[:book][:authors][0][:name] assert_equal 'Christopher Marlowe', permitted[:book]['authors'][1][:name] assert_equal 'Christopher Marlowe', permitted[:book][:authors][1][:name] end test "nested arrays with strings" do params = ActionController::Parameters.new({ book: { genres: ["Tragedy"] } }) permitted = params.permit book: {genres: []} assert_equal ["Tragedy"], permitted[:book][:genres] end test "permit may specify symbols or strings" do params = ActionController::Parameters.new({ book: { title: "Romeo and Juliet", author: "William Shakespeare" }, magazine: "Shakespeare Today" }) permitted = params.permit({book: ["title", :author]}, "magazine") assert_equal "Romeo and Juliet", permitted[:book][:title] assert_equal "William Shakespeare", permitted[:book][:author] assert_equal "Shakespeare Today", permitted[:magazine] end test "nested array with strings that should be hashes" do params = ActionController::Parameters.new({ book: { genres: ["Tragedy"] } }) permitted = params.permit book: { genres: :type } assert_empty permitted[:book][:genres] end test "nested array with strings that should be hashes and additional values" do params = ActionController::Parameters.new({ book: { title: "Romeo and Juliet", genres: ["Tragedy"] } }) permitted = params.permit book: [ :title, { genres: :type } ] assert_equal "Romeo and Juliet", permitted[:book][:title] assert_empty permitted[:book][:genres] end test "nested string that should be a hash" do params = ActionController::Parameters.new({ book: { genre: "Tragedy" } }) permitted = params.permit book: { genre: :type } assert_nil permitted[:book][:genre] end test "fields_for-style nested params" do params = ActionController::Parameters.new({ book: { authors_attributes: { :'0' => { name: 'William Shakespeare', age_of_death: '52' }, :'1' => { name: 'Unattributed Assistant' }, :'2' => { name: %w(injected names)} } } }) permitted = params.permit book: { authors_attributes: [ :name ] } assert_not_nil permitted[:book][:authors_attributes]['0'] assert_not_nil permitted[:book][:authors_attributes]['1'] assert_empty permitted[:book][:authors_attributes]['2'] assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name] assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][:name] assert_filtered_out permitted[:book][:authors_attributes]['0'], :age_of_death end test "fields_for-style nested params with negative numbers" do params = ActionController::Parameters.new({ book: { authors_attributes: { :'-1' => { name: 'William Shakespeare', age_of_death: '52' }, :'-2' => { name: 'Unattributed Assistant' } } } }) permitted = params.permit book: { authors_attributes: [:name] } assert_not_nil permitted[:book][:authors_attributes]['-1'] assert_not_nil permitted[:book][:authors_attributes]['-2'] assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['-1'][:name] assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-2'][:name] assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death end test "nested number as key" do params = ActionController::Parameters.new({ product: { properties: { '0' => "prop0", '1' => "prop1" } } }) params = params.require(:product).permit(:properties => ["0"]) assert_not_nil params[:properties]["0"] assert_nil params[:properties]["1"] assert_equal "prop0", params[:properties]["0"] end end rails-4.2.6/actionpack/test/controller/parameters/parameters_permit_test.rb000066400000000000000000000231071266740050600273760ustar00rootroot00000000000000require 'abstract_unit' require 'action_dispatch/http/upload' require 'action_controller/metal/strong_parameters' class ParametersPermitTest < ActiveSupport::TestCase def assert_filtered_out(params, key) assert !params.has_key?(key), "key #{key.inspect} has not been filtered out" end setup do @params = ActionController::Parameters.new( person: { age: '32', name: { first: 'David', last: 'Heinemeier Hansson' }, addresses: [{city: 'Chicago', state: 'Illinois'}] } ) @struct_fields = [] %w(0 1 12).each do |number| ['', 'i', 'f'].each do |suffix| @struct_fields << "sf(#{number}#{suffix})" end end end def walk_permitted params params.each do |k,v| case v when ActionController::Parameters walk_permitted v when Array v.each { |x| walk_permitted v } end end end test 'iteration should not impact permit' do hash = {"foo"=>{"bar"=>{"0"=>{"baz"=>"hello", "zot"=>"1"}}}} params = ActionController::Parameters.new(hash) walk_permitted params sanitized = params[:foo].permit(bar: [:baz]) assert_equal({"0"=>{"baz"=>"hello"}}, sanitized[:bar].to_unsafe_h) end test 'if nothing is permitted, the hash becomes empty' do params = ActionController::Parameters.new(id: '1234') permitted = params.permit assert permitted.permitted? assert permitted.empty? end test 'key: permitted scalar values' do values = ['a', :a, nil] values += [0, 1.0, 2**128, BigDecimal.new(1)] values += [true, false] values += [Date.today, Time.now, DateTime.now] values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__), Rack::Test::UploadedFile.new(__FILE__)] values.each do |value| params = ActionController::Parameters.new(id: value) permitted = params.permit(:id) assert_equal value, permitted[:id] @struct_fields.each do |sf| params = ActionController::Parameters.new(sf => value) permitted = params.permit(:sf) assert_equal value, permitted[sf] end end end test 'key: unknown keys are filtered out' do params = ActionController::Parameters.new(id: '1234', injected: 'injected') permitted = params.permit(:id) assert_equal '1234', permitted[:id] assert_filtered_out permitted, :injected end test 'key: arrays are filtered out' do [[], [1], ['1']].each do |array| params = ActionController::Parameters.new(id: array) permitted = params.permit(:id) assert_filtered_out permitted, :id @struct_fields.each do |sf| params = ActionController::Parameters.new(sf => array) permitted = params.permit(:sf) assert_filtered_out permitted, sf end end end test 'key: hashes are filtered out' do [{}, {foo: 1}, {foo: 'bar'}].each do |hash| params = ActionController::Parameters.new(id: hash) permitted = params.permit(:id) assert_filtered_out permitted, :id @struct_fields.each do |sf| params = ActionController::Parameters.new(sf => hash) permitted = params.permit(:sf) assert_filtered_out permitted, sf end end end test 'key: non-permitted scalar values are filtered out' do params = ActionController::Parameters.new(id: Object.new) permitted = params.permit(:id) assert_filtered_out permitted, :id @struct_fields.each do |sf| params = ActionController::Parameters.new(sf => Object.new) permitted = params.permit(:sf) assert_filtered_out permitted, sf end end test 'key: it is not assigned if not present in params' do params = ActionController::Parameters.new(name: 'Joe') permitted = params.permit(:id) assert !permitted.has_key?(:id) end test 'key to empty array: empty arrays pass' do params = ActionController::Parameters.new(id: []) permitted = params.permit(id: []) assert_equal [], permitted[:id] end test 'do not break params filtering on nil values' do params = ActionController::Parameters.new(a: 1, b: [1, 2, 3], c: nil) permitted = params.permit(:a, c: [], b: []) assert_equal 1, permitted[:a] assert_equal [1, 2, 3], permitted[:b] assert_equal nil, permitted[:c] end test 'key to empty array: arrays of permitted scalars pass' do [['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array| params = ActionController::Parameters.new(id: array) permitted = params.permit(id: []) assert_equal array, permitted[:id] end end test 'key to empty array: permitted scalar values do not pass' do ['foo', 1].each do |permitted_scalar| params = ActionController::Parameters.new(id: permitted_scalar) permitted = params.permit(id: []) assert_filtered_out permitted, :id end end test 'key to empty array: arrays of non-permitted scalar do not pass' do [[Object.new], [[]], [[1]], [{}], [{id: '1'}]].each do |non_permitted_scalar| params = ActionController::Parameters.new(id: non_permitted_scalar) permitted = params.permit(id: []) assert_filtered_out permitted, :id end end test "fetch raises ParameterMissing exception" do e = assert_raises(ActionController::ParameterMissing) do @params.fetch :foo end assert_equal :foo, e.param end test "fetch with a default value of a hash does not mutate the object" do params = ActionController::Parameters.new({}) params.fetch :foo, {} assert_equal nil, params[:foo] end test 'hashes in array values get wrapped' do params = ActionController::Parameters.new(foo: [{}, {}]) params[:foo].each do |hash| assert !hash.permitted? end end # Strong params has an optimization to avoid looping every time you read # a key whose value is an array and building a new object. We check that # optimization here. test 'arrays are converted at most once' do params = ActionController::Parameters.new(foo: [{}]) assert_same params[:foo], params[:foo] end # Strong params has an internal cache to avoid duplicated loops in the most # common usage pattern. See the docs of the method `converted_arrays`. # # This test checks that if we push a hash to an array (in-place modification) # the cache does not get fooled, the hash is still wrapped as strong params, # and not permitted. test 'mutated arrays are detected' do params = ActionController::Parameters.new(users: [{id: 1}]) permitted = params.permit(users: [:id]) permitted[:users] << {injected: 1} assert_not permitted[:users].last.permitted? end test "fetch doesnt raise ParameterMissing exception if there is a default" do assert_equal "monkey", @params.fetch(:foo, "monkey") assert_equal "monkey", @params.fetch(:foo) { "monkey" } end test "not permitted is sticky beyond merges" do assert !@params.merge(a: "b").permitted? end test "permitted is sticky beyond merges" do @params.permit! assert @params.merge(a: "b").permitted? end test "modifying the parameters" do @params[:person][:hometown] = "Chicago" @params[:person][:family] = { brother: "Jonas" } assert_equal "Chicago", @params[:person][:hometown] assert_equal "Jonas", @params[:person][:family][:brother] end test "permit state is kept on a dup" do @params.permit! assert_equal @params.permitted?, @params.dup.permitted? end test "permit is recursive" do @params.permit! assert @params.permitted? assert @params[:person].permitted? assert @params[:person][:name].permitted? assert @params[:person][:addresses][0].permitted? end test "permitted takes a default value when Parameters.permit_all_parameters is set" do begin ActionController::Parameters.permit_all_parameters = true params = ActionController::Parameters.new({ person: { age: "32", name: { first: "David", last: "Heinemeier Hansson" } }}) assert params.slice(:person).permitted? assert params[:person][:name].permitted? ensure ActionController::Parameters.permit_all_parameters = false end end test "permitting parameters as an array" do assert_equal "32", @params[:person].permit([ :age ])[:age] end test "to_h returns empty hash on unpermitted params" do assert @params.to_h.is_a? Hash assert_not @params.to_h.is_a? ActionController::Parameters assert @params.to_h.empty? end test "to_h returns converted hash on permitted params" do @params.permit! assert @params.to_h.is_a? Hash assert_not @params.to_h.is_a? ActionController::Parameters assert_equal @params.to_hash, @params.to_h end test "to_h returns converted hash when .permit_all_parameters is set" do begin ActionController::Parameters.permit_all_parameters = true params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") assert params.to_h.is_a? Hash assert_not @params.to_h.is_a? ActionController::Parameters assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h) ensure ActionController::Parameters.permit_all_parameters = false end end test "to_h returns always permitted parameter on unpermitted params" do params = ActionController::Parameters.new( controller: "users", action: "create", user: { name: "Sengoku Nadeko" } ) assert_equal({ "controller" => "users", "action" => "create" }, params.to_h) end test "to_unsafe_h returns unfiltered params" do assert @params.to_h.is_a? Hash assert_not @params.to_h.is_a? ActionController::Parameters assert_equal @params.to_hash, @params.to_unsafe_h end end rails-4.2.6/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb000066400000000000000000000015571266740050600314420ustar00rootroot00000000000000require 'abstract_unit' require 'action_controller/metal/strong_parameters' class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase def setup ActionController::Parameters.action_on_unpermitted_parameters = :raise end def teardown ActionController::Parameters.action_on_unpermitted_parameters = false end test "raises on unexpected params" do params = ActionController::Parameters.new({ book: { pages: 65 }, fishing: "Turnips" }) assert_raises(ActionController::UnpermittedParameters) do params.permit(book: [:pages]) end end test "raises on unexpected nested params" do params = ActionController::Parameters.new({ book: { pages: 65, title: "Green Cats and where to find then." } }) assert_raises(ActionController::UnpermittedParameters) do params.permit(book: [:pages]) end end end rails-4.2.6/actionpack/test/controller/params_wrapper_test.rb000066400000000000000000000265101266740050600245340ustar00rootroot00000000000000require 'abstract_unit' module Admin; class User; end; end module ParamsWrapperTestHelp def with_default_wrapper_options(&block) @controller.class._set_wrapper_options({:format => [:json]}) @controller.class.inherited(@controller.class) yield end def assert_parameters(expected) assert_equal expected, self.class.controller_class.last_parameters end end class ParamsWrapperTest < ActionController::TestCase include ParamsWrapperTestHelp class UsersController < ActionController::Base class << self attr_accessor :last_parameters end def parse self.class.last_parameters = request.params.except(:controller, :action) head :ok end end class User; end class Person; end tests UsersController def teardown UsersController.last_parameters = nil end def test_filtered_parameters with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu' } assert_equal @request.filtered_parameters, { 'controller' => 'params_wrapper_test/users', 'action' => 'parse', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' } } end end def test_derived_name_from_controller with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) end end def test_specify_wrapper_name with_default_wrapper_options do UsersController.wrap_parameters :person @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) end end def test_specify_wrapper_model with_default_wrapper_options do UsersController.wrap_parameters Person @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) end end def test_specify_include_option with_default_wrapper_options do UsersController.wrap_parameters :include => :username @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end def test_specify_exclude_option with_default_wrapper_options do UsersController.wrap_parameters :exclude => :title @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end def test_specify_both_wrapper_name_and_include_option with_default_wrapper_options do UsersController.wrap_parameters :person, :include => :username @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) end end def test_not_enabled_format with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/xml' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) end end def test_wrap_parameters_false with_default_wrapper_options do UsersController.wrap_parameters false @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) end end def test_specify_format with_default_wrapper_options do UsersController.wrap_parameters :format => :xml @request.env['CONTENT_TYPE'] = 'application/xml' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) end end def test_not_wrap_reserved_parameters with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu' } assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) end end def test_no_double_wrap_if_key_exists with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'user' => { 'username' => 'sikachu' }} assert_parameters({ 'user' => { 'username' => 'sikachu' }}) end end def test_nested_params with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'person' => { 'username' => 'sikachu' }} assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}}) end end def test_derived_wrapped_keys_from_matching_model User.expects(:respond_to?).with(:attribute_names).returns(true) User.expects(:attribute_names).twice.returns(["username"]) with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end def test_derived_wrapped_keys_from_specified_model with_default_wrapper_options do Person.expects(:respond_to?).with(:attribute_names).returns(true) Person.expects(:attribute_names).twice.returns(["username"]) UsersController.wrap_parameters Person @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) end end def test_not_wrapping_abstract_model User.expects(:respond_to?).with(:attribute_names).returns(true) User.expects(:attribute_names).returns([]) with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) end end def test_preserves_query_string_params with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' get :parse, { 'user' => { 'username' => 'nixon' } } assert_parameters( {'user' => { 'username' => 'nixon' } } ) end end def test_empty_parameter_set with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, {} assert_parameters( {'user' => { } } ) end end end class NamespacedParamsWrapperTest < ActionController::TestCase include ParamsWrapperTestHelp module Admin module Users class UsersController < ActionController::Base; class << self attr_accessor :last_parameters end def parse self.class.last_parameters = request.params.except(:controller, :action) head :ok end end end end class SampleOne def self.attribute_names ["username"] end end class SampleTwo def self.attribute_names ["title"] end end tests Admin::Users::UsersController def teardown Admin::Users::UsersController.last_parameters = nil end def test_derived_name_from_controller with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu' } assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) end end def test_namespace_lookup_from_model Admin.const_set(:User, Class.new(SampleOne)) begin with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end ensure Admin.send :remove_const, :User end end def test_hierarchy_namespace_lookup_from_model Object.const_set(:User, Class.new(SampleTwo)) begin with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'title' => 'Developer' }}) end ensure Object.send :remove_const, :User end end end class AnonymousControllerParamsWrapperTest < ActionController::TestCase include ParamsWrapperTestHelp tests(Class.new(ActionController::Base) do class << self attr_accessor :last_parameters end def parse self.class.last_parameters = request.params.except(:controller, :action) head :ok end end) def test_does_not_implicitly_wrap_params with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu' }) end end def test_does_wrap_params_if_name_provided with_default_wrapper_options do @controller.class.wrap_parameters(:name => "guest") @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu', 'guest' => { 'username' => 'sikachu' }}) end end end class IrregularInflectionParamsWrapperTest < ActionController::TestCase include ParamsWrapperTestHelp class ParamswrappernewsItem def self.attribute_names ['test_attr'] end end class ParamswrappernewsController < ActionController::Base class << self attr_accessor :last_parameters end def parse self.class.last_parameters = request.params.except(:controller, :action) head :ok end end tests ParamswrappernewsController def test_uses_model_attribute_names_with_irregular_inflection with_dup do ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'paramswrappernews_item', 'paramswrappernews' end with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' } assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }}) end end end private def with_dup original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en] ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup) yield ensure ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original) end end rails-4.2.6/actionpack/test/controller/permitted_params_test.rb000066400000000000000000000013011266740050600250400ustar00rootroot00000000000000require 'abstract_unit' class PeopleController < ActionController::Base def create render text: params[:person].permitted? ? "permitted" : "forbidden" end def create_with_permit render text: params[:person].permit(:name).permitted? ? "permitted" : "forbidden" end end class ActionControllerPermittedParamsTest < ActionController::TestCase tests PeopleController test "parameters are forbidden" do post :create, { person: { name: "Mjallo!" } } assert_equal "forbidden", response.body end test "parameters can be permitted and are then not forbidden" do post :create_with_permit, { person: { name: "Mjallo!" } } assert_equal "permitted", response.body end end rails-4.2.6/actionpack/test/controller/redirect_test.rb000066400000000000000000000224211266740050600233070ustar00rootroot00000000000000require 'abstract_unit' class WorkshopsController < ActionController::Base end class RedirectController < ActionController::Base # empty method not used anywhere to ensure methods like # `status` and `location` aren't called on `redirect_to` calls def status; render :text => 'called status'; end def location; render :text => 'called location'; end def simple_redirect redirect_to :action => "hello_world" end def redirect_with_status redirect_to({:action => "hello_world", :status => 301}) end def redirect_with_status_hash redirect_to({:action => "hello_world"}, {:status => 301}) end def redirect_with_protocol redirect_to :action => "hello_world", :protocol => "https" end def url_redirect_with_status redirect_to("http://www.example.com", :status => :moved_permanently) end def url_redirect_with_status_hash redirect_to("http://www.example.com", {:status => 301}) end def relative_url_redirect_with_status redirect_to("/things/stuff", :status => :found) end def relative_url_redirect_with_status_hash redirect_to("/things/stuff", {:status => 301}) end def redirect_to_back_with_status redirect_to :back, :status => 307 end def host_redirect redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host' end def module_redirect redirect_to :controller => 'module_test/module_redirect', :action => "hello_world" end def redirect_with_assigns @hello = "world" redirect_to :action => "hello_world" end def redirect_to_url redirect_to "http://www.rubyonrails.org/" end def redirect_to_url_with_unescaped_query_string redirect_to "http://dev.rubyonrails.org/query?status=new" end def redirect_to_url_with_complex_scheme redirect_to "x-test+scheme.complex:redirect" end def redirect_to_url_with_network_path_reference redirect_to "//www.rubyonrails.org/" end def redirect_to_back redirect_to :back end def redirect_to_existing_record redirect_to Workshop.new(5) end def redirect_to_new_record redirect_to Workshop.new(nil) end def redirect_to_nil redirect_to nil end def redirect_to_params redirect_to ActionController::Parameters.new(status: 200, protocol: 'javascript', f: '%0Aeval(name)') end def redirect_to_with_block redirect_to proc { "http://www.rubyonrails.org/" } end def redirect_to_with_block_and_assigns @url = "http://www.rubyonrails.org/" redirect_to proc { @url } end def redirect_to_with_block_and_options redirect_to proc { {:action => "hello_world"} } end def redirect_with_header_break redirect_to "/lol\r\nwat" end def redirect_with_null_bytes redirect_to "\000/lol\r\nwat" end def rescue_errors(e) raise e end protected def dashbord_url(id, message) url_for :action => "dashboard", :params => { "id" => id, "message" => message } end end class RedirectTest < ActionController::TestCase tests RedirectController def test_simple_redirect get :simple_redirect assert_response :redirect assert_equal "http://test.host/redirect/hello_world", redirect_to_url end def test_redirect_with_header_break get :redirect_with_header_break assert_response :redirect assert_equal "http://test.host/lolwat", redirect_to_url end def test_redirect_with_null_bytes get :redirect_with_null_bytes assert_response :redirect assert_equal "http://test.host/lolwat", redirect_to_url end def test_redirect_with_no_status get :simple_redirect assert_response 302 assert_equal "http://test.host/redirect/hello_world", redirect_to_url end def test_redirect_with_status get :redirect_with_status assert_response 301 assert_equal "http://test.host/redirect/hello_world", redirect_to_url end def test_redirect_with_status_hash get :redirect_with_status_hash assert_response 301 assert_equal "http://test.host/redirect/hello_world", redirect_to_url end def test_redirect_with_protocol get :redirect_with_protocol assert_response 302 assert_equal "https://test.host/redirect/hello_world", redirect_to_url end def test_url_redirect_with_status get :url_redirect_with_status assert_response 301 assert_equal "http://www.example.com", redirect_to_url end def test_url_redirect_with_status_hash get :url_redirect_with_status_hash assert_response 301 assert_equal "http://www.example.com", redirect_to_url end def test_relative_url_redirect_with_status get :relative_url_redirect_with_status assert_response 302 assert_equal "http://test.host/things/stuff", redirect_to_url end def test_relative_url_redirect_with_status_hash get :relative_url_redirect_with_status_hash assert_response 301 assert_equal "http://test.host/things/stuff", redirect_to_url end def test_redirect_to_back_with_status @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" get :redirect_to_back_with_status assert_response 307 assert_equal "http://www.example.com/coming/from", redirect_to_url end def test_simple_redirect_using_options get :host_redirect assert_response :redirect assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host' end def test_module_redirect get :module_redirect assert_response :redirect assert_redirected_to "http://test.host/module_test/module_redirect/hello_world" end def test_module_redirect_using_options get :module_redirect assert_response :redirect assert_redirected_to :controller => 'module_test/module_redirect', :action => 'hello_world' end def test_redirect_with_assigns get :redirect_with_assigns assert_response :redirect assert_equal "world", assigns["hello"] end def test_redirect_to_url get :redirect_to_url assert_response :redirect assert_redirected_to "http://www.rubyonrails.org/" end def test_redirect_to_url_with_unescaped_query_string get :redirect_to_url_with_unescaped_query_string assert_response :redirect assert_redirected_to "http://dev.rubyonrails.org/query?status=new" end def test_redirect_to_url_with_complex_scheme get :redirect_to_url_with_complex_scheme assert_response :redirect assert_equal "x-test+scheme.complex:redirect", redirect_to_url end def test_redirect_to_url_with_network_path_reference get :redirect_to_url_with_network_path_reference assert_response :redirect assert_equal "//www.rubyonrails.org/", redirect_to_url end def test_redirect_to_back @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" get :redirect_to_back assert_response :redirect assert_equal "http://www.example.com/coming/from", redirect_to_url end def test_redirect_to_back_with_no_referer assert_raise(ActionController::RedirectBackError) { @request.env["HTTP_REFERER"] = nil get :redirect_to_back } end def test_redirect_to_record with_routing do |set| set.draw do resources :workshops get ':controller/:action' end get :redirect_to_existing_record assert_equal "http://test.host/workshops/5", redirect_to_url assert_redirected_to Workshop.new(5) get :redirect_to_new_record assert_equal "http://test.host/workshops", redirect_to_url assert_redirected_to Workshop.new(nil) end end def test_redirect_to_nil assert_raise(ActionController::ActionControllerError) do get :redirect_to_nil end end def test_redirect_to_params assert_raise(ActionController::ActionControllerError) do get :redirect_to_params end end def test_redirect_to_with_block get :redirect_to_with_block assert_response :redirect assert_redirected_to "http://www.rubyonrails.org/" end def test_redirect_to_with_block_and_assigns get :redirect_to_with_block_and_assigns assert_response :redirect assert_redirected_to "http://www.rubyonrails.org/" end def test_redirect_to_with_block_and_accepted_options with_routing do |set| set.draw do get ':controller/:action' end get :redirect_to_with_block_and_options assert_response :redirect assert_redirected_to "http://test.host/redirect/hello_world" end end end module ModuleTest class ModuleRedirectController < ::RedirectController def module_redirect redirect_to :controller => '/redirect', :action => "hello_world" end end class ModuleRedirectTest < ActionController::TestCase tests ModuleRedirectController def test_simple_redirect get :simple_redirect assert_response :redirect assert_equal "http://test.host/module_test/module_redirect/hello_world", redirect_to_url end def test_simple_redirect_using_options get :host_redirect assert_response :redirect assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host' end def test_module_redirect get :module_redirect assert_response :redirect assert_equal "http://test.host/redirect/hello_world", redirect_to_url end def test_module_redirect_using_options get :module_redirect assert_response :redirect assert_redirected_to :controller => '/redirect', :action => "hello_world" end end end rails-4.2.6/actionpack/test/controller/render_js_test.rb000066400000000000000000000013501266740050600234570ustar00rootroot00000000000000require 'abstract_unit' require 'controller/fake_models' require 'pathname' class RenderJSTest < ActionController::TestCase class TestController < ActionController::Base protect_from_forgery def self.controller_path 'test' end def render_vanilla_js_hello render :js => "alert('hello')" end def show_partial render :partial => 'partial' end end tests TestController def test_render_vanilla_js xhr :get, :render_vanilla_js_hello assert_equal "alert('hello')", @response.body assert_equal "text/javascript", @response.content_type end def test_should_render_js_partial xhr :get, :show_partial, :format => 'js' assert_equal 'partial js', @response.body end end rails-4.2.6/actionpack/test/controller/render_json_test.rb000066400000000000000000000073031266740050600240200ustar00rootroot00000000000000require 'abstract_unit' require 'controller/fake_models' require 'active_support/logger' require 'pathname' class RenderJsonTest < ActionController::TestCase class JsonRenderable def as_json(options={}) hash = { :a => :b, :c => :d, :e => :f } hash.except!(*options[:except]) if options[:except] hash end def to_json(options = {}) super :except => [:c, :e] end end class TestController < ActionController::Base protect_from_forgery def self.controller_path 'test' end def render_json_nil render :json => nil end def render_json_render_to_string render :text => render_to_string(:json => '[]') end def render_json_hello_world render :json => ActiveSupport::JSON.encode(:hello => 'world') end def render_json_hello_world_with_status render :json => ActiveSupport::JSON.encode(:hello => 'world'), :status => 401 end def render_json_hello_world_with_callback render :json => ActiveSupport::JSON.encode(:hello => 'world'), :callback => 'alert' end def render_json_with_custom_content_type render :json => ActiveSupport::JSON.encode(:hello => 'world'), :content_type => 'text/javascript' end def render_symbol_json render :json => ActiveSupport::JSON.encode(:hello => 'world') end def render_json_with_render_to_string render :json => {:hello => render_to_string(:partial => 'partial')} end def render_json_with_extra_options render :json => JsonRenderable.new, :except => [:c, :e] end def render_json_without_options render :json => JsonRenderable.new end end tests TestController def setup # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get # a more accurate simulation of what happens in "real life". super @controller.logger = ActiveSupport::Logger.new(nil) @request.host = "www.nextangle.com" end def test_render_json_nil get :render_json_nil assert_equal 'null', @response.body assert_equal 'application/json', @response.content_type end def test_render_json_render_to_string get :render_json_render_to_string assert_equal '[]', @response.body end def test_render_json get :render_json_hello_world assert_equal '{"hello":"world"}', @response.body assert_equal 'application/json', @response.content_type end def test_render_json_with_status get :render_json_hello_world_with_status assert_equal '{"hello":"world"}', @response.body assert_equal 401, @response.status end def test_render_json_with_callback xhr :get, :render_json_hello_world_with_callback assert_equal '/**/alert({"hello":"world"})', @response.body assert_equal 'text/javascript', @response.content_type end def test_render_json_with_custom_content_type xhr :get, :render_json_with_custom_content_type assert_equal '{"hello":"world"}', @response.body assert_equal 'text/javascript', @response.content_type end def test_render_symbol_json get :render_symbol_json assert_equal '{"hello":"world"}', @response.body assert_equal 'application/json', @response.content_type end def test_render_json_with_render_to_string get :render_json_with_render_to_string assert_equal '{"hello":"partial html"}', @response.body assert_equal 'application/json', @response.content_type end def test_render_json_forwards_extra_options get :render_json_with_extra_options assert_equal '{"a":"b"}', @response.body assert_equal 'application/json', @response.content_type end def test_render_json_calls_to_json_from_object get :render_json_without_options assert_equal '{"a":"b"}', @response.body end end rails-4.2.6/actionpack/test/controller/render_other_test.rb000066400000000000000000000010521266740050600241630ustar00rootroot00000000000000require 'abstract_unit' class RenderOtherTest < ActionController::TestCase class TestController < ActionController::Base def render_simon_says render :simon => "foo" end end tests TestController def test_using_custom_render_option ActionController.add_renderer :simon do |says, options| self.content_type = Mime::TEXT self.response_body = "Simon says: #{says}" end get :render_simon_says assert_equal "Simon says: foo", @response.body ensure ActionController.remove_renderer :simon end end rails-4.2.6/actionpack/test/controller/render_test.rb000066400000000000000000000443761266740050600230020ustar00rootroot00000000000000require 'abstract_unit' require 'controller/fake_models' require 'pathname' class TestControllerWithExtraEtags < ActionController::Base etag { nil } etag { 'ab' } etag { :cde } etag { [:f] } etag { nil } def fresh render text: "stale" if stale?(etag: '123', template: false) end def array render text: "stale" if stale?(etag: %w(1 2 3), template: false) end def with_template if stale? template: 'test/hello_world' render text: 'stale' end end end class TestController < ActionController::Base protect_from_forgery before_action :set_variable_for_layout class LabellingFormBuilder < ActionView::Helpers::FormBuilder end layout :determine_layout def name nil end private :name helper_method :name def hello_world end def conditional_hello if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123]) render :action => 'hello_world' end end def conditional_hello_with_record record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123") if stale?(record) render :action => 'hello_world' end end def dynamic_render render params[:id] # => String, AC:Params end def dynamic_render_permit render params[:id].permit(:file) end def dynamic_render_with_file # This is extremely bad, but should be possible to do. file = params[:id] # => String, AC:Params render file: file end def conditional_hello_with_public_header if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true) render :action => 'hello_world' end end def conditional_hello_with_public_header_with_record record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123") if stale?(record, :public => true) render :action => 'hello_world' end end def conditional_hello_with_public_header_and_expires_at expires_in 1.minute if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true) render :action => 'hello_world' end end def conditional_hello_with_expires_in expires_in 60.1.seconds render :action => 'hello_world' end def conditional_hello_with_expires_in_with_public expires_in 1.minute, :public => true render :action => 'hello_world' end def conditional_hello_with_expires_in_with_must_revalidate expires_in 1.minute, :must_revalidate => true render :action => 'hello_world' end def conditional_hello_with_expires_in_with_public_and_must_revalidate expires_in 1.minute, :public => true, :must_revalidate => true render :action => 'hello_world' end def conditional_hello_with_expires_in_with_public_with_more_keys expires_in 1.minute, :public => true, 's-maxage' => 5.hours render :action => 'hello_world' end def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax expires_in 1.minute, :public => true, :private => nil, 's-maxage' => 5.hours render :action => 'hello_world' end def conditional_hello_with_expires_now expires_now render :action => 'hello_world' end def conditional_hello_with_cache_control_headers response.headers['Cache-Control'] = 'no-transform' expires_now render :action => 'hello_world' end def conditional_hello_with_bangs render :action => 'hello_world' end before_action :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs def handle_last_modified_and_etags fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ]) end def heading head :ok end # :ported: def double_render render :text => "hello" render :text => "world" end def double_redirect redirect_to :action => "double_render" redirect_to :action => "double_render" end def render_and_redirect render :text => "hello" redirect_to :action => "double_render" end def render_to_string_and_render @stuff = render_to_string :text => "here is some cached stuff" render :text => "Hi web users! #{@stuff}" end def render_to_string_with_inline_and_render render_to_string :inline => "<%= 'dlrow olleh'.reverse %>" render :template => "test/hello_world" end def rendering_with_conflicting_local_vars @name = "David" render :action => "potential_conflicts" end def hello_world_from_rxml_using_action render :action => "hello_world_from_rxml", :handlers => [:builder] end # :deprecated: def hello_world_from_rxml_using_template render :template => "test/hello_world_from_rxml", :handlers => [:builder] end def head_created head :created end def head_created_with_application_json_content_type head :created, :content_type => "application/json" end def head_ok_with_image_png_content_type head :ok, :content_type => "image/png" end def head_with_location_header head :location => "/foo" end def head_with_location_object head :location => Customer.new("david", 1) end def head_with_symbolic_status head :status => params[:status].intern end def head_with_integer_status head :status => params[:status].to_i end def head_with_string_status head :status => params[:status] end def head_with_custom_header head :x_custom_header => "something" end def head_with_www_authenticate_header head 'WWW-Authenticate' => 'something' end def head_with_status_code_first head :forbidden, :x_custom_header => "something" end def head_and_return head :ok and return raise 'should not reach this line' end def head_with_no_content # Fill in the headers with dummy data to make # sure they get removed during the testing response.headers["Content-Type"] = "dummy" response.headers["Content-Length"] = 42 head 204 end private def set_variable_for_layout @variable_for_layout = nil end def determine_layout case action_name when "hello_world", "layout_test", "rendering_without_layout", "rendering_nothing_on_layout", "render_text_hello_world", "render_text_hello_world_with_layout", "hello_world_with_layout_false", "partial_only", "accessing_params_in_template", "accessing_params_in_template_with_layout", "render_with_explicit_template", "render_with_explicit_string_template", "update_page", "update_page_with_instance_variables" "layouts/standard" when "action_talk_to_layout", "layout_overriding_layout" "layouts/talk_from_action" when "render_implicit_html_template_from_xhr_request" (request.xhr? ? 'layouts/xhr' : 'layouts/standard') end end end class MetalTestController < ActionController::Metal include AbstractController::Rendering include ActionView::Rendering include ActionController::Rendering include ActionController::RackDelegation def accessing_logger_in_template render :inline => "<%= logger.class %>" end end class MetalWithoutAVTestController < ActionController::Metal include AbstractController::Rendering include ActionController::Rendering include ActionController::StrongParameters def dynamic_params_render render params end end class ExpiresInRenderTest < ActionController::TestCase tests TestController def setup super ActionController::Base.view_paths.paths.each(&:clear_cache) end def test_dynamic_render_with_file # This is extremely bad, but should be possible to do. assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) response = get :dynamic_render_with_file, { id: '../\\../test/abstract_unit.rb' } assert_equal File.read(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')), response.body end def test_dynamic_render_with_absolute_path file = Tempfile.new('name') file.write "secrets!" file.flush assert_raises ActionView::MissingTemplate do response = get :dynamic_render, { id: file.path } end ensure file.close file.unlink end def test_dynamic_render assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) assert_raises ActionView::MissingTemplate do get :dynamic_render, { id: '../\\../test/abstract_unit.rb' } end end def test_permitted_dynamic_render_file_hash assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) response = get :dynamic_render_permit, { id: { file: '../\\../test/abstract_unit.rb' } } assert_equal File.read(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')), response.body end def test_dynamic_render_file_hash e = assert_raises ArgumentError do get :dynamic_render, { id: { file: '../\\../test/abstract_unit.rb' } } end assert_equal "render parameters are not permitted", e.message end def test_expires_in_header get :conditional_hello_with_expires_in assert_equal "max-age=60, private", @response.headers["Cache-Control"] end def test_expires_in_header_with_public get :conditional_hello_with_expires_in_with_public assert_equal "max-age=60, public", @response.headers["Cache-Control"] end def test_expires_in_header_with_must_revalidate get :conditional_hello_with_expires_in_with_must_revalidate assert_equal "max-age=60, private, must-revalidate", @response.headers["Cache-Control"] end def test_expires_in_header_with_public_and_must_revalidate get :conditional_hello_with_expires_in_with_public_and_must_revalidate assert_equal "max-age=60, public, must-revalidate", @response.headers["Cache-Control"] end def test_expires_in_header_with_additional_headers get :conditional_hello_with_expires_in_with_public_with_more_keys assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"] end def test_expires_in_old_syntax get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"] end def test_expires_now get :conditional_hello_with_expires_now assert_equal "no-cache", @response.headers["Cache-Control"] end def test_expires_now_with_cache_control_headers get :conditional_hello_with_cache_control_headers assert_match(/no-cache/, @response.headers["Cache-Control"]) assert_match(/no-transform/, @response.headers["Cache-Control"]) end def test_date_header_when_expires_in time = Time.mktime(2011,10,30) Time.stubs(:now).returns(time) get :conditional_hello_with_expires_in assert_equal Time.now.httpdate, @response.headers["Date"] end end class LastModifiedRenderTest < ActionController::TestCase tests TestController def setup super @last_modified = Time.now.utc.beginning_of_day.httpdate end def test_responds_with_last_modified get :conditional_hello assert_equal @last_modified, @response.headers['Last-Modified'] end def test_request_not_modified @request.if_modified_since = @last_modified get :conditional_hello assert_equal 304, @response.status.to_i assert @response.body.blank? assert_equal @last_modified, @response.headers['Last-Modified'] end def test_request_not_modified_but_etag_differs @request.if_modified_since = @last_modified @request.if_none_match = "234" get :conditional_hello assert_response :success end def test_request_modified @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' get :conditional_hello assert_equal 200, @response.status.to_i assert @response.body.present? assert_equal @last_modified, @response.headers['Last-Modified'] end def test_responds_with_last_modified_with_record get :conditional_hello_with_record assert_equal @last_modified, @response.headers['Last-Modified'] end def test_request_not_modified_with_record @request.if_modified_since = @last_modified get :conditional_hello_with_record assert_equal 304, @response.status.to_i assert @response.body.blank? assert_equal @last_modified, @response.headers['Last-Modified'] end def test_request_not_modified_but_etag_differs_with_record @request.if_modified_since = @last_modified @request.if_none_match = "234" get :conditional_hello_with_record assert_response :success end def test_request_modified_with_record @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' get :conditional_hello_with_record assert_equal 200, @response.status.to_i assert @response.body.present? assert_equal @last_modified, @response.headers['Last-Modified'] end def test_request_with_bang_gets_last_modified get :conditional_hello_with_bangs assert_equal @last_modified, @response.headers['Last-Modified'] assert_response :success end def test_request_with_bang_obeys_last_modified @request.if_modified_since = @last_modified get :conditional_hello_with_bangs assert_response :not_modified end def test_last_modified_works_with_less_than_too @request.if_modified_since = 5.years.ago.httpdate get :conditional_hello_with_bangs assert_response :success end end class EtagRenderTest < ActionController::TestCase tests TestControllerWithExtraEtags def test_multiple_etags @request.if_none_match = etag(["123", 'ab', :cde, [:f]]) get :fresh assert_response :not_modified @request.if_none_match = %("nomatch") get :fresh assert_response :success end def test_array @request.if_none_match = etag([%w(1 2 3), 'ab', :cde, [:f]]) get :array assert_response :not_modified @request.if_none_match = %("nomatch") get :array assert_response :success end def test_etag_reflects_template_digest get :with_template assert_response :ok assert_not_nil etag = @response.etag request.if_none_match = etag get :with_template assert_response :not_modified # Modify the template digest path = File.expand_path('../../fixtures/test/hello_world.erb', __FILE__) old = File.read(path) begin File.write path, 'foo' ActionView::Digestor.cache.clear request.if_none_match = etag get :with_template assert_response :ok assert_not_equal etag, @response.etag ensure File.write path, old end end def etag(record) Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record)).inspect end end class MetalRenderTest < ActionController::TestCase tests MetalTestController def test_access_to_logger_in_view get :accessing_logger_in_template assert_equal "NilClass", @response.body end end class MetalRenderWithoutAVTest < ActionController::TestCase tests MetalWithoutAVTestController def test_dynamic_params_render e = assert_raises ArgumentError do get :dynamic_params_render, { inline: '<%= RUBY_VERSION %>' } end assert_equal "render parameters are not permitted", e.message end end class HeadRenderTest < ActionController::TestCase tests TestController def setup @request.host = "www.nextangle.com" end def test_head_created post :head_created assert @response.body.blank? assert_response :created end def test_head_created_with_application_json_content_type post :head_created_with_application_json_content_type assert @response.body.blank? assert_equal "application/json", @response.header["Content-Type"] assert_response :created end def test_head_ok_with_image_png_content_type post :head_ok_with_image_png_content_type assert @response.body.blank? assert_equal "image/png", @response.header["Content-Type"] assert_response :ok end def test_head_with_location_header get :head_with_location_header assert @response.body.blank? assert_equal "/foo", @response.headers["Location"] assert_response :ok end def test_head_with_location_object with_routing do |set| set.draw do resources :customers get ':controller/:action' end get :head_with_location_object assert @response.body.blank? assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"] assert_response :ok end end def test_head_with_custom_header get :head_with_custom_header assert @response.body.blank? assert_equal "something", @response.headers["X-Custom-Header"] assert_response :ok end def test_head_with_www_authenticate_header get :head_with_www_authenticate_header assert @response.body.blank? assert_equal "something", @response.headers["WWW-Authenticate"] assert_response :ok end def test_head_with_symbolic_status get :head_with_symbolic_status, :status => "ok" assert_equal 200, @response.status assert_response :ok get :head_with_symbolic_status, :status => "not_found" assert_equal 404, @response.status assert_response :not_found get :head_with_symbolic_status, :status => "no_content" assert_equal 204, @response.status assert !@response.headers.include?('Content-Length') assert_response :no_content Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code| get :head_with_symbolic_status, :status => status.to_s assert_equal code, @response.response_code assert_response status end end def test_head_with_integer_status Rack::Utils::HTTP_STATUS_CODES.each do |code, message| get :head_with_integer_status, :status => code.to_s assert_equal message, @response.message end end def test_head_with_no_content get :head_with_no_content assert_equal 204, @response.status assert_nil @response.headers["Content-Type"] assert_nil @response.headers["Content-Length"] end def test_head_with_string_status get :head_with_string_status, :status => "404 Eat Dirt" assert_equal 404, @response.response_code assert_equal "Not Found", @response.message assert_response :not_found end def test_head_with_status_code_first get :head_with_status_code_first assert_equal 403, @response.response_code assert_equal "Forbidden", @response.message assert_equal "something", @response.headers["X-Custom-Header"] assert_response :forbidden end def test_head_returns_truthy_value assert_nothing_raised do get :head_and_return end end end rails-4.2.6/actionpack/test/controller/render_xml_test.rb000066400000000000000000000050731266740050600236510ustar00rootroot00000000000000require 'abstract_unit' require 'controller/fake_models' require 'pathname' class RenderXmlTest < ActionController::TestCase class XmlRenderable def to_xml(options) options[:root] ||= "i-am-xml" "<#{options[:root]}/>" end end class TestController < ActionController::Base protect_from_forgery def self.controller_path 'test' end def render_with_location render :xml => "", :location => "http://example.com", :status => 201 end def render_with_object_location customer = Customer.new("Some guy", 1) render :xml => "", :location => customer, :status => :created end def render_with_to_xml render :xml => XmlRenderable.new end def formatted_xml_erb end def render_xml_with_custom_content_type render :xml => "", :content_type => "application/atomsvc+xml" end def render_xml_with_custom_options render :xml => XmlRenderable.new, :root => "i-am-THE-xml" end end tests TestController def setup # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get # a more accurate simulation of what happens in "real life". super @controller.logger = ActiveSupport::Logger.new(nil) @request.host = "www.nextangle.com" end def test_rendering_with_location_should_set_header get :render_with_location assert_equal "http://example.com", @response.headers["Location"] end def test_rendering_xml_should_call_to_xml_if_possible get :render_with_to_xml assert_equal "", @response.body end def test_rendering_xml_should_call_to_xml_with_extra_options get :render_xml_with_custom_options assert_equal "", @response.body end def test_rendering_with_object_location_should_set_header_with_url_for with_routing do |set| set.draw do resources :customers get ':controller/:action' end get :render_with_object_location assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"] end end def test_should_render_formatted_xml_erb_template get :formatted_xml_erb, :format => :xml assert_equal 'passed formatted xml erb', @response.body end def test_should_render_xml_but_keep_custom_content_type get :render_xml_with_custom_content_type assert_equal "application/atomsvc+xml", @response.content_type end def test_should_use_implicit_content_type get :implicit_content_type, :format => 'atom' assert_equal Mime::ATOM, @response.content_type end end rails-4.2.6/actionpack/test/controller/request/000077500000000000000000000000001266740050600216115ustar00rootroot00000000000000rails-4.2.6/actionpack/test/controller/request/test_request_test.rb000066400000000000000000000022461266740050600257300ustar00rootroot00000000000000require 'abstract_unit' require 'stringio' class ActionController::TestRequestTest < ActiveSupport::TestCase def setup @request = ActionController::TestRequest.new end def test_test_request_has_session_options_initialized assert @request.session_options end ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS.each_key do |option| test "rack default session options #{option} exists in session options and is default" do assert_equal(ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS[option], @request.session_options[option], "Missing rack session default option #{option} in request.session_options") end test "rack default session options #{option} exists in session options" do assert(@request.session_options.has_key?(option), "Missing rack session option #{option} in request.session_options") end end def test_session_id_exists_by_default assert_not_nil(@request.session_options[:id]) end def test_session_id_different_on_each_call assert_not_equal(@request.session_options[:id], ActionController::TestRequest.new.session_options[:id]) end end rails-4.2.6/actionpack/test/controller/request_forgery_protection_test.rb000066400000000000000000000362261266740050600272110ustar00rootroot00000000000000require 'abstract_unit' require 'digest/sha1' require "active_support/log_subscriber/test_helper" # common controller actions module RequestForgeryProtectionActions def index render :inline => "<%= form_tag('/') {} %>" end def show_button render :inline => "<%= button_to('New', '/') %>" end def external_form render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => 'external_token') {} %>" end def external_form_without_protection render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => false) {} %>" end def unsafe render :text => 'pwn' end def meta render :inline => "<%= csrf_meta_tags %>" end def external_form_for render :inline => "<%= form_for(:some_resource, :authenticity_token => 'external_token') {} %>" end def form_for_without_protection render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>" end def form_for_remote render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>" end def form_for_remote_with_token render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>" end def form_for_with_token render :inline => "<%= form_for(:some_resource, :authenticity_token => true ) {} %>" end def form_for_remote_with_external_token render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>" end def same_origin_js render js: 'foo();' end def negotiate_same_origin respond_to do |format| format.js { same_origin_js } end end def cross_origin_js same_origin_js end def negotiate_cross_origin negotiate_same_origin end def rescue_action(e) raise e end end # sample controllers class RequestForgeryProtectionControllerUsingResetSession < ActionController::Base include RequestForgeryProtectionActions protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :reset_session end class RequestForgeryProtectionControllerUsingException < ActionController::Base include RequestForgeryProtectionActions protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :exception end class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base protect_from_forgery :with => :null_session def signed cookies.signed[:foo] = 'bar' render :nothing => true end def encrypted cookies.encrypted[:foo] = 'bar' render :nothing => true end def try_to_reset_session reset_session render :nothing => true end end class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession self.allow_forgery_protection = false def index render :inline => "<%= form_tag('/') {} %>" end def show_button render :inline => "<%= button_to('New', '/') %>" end end class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsingResetSession def form_authenticity_param 'foobar' end end # common test methods module RequestForgeryProtectionTests def setup @token = "cf50faa3fe97702ca1ae" @controller.stubs(:form_authenticity_token).returns(@token) @controller.stubs(:valid_authenticity_token?).with{ |_, t| t == @token }.returns(true) @controller.stubs(:valid_authenticity_token?).with{ |_, t| t != @token }.returns(false) @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token ActionController::Base.request_forgery_protection_token = :custom_authenticity_token end def teardown ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token end def test_should_render_form_with_token_tag assert_not_blocked do get :index end assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_button_to_with_token_tag assert_not_blocked do get :show_button end assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_without_token_tag_if_remote assert_not_blocked do get :form_for_remote end assert_no_match(/authenticity_token/, response.body) end def test_should_render_form_with_token_tag_if_remote_and_embedding_token_is_on original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms begin ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true assert_not_blocked do get :form_for_remote end assert_match(/authenticity_token/, response.body) ensure ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original end end def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested_and_embedding_is_on original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms begin ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true assert_not_blocked do get :form_for_remote_with_external_token end assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' ensure ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original end end def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested assert_not_blocked do get :form_for_remote_with_external_token end assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' end def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested assert_not_blocked do get :form_for_remote_with_token end assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_with_token_tag_with_authenticity_token_requested assert_not_blocked do get :form_for_with_token end assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_allow_get assert_not_blocked { get :index } end def test_should_allow_head assert_not_blocked { head :index } end def test_should_allow_post_without_token_on_unsafe_action assert_not_blocked { post :unsafe } end def test_should_not_allow_post_without_token assert_blocked { post :index } end def test_should_not_allow_post_without_token_irrespective_of_format assert_blocked { post :index, format: 'xml' } end def test_should_not_allow_patch_without_token assert_blocked { patch :index } end def test_should_not_allow_put_without_token assert_blocked { put :index } end def test_should_not_allow_delete_without_token assert_blocked { delete :index } end def test_should_not_allow_xhr_post_without_token assert_blocked { xhr :post, :index } end def test_should_allow_post_with_token assert_not_blocked { post :index, :custom_authenticity_token => @token } end def test_should_allow_patch_with_token assert_not_blocked { patch :index, :custom_authenticity_token => @token } end def test_should_allow_put_with_token assert_not_blocked { put :index, :custom_authenticity_token => @token } end def test_should_allow_delete_with_token assert_not_blocked { delete :index, :custom_authenticity_token => @token } end def test_should_allow_post_with_token_in_header @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { post :index } end def test_should_allow_delete_with_token_in_header @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { delete :index } end def test_should_allow_patch_with_token_in_header @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { patch :index } end def test_should_allow_put_with_token_in_header @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { put :index } end def test_should_warn_on_missing_csrf_token old_logger = ActionController::Base.logger logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new ActionController::Base.logger = logger begin assert_blocked { post :index } assert_equal 1, logger.logged(:warn).size assert_match(/CSRF token authenticity/, logger.logged(:warn).last) ensure ActionController::Base.logger = old_logger end end def test_should_not_warn_if_csrf_logging_disabled old_logger = ActionController::Base.logger logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new ActionController::Base.logger = logger ActionController::Base.log_warning_on_csrf_failure = false begin assert_blocked { post :index } assert_equal 0, logger.logged(:warn).size ensure ActionController::Base.logger = old_logger ActionController::Base.log_warning_on_csrf_failure = true end end def test_should_only_allow_same_origin_js_get_with_xhr_header assert_cross_origin_blocked { get :same_origin_js } assert_cross_origin_blocked { get :same_origin_js, format: 'js' } assert_cross_origin_blocked do @request.accept = 'text/javascript' get :negotiate_same_origin end assert_cross_origin_not_blocked { xhr :get, :same_origin_js } assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js' } assert_cross_origin_not_blocked do @request.accept = 'text/javascript' xhr :get, :negotiate_same_origin end end # Allow non-GET requests since GET is all a remote ) # Specify whether rendering within namespaced controllers should prefix # the partial paths for ActiveModel objects with the namespace. # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb) cattr_accessor :prefix_partial_path_with_controller_namespace @@prefix_partial_path_with_controller_namespace = true # Specify default_formats that can be rendered. cattr_accessor :default_formats # Specify whether an error should be raised for missing translations cattr_accessor :raise_on_missing_translations @@raise_on_missing_translations = false class_attribute :_routes class_attribute :logger class << self delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' def cache_template_loading ActionView::Resolver.caching? end def cache_template_loading=(value) ActionView::Resolver.caching = value end def xss_safe? #:nodoc: true end end attr_accessor :view_renderer attr_internal :config, :assigns delegate :lookup_context, :to => :view_renderer delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context def assign(new_assigns) # :nodoc: @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } end def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc: @_config = ActiveSupport::InheritableOptions.new if context.is_a?(ActionView::Renderer) @view_renderer = context else lookup_context = context.is_a?(ActionView::LookupContext) ? context : ActionView::LookupContext.new(context) lookup_context.formats = formats if formats lookup_context.prefixes = controller._prefixes if controller @view_renderer = ActionView::Renderer.new(lookup_context) end assign(assigns) assign_controller(controller) _prepare_context end ActiveSupport.run_load_hooks(:action_view, self) end end rails-4.2.6/actionview/lib/action_view/buffers.rb000066400000000000000000000015761266740050600220620ustar00rootroot00000000000000require 'active_support/core_ext/string/output_safety' module ActionView class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: def initialize(*) super encode! end def <<(value) return self if value.nil? super(value.to_s) end alias :append= :<< def safe_expr_append=(val) return self if val.nil? safe_concat val.to_s end alias :safe_append= :safe_concat end class StreamingBuffer #:nodoc: def initialize(block) @block = block end def <<(value) value = value.to_s value = ERB::Util.h(value) unless value.html_safe? @block.call(value) end alias :concat :<< alias :append= :<< def safe_concat(value) @block.call(value.to_s) end alias :safe_append= :safe_concat def html_safe? true end def html_safe self end end end rails-4.2.6/actionview/lib/action_view/context.rb000066400000000000000000000022511266740050600221010ustar00rootroot00000000000000module ActionView module CompiledTemplates #:nodoc: # holds compiled template code end # = Action View Context # # Action View contexts are supplied to Action Controller to render a template. # The default Action View context is ActionView::Base. # # In order to work with ActionController, a Context must just include this module. # The initialization of the variables used by the context (@output_buffer, @view_flow, # and @virtual_path) is responsibility of the object that includes this module # (although you can call _prepare_context defined below). module Context include CompiledTemplates attr_accessor :output_buffer, :view_flow # Prepares the context by setting the appropriate instance variables. # :api: plugin def _prepare_context @view_flow = OutputFlow.new @output_buffer = nil @virtual_path = nil end # Encapsulates the interaction with the view flow so it # returns the correct buffer on +yield+. This is usually # overwritten by helpers to add more behavior. # :api: plugin def _layout_for(name=nil) name ||= :layout view_flow.get(name).html_safe end end end rails-4.2.6/actionview/lib/action_view/dependency_tracker.rb000066400000000000000000000105631266740050600242530ustar00rootroot00000000000000require 'thread_safe' module ActionView class DependencyTracker # :nodoc: @trackers = ThreadSafe::Cache.new def self.find_dependencies(name, template) tracker = @trackers[template.handler] if tracker.present? tracker.call(name, template) else [] end end def self.register_tracker(extension, tracker) handler = Template.handler_for_extension(extension) @trackers[handler] = tracker end def self.remove_tracker(handler) @trackers.delete(handler) end class ERBTracker # :nodoc: EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ # A valid ruby identifier - suitable for class, method and specially variable names IDENTIFIER = / [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore [[:word:]]* # followed by optional letters, numbers or underscores /x # Any kind of variable name. e.g. @instance, @@class, $global or local. # Possibly following a method call chain VARIABLE_OR_METHOD_CHAIN = / (?:\$|@{1,2})? # optional global, instance or class variable indicator (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls (?#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC /x # A simple string literal. e.g. "School's out!" STRING = / (?['"]) # an opening quote (?.*?) # with anything inside, captured as STATIC \k # and a matching closing quote /x # Part of any hash containing the :partial key PARTIAL_HASH_KEY = / (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax \s* # followed by optional spaces /x # Part of any hash containing the :layout key LAYOUT_HASH_KEY = / (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax \s* # followed by optional spaces /x # Matches: # partial: "comments/comment", collection: @all_comments => "comments/comment" # (object: @single_comment, partial: "comments/comment") => "comments/comment" # # "comments/comments" # 'comments/comments' # ('comments/comments') # # (@topic) => "topics/topic" # topics => "topics/topic" # (message.topics) => "topics/topic" RENDER_ARGUMENTS = /\A (?:\s*\(?\s*) # optional opening paren surrounded by spaces (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest /xm def self.call(name, template) new(name, template).dependencies end def initialize(name, template) @name, @template = name, template end def dependencies render_dependencies + explicit_dependencies end attr_reader :name, :template private :name, :template private def source template.source end def directory name.split("/")[0..-2].join("/") end def render_dependencies render_dependencies = [] render_calls = source.split(/\brender\b/).drop(1) render_calls.each do |arguments| arguments.scan(RENDER_ARGUMENTS) do add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic]) add_static_dependency(render_dependencies, Regexp.last_match[:static]) end end render_dependencies.uniq end def add_dynamic_dependency(dependencies, dependency) if dependency dependencies << "#{dependency.pluralize}/#{dependency.singularize}" end end def add_static_dependency(dependencies, dependency) if dependency if dependency.include?('/') dependencies << dependency else dependencies << "#{directory}/#{dependency}" end end end def explicit_dependencies source.scan(EXPLICIT_DEPENDENCY).flatten.uniq end end register_tracker :erb, ERBTracker end end rails-4.2.6/actionview/lib/action_view/digestor.rb000066400000000000000000000075651266740050600222520ustar00rootroot00000000000000require 'thread_safe' require 'action_view/dependency_tracker' require 'monitor' module ActionView class Digestor cattr_reader(:cache) @@cache = ThreadSafe::Cache.new @@digest_monitor = Monitor.new class << self # Supported options: # # * name - Template name # * finder - An instance of ActionView::LookupContext # * dependencies - An array of dependent views # * partial - Specifies whether the template is a partial def digest(options) options.assert_valid_keys(:name, :finder, :dependencies, :partial) cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.') # this is a correctly done double-checked locking idiom # (ThreadSafe::Cache's lookups have volatile semantics) @@cache[cache_key] || @@digest_monitor.synchronize do @@cache.fetch(cache_key) do # re-check under lock compute_and_store_digest(cache_key, options) end end end private def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock klass = if options[:partial] || options[:name].include?("/_") # Prevent re-entry or else recursive templates will blow the stack. # There is no need to worry about other threads seeing the +false+ value, # as they will then have to wait for this thread to let go of the @@digest_monitor lock. pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion PartialDigestor else Digestor end digest = klass.new(options).digest # Store the actual digest if config.cache_template_loading is true @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching? digest ensure # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest end end attr_reader :name, :finder, :options def initialize(options) @name, @finder = options.values_at(:name, :finder) @options = options.except(:name, :finder) end def digest Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest| logger.try :debug, " Cache digest for #{template.inspect}: #{digest}" end rescue ActionView::MissingTemplate logger.try :error, " Couldn't find template for digesting: #{name}" '' end def dependencies DependencyTracker.find_dependencies(name, template) rescue ActionView::MissingTemplate logger.try :error, " '#{name}' file doesn't exist, so no dependencies" [] end def nested_dependencies dependencies.collect do |dependency| dependencies = PartialDigestor.new(name: dependency, finder: finder).nested_dependencies dependencies.any? ? { dependency => dependencies } : dependency end end private def logger ActionView::Base.logger end def logical_name name.gsub(%r|/_|, "/") end def partial? false end def template @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) } end def source template.source end def dependency_digest template_digests = dependencies.collect do |template_name| Digestor.digest(name: template_name, finder: finder, partial: true) end (template_digests + injected_dependencies).join("-") end def injected_dependencies Array.wrap(options[:dependencies]) end end class PartialDigestor < Digestor # :nodoc: def partial? true end end end rails-4.2.6/actionview/lib/action_view/flows.rb000066400000000000000000000033731266740050600215550ustar00rootroot00000000000000require 'active_support/core_ext/string/output_safety' module ActionView class OutputFlow #:nodoc: attr_reader :content def initialize @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } end # Called by _layout_for to read stored values. def get(key) @content[key] end # Called by each renderer object to set the layout contents. def set(key, value) @content[key] = ActiveSupport::SafeBuffer.new(value) end # Called by content_for def append(key, value) @content[key] << value end alias_method :append!, :append end class StreamingFlow < OutputFlow #:nodoc: def initialize(view, fiber) @view = view @parent = nil @child = view.output_buffer @content = view.view_flow.content @fiber = fiber @root = Fiber.current.object_id end # Try to get stored content. If the content # is not available and we are inside the layout # fiber, we set that we are waiting for the given # key and yield. def get(key) return super if @content.key?(key) if inside_fiber? view = @view begin @waiting_for = key view.output_buffer, @parent = @child, view.output_buffer Fiber.yield ensure @waiting_for = nil view.output_buffer, @child = @parent, view.output_buffer end end super end # Appends the contents for the given key. This is called # by provides and resumes back to the fiber if it is # the key it is waiting for. def append!(key, value) super @fiber.resume if @waiting_for == key end private def inside_fiber? Fiber.current.object_id != @root end end end rails-4.2.6/actionview/lib/action_view/gem_version.rb000066400000000000000000000004731266740050600227360ustar00rootroot00000000000000module ActionView # Returns the version of the currently loaded Action View as a Gem::Version def self.gem_version Gem::Version.new VERSION::STRING end module VERSION MAJOR = 4 MINOR = 2 TINY = 6 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end end rails-4.2.6/actionview/lib/action_view/helpers.rb000066400000000000000000000030771266740050600220660ustar00rootroot00000000000000require 'active_support/benchmarkable' module ActionView #:nodoc: module Helpers #:nodoc: extend ActiveSupport::Autoload autoload :ActiveModelHelper autoload :AssetTagHelper autoload :AssetUrlHelper autoload :AtomFeedHelper autoload :CacheHelper autoload :CaptureHelper autoload :ControllerHelper autoload :CsrfHelper autoload :DateHelper autoload :DebugHelper autoload :FormHelper autoload :FormOptionsHelper autoload :FormTagHelper autoload :JavaScriptHelper, "action_view/helpers/javascript_helper" autoload :NumberHelper autoload :OutputSafetyHelper autoload :RecordTagHelper autoload :RenderingHelper autoload :SanitizeHelper autoload :TagHelper autoload :TextHelper autoload :TranslationHelper autoload :UrlHelper autoload :Tags def self.eager_load! super Tags.eager_load! end extend ActiveSupport::Concern include ActiveSupport::Benchmarkable include ActiveModelHelper include AssetTagHelper include AssetUrlHelper include AtomFeedHelper include CacheHelper include CaptureHelper include ControllerHelper include CsrfHelper include DateHelper include DebugHelper include FormHelper include FormOptionsHelper include FormTagHelper include JavaScriptHelper include NumberHelper include OutputSafetyHelper include RecordTagHelper include RenderingHelper include SanitizeHelper include TagHelper include TextHelper include TranslationHelper include UrlHelper end end rails-4.2.6/actionview/lib/action_view/helpers/000077500000000000000000000000001266740050600215325ustar00rootroot00000000000000rails-4.2.6/actionview/lib/action_view/helpers/active_model_helper.rb000066400000000000000000000021021266740050600260440ustar00rootroot00000000000000require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/enumerable' module ActionView # = Active Model Helpers module Helpers module ActiveModelHelper end module ActiveModelInstanceTag def object @active_model_object ||= begin object = super object.respond_to?(:to_model) ? object.to_model : object end end def content_tag(*) error_wrapping(super) end def tag(type, options, *) tag_generate_errors?(options) ? error_wrapping(super) : super end def error_wrapping(html_tag) if object_has_errors? Base.field_error_proc.call(html_tag, self) else html_tag end end def error_message object.errors[@method_name] end private def object_has_errors? object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present? end def tag_generate_errors?(options) options['type'] != 'hidden' end end end end rails-4.2.6/actionview/lib/action_view/helpers/asset_tag_helper.rb000066400000000000000000000367421266740050600254040ustar00rootroot00000000000000require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/hash/keys' require 'action_view/helpers/asset_url_helper' require 'action_view/helpers/tag_helper' module ActionView # = Action View Asset Tag Helpers module Helpers #:nodoc: # This module provides methods for generating HTML that links views to assets such # as images, JavaScripts, stylesheets, and feeds. These methods do not verify # the assets exist before linking to them: # # image_tag("rails.png") # # => Rails # stylesheet_link_tag("application") # # => module AssetTagHelper extend ActiveSupport::Concern include AssetUrlHelper include TagHelper # Returns an HTML script tag for each of the +sources+ provided. # # Sources may be paths to JavaScript files. Relative paths are assumed to be relative # to assets/javascripts, full paths are assumed to be relative to the document # root. Relative paths are idiomatic, use absolute paths only when needed. # # When passing paths, the ".js" extension is optional. If you do not want ".js" # appended to the path extname: false can be set on the options. # # You can modify the HTML attributes of the script tag by passing a hash as the # last argument. # # When the Asset Pipeline is enabled, you can pass the name of your manifest as # source, and include other JavaScript or CoffeeScript files inside the manifest. # # javascript_include_tag "xmlhr" # # => # # javascript_include_tag "template.jst", extname: false # # => # # javascript_include_tag "xmlhr.js" # # => # # javascript_include_tag "common.javascript", "/elsewhere/cools" # # => # # # # javascript_include_tag "http://www.example.com/xmlhr" # # => # # javascript_include_tag "http://www.example.com/xmlhr.js" # # => def javascript_include_tag(*sources) options = sources.extract_options!.stringify_keys path_options = options.extract!('protocol', 'extname').symbolize_keys sources.uniq.map { |source| tag_options = { "src" => path_to_javascript(source, path_options) }.merge!(options) content_tag(:script, "", tag_options) }.join("\n").html_safe end # Returns a stylesheet link tag for the sources specified as arguments. If # you don't specify an extension, .css will be appended automatically. # You can modify the link attributes by passing a hash as the last argument. # For historical reasons, the 'media' attribute will always be present and defaults # to "screen", so you must explicitly set it to "all" for the stylesheet(s) to # apply to all media types. # # stylesheet_link_tag "style" # # => # # stylesheet_link_tag "style.css" # # => # # stylesheet_link_tag "http://www.example.com/style.css" # # => # # stylesheet_link_tag "style", media: "all" # # => # # stylesheet_link_tag "style", media: "print" # # => # # stylesheet_link_tag "random.styles", "/css/stylish" # # => # # def stylesheet_link_tag(*sources) options = sources.extract_options!.stringify_keys path_options = options.extract!('protocol').symbolize_keys sources.uniq.map { |source| tag_options = { "rel" => "stylesheet", "media" => "screen", "href" => path_to_stylesheet(source, path_options) }.merge!(options) tag(:link, tag_options) }.join("\n").html_safe end # Returns a link tag that browsers and feed readers can use to auto-detect # an RSS or Atom feed. The +type+ can either be :rss (default) or # :atom. Control the link options in url_for format using the # +url_options+. You can modify the LINK tag itself in +tag_options+. # # ==== Options # # * :rel - Specify the relation of this link, defaults to "alternate" # * :type - Override the auto-generated mime type # * :title - Specify the title of the link, defaults to the +type+ # # ==== Examples # # auto_discovery_link_tag # # => # auto_discovery_link_tag(:atom) # # => # auto_discovery_link_tag(:rss, {action: "feed"}) # # => # auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"}) # # => # auto_discovery_link_tag(:rss, {controller: "news", action: "feed"}) # # => # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"}) # # => def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) if !(type == :rss || type == :atom) && tag_options[:type].blank? raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss or :atom.") end tag( "link", "rel" => tag_options[:rel] || "alternate", "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s, "title" => tag_options[:title] || type.to_s.upcase, "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options ) end # Returns a link tag for a favicon managed by the asset pipeline. # # If a page has no link like the one generated by this helper, browsers # ask for /favicon.ico automatically, and cache the file if the # request succeeds. If the favicon changes it is hard to get it updated. # # To have better control applications may let the asset pipeline manage # their favicon storing the file under app/assets/images, and # using this helper to generate its corresponding link tag. # # The helper gets the name of the favicon file as first argument, which # defaults to "favicon.ico", and also supports +:rel+ and +:type+ options # to override their defaults, "shortcut icon" and "image/x-icon" # respectively: # # favicon_link_tag # # => # # favicon_link_tag 'myicon.ico' # # => # # Mobile Safari looks for a different link tag, pointing to an image that # will be used if you add the page to the home screen of an iOS device. # The following call would generate such a tag: # # favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' # # => def favicon_link_tag(source='favicon.ico', options={}) tag('link', { :rel => 'shortcut icon', :type => 'image/x-icon', :href => path_to_image(source) }.merge!(options.symbolize_keys)) end # Returns an HTML image tag for the +source+. The +source+ can be a full # path or a file. # # ==== Options # # You can add HTML attributes using the +options+. The +options+ supports # two additional keys for convenience and conformance: # # * :alt - If no alt text is given, the file name part of the # +source+ is used (capitalized and without the extension) # * :size - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes # width="30" and height="45", and "50" becomes width="50" and height="50". # :size will be ignored if the value is not in the correct format. # # ==== Examples # # image_tag("icon") # # => Icon # image_tag("icon.png") # # => Icon # image_tag("icon.png", size: "16x10", alt: "Edit Entry") # # => Edit Entry # image_tag("/icons/icon.gif", size: "16") # # => Icon # image_tag("/icons/icon.gif", height: '32', width: '32') # # => Icon # image_tag("/icons/icon.gif", class: "menu_icon") # # => Icon def image_tag(source, options={}) options = options.symbolize_keys src = options[:src] = path_to_image(source) unless src =~ /^(?:cid|data):/ || src.blank? options[:alt] = options.fetch(:alt){ image_alt(src) } end options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] tag("img", options) end # Returns a string suitable for an HTML image tag alt attribute. # The +src+ argument is meant to be an image file path. # The method removes the basename of the file path and the digest, # if any. It also removes hyphens and underscores from file names and # replaces them with spaces, returning a space-separated, titleized # string. # # ==== Examples # # image_alt('rails.png') # # => Rails # # image_alt('hyphenated-file-name.png') # # => Hyphenated file name # # image_alt('underscored_file_name.png') # # => Underscored file name def image_alt(src) File.basename(src, '.*').sub(/-[[:xdigit:]]{32,64}\z/, '').tr('-_', ' ').capitalize end # Returns an HTML video tag for the +sources+. If +sources+ is a string, # a single video tag will be returned. If +sources+ is an array, a video # tag with nested source tags for each source will be returned. The # +sources+ can be full paths or files that exists in your public videos # directory. # # ==== Options # You can add HTML attributes using the +options+. The +options+ supports # two additional keys for convenience and conformance: # # * :poster - Set an image (like a screenshot) to be shown # before the video loads. The path is calculated like the +src+ of +image_tag+. # * :size - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes # width="30" and height="45", and "50" becomes width="50" and height="50". # :size will be ignored if the value is not in the correct format. # # ==== Examples # # video_tag("trailer") # # => # video_tag("trailer.ogg") # # => # video_tag("trailer.ogg", controls: true, autobuffer: true) # # => # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png") # # => # video_tag("/trailers/hd.avi", size: "16x16") # # => # video_tag("/trailers/hd.avi", size: "16") # # => # video_tag("/trailers/hd.avi", height: '32', width: '32') # # => # video_tag("trailer.ogg", "trailer.flv") # # => # video_tag(["trailer.ogg", "trailer.flv"]) # # => # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120") # # => def video_tag(*sources) multiple_sources_tag('video', sources) do |options| options[:poster] = path_to_image(options[:poster]) if options[:poster] options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] end end # Returns an HTML audio tag for the +source+. # The +source+ can be full path or file that exists in # your public audios directory. # # audio_tag("sound") # # => # audio_tag("sound.wav") # # => # audio_tag("sound.wav", autoplay: true, controls: true) # # => # audio_tag("sound.wav", "sound.mid") # # => def audio_tag(*sources) multiple_sources_tag('audio', sources) end private def multiple_sources_tag(type, sources) options = sources.extract_options!.symbolize_keys sources.flatten! yield options if block_given? if sources.size > 1 content_tag(type, options) do safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) } end else options[:src] = send("path_to_#{type}", sources.first) content_tag(type, nil, options) end end def extract_dimensions(size) if size =~ %r{\A\d+x\d+\z} size.split('x') elsif size =~ %r{\A\d+\z} [size, size] end end end end end rails-4.2.6/actionview/lib/action_view/helpers/asset_url_helper.rb000066400000000000000000000440221266740050600254210ustar00rootroot00000000000000require 'zlib' module ActionView # = Action View Asset URL Helpers module Helpers # This module provides methods for generating asset paths and # urls. # # image_path("rails.png") # # => "/assets/rails.png" # # image_url("rails.png") # # => "http://www.example.com/assets/rails.png" # # === Using asset hosts # # By default, Rails links to these assets on the current host in the public # folder, but you can direct Rails to link to assets from a dedicated asset # server by setting ActionController::Base.asset_host in the application # configuration, typically in config/environments/production.rb. # For example, you'd define assets.example.com to be your asset # host this way, inside the configure block of your environment-specific # configuration files or config/application.rb: # # config.action_controller.asset_host = "assets.example.com" # # Helpers take that into account: # # image_tag("rails.png") # # => Rails # stylesheet_link_tag("application") # # => # # Browsers typically open at most two simultaneous connections to a single # host, which means your assets often have to wait for other assets to finish # downloading. You can alleviate this by using a %d wildcard in the # +asset_host+. For example, "assets%d.example.com". If that wildcard is # present Rails distributes asset requests among the corresponding four hosts # "assets0.example.com", ..., "assets3.example.com". With this trick browsers # will open eight simultaneous connections rather than two. # # image_tag("rails.png") # # => Rails # stylesheet_link_tag("application") # # => # # To do this, you can either setup four actual hosts, or you can use wildcard # DNS to CNAME the wildcard to a single asset host. You can read more about # setting up your DNS CNAME records from your ISP. # # Note: This is purely a browser performance optimization and is not meant # for server load balancing. See http://www.die.net/musings/page_load_time/ # for background. # # Alternatively, you can exert more control over the asset host by setting # +asset_host+ to a proc like this: # # ActionController::Base.asset_host = Proc.new { |source| # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com" # } # image_tag("rails.png") # # => Rails # stylesheet_link_tag("application") # # => # # The example above generates "http://assets1.example.com" and # "http://assets2.example.com". This option is useful for example if # you need fewer/more than four hosts, custom host names, etc. # # As you see the proc takes a +source+ parameter. That's a string with the # absolute path of the asset, for example "/assets/rails.png". # # ActionController::Base.asset_host = Proc.new { |source| # if source.ends_with?('.css') # "http://stylesheets.example.com" # else # "http://assets.example.com" # end # } # image_tag("rails.png") # # => Rails # stylesheet_link_tag("application") # # => # # Alternatively you may ask for a second parameter +request+. That one is # particularly useful for serving assets from an SSL-protected page. The # example proc below disables asset hosting for HTTPS connections, while # still sending assets for plain HTTP requests from asset hosts. If you don't # have SSL certificates for each of the asset hosts this technique allows you # to avoid warnings in the client about mixed media. # Note that the request parameter might not be supplied, e.g. when the assets # are precompiled via a Rake task. Make sure to use a Proc instead of a lambda, # since a Proc allows missing parameters and sets them to nil. # # config.action_controller.asset_host = Proc.new { |source, request| # if request && request.ssl? # "#{request.protocol}#{request.host_with_port}" # else # "#{request.protocol}assets.example.com" # end # } # # You can also implement a custom asset host object that responds to +call+ # and takes either one or two parameters just like the proc. # # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new( # "http://asset%d.example.com", "https://asset1.example.com" # ) # module AssetUrlHelper URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i # Computes the path to asset in public directory. If :type # options is set, a file extension will be appended and scoped # to the corresponding public directory. # # All other asset *_path helpers delegate through this method. # # asset_path "application.js" # => /assets/application.js # asset_path "application", type: :javascript # => /assets/application.js # asset_path "application", type: :stylesheet # => /assets/application.css # asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js def asset_path(source, options = {}) source = source.to_s return "" unless source.present? return source if source =~ URI_REGEXP tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '') if extname = compute_asset_extname(source, options) source = "#{source}#{extname}" end if source[0] != ?/ source = compute_asset_path(source, options) end relative_url_root = defined?(config.relative_url_root) && config.relative_url_root if relative_url_root source = File.join(relative_url_root, source) unless source.starts_with?("#{relative_url_root}/") end if host = compute_asset_host(source, options) source = File.join(host, source) end "#{source}#{tail}" end alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route # Computes the full URL to an asset in the public directory. This # will use +asset_path+ internally, so most of their behaviors # will be the same. If :host options is set, it overwrites global # +config.action_controller.asset_host+ setting. # # All other options provided are forwarded to +asset_path+ call. # # asset_url "application.js" # => http://example.com/assets/application.js # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js # def asset_url(source, options = {}) path_to_asset(source, options.merge(:protocol => :request)) end alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route ASSET_EXTENSIONS = { javascript: '.js', stylesheet: '.css' } # Compute extname to append to asset path. Returns nil if # nothing should be added. def compute_asset_extname(source, options = {}) return if options[:extname] == false extname = options[:extname] || ASSET_EXTENSIONS[options[:type]] extname if extname && File.extname(source) != extname end # Maps asset types to public directory. ASSET_PUBLIC_DIRECTORIES = { audio: '/audios', font: '/fonts', image: '/images', javascript: '/javascripts', stylesheet: '/stylesheets', video: '/videos' } # Computes asset path to public directory. Plugins and # extensions can override this method to point to custom assets # or generate digested paths or query strings. def compute_asset_path(source, options = {}) dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || "" File.join(dir, source) end # Pick an asset host for this source. Returns +nil+ if no host is set, # the host if no wildcard is set, the host interpolated with the # numbers 0-3 if it contains %d (the number is the source hash mod 4), # or the value returned from invoking call on an object responding to call # (proc or otherwise). def compute_asset_host(source = "", options = {}) request = self.request if respond_to?(:request) host = options[:host] host ||= config.asset_host if defined? config.asset_host if host.respond_to?(:call) arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity args = [source] args << request if request && (arity > 1 || arity < 0) host = host.call(*args) elsif host =~ /%d/ host = host % (Zlib.crc32(source) % 4) end host ||= request.base_url if request && options[:protocol] == :request return unless host if host =~ URI_REGEXP host else protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative) case protocol when :relative "//#{host}" when :request "#{request.protocol}#{host}" else "#{protocol}://#{host}" end end end # Computes the path to a JavaScript asset in the public javascripts directory. # If the +source+ filename has no extension, .js will be appended (except for explicit URIs) # Full paths from the document root will be passed through. # Used internally by +javascript_include_tag+ to build the script path. # # javascript_path "xmlhr" # => /assets/xmlhr.js # javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js def javascript_path(source, options = {}) path_to_asset(source, {type: :javascript}.merge!(options)) end alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route # Computes the full URL to a JavaScript asset in the public javascripts directory. # This will use +javascript_path+ internally, so most of their behaviors will be the same. def javascript_url(source, options = {}) url_to_asset(source, {type: :javascript}.merge!(options)) end alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route # Computes the path to a stylesheet asset in the public stylesheets directory. # If the +source+ filename has no extension, .css will be appended (except for explicit URIs). # Full paths from the document root will be passed through. # Used internally by +stylesheet_link_tag+ to build the stylesheet path. # # stylesheet_path "style" # => /assets/style.css # stylesheet_path "dir/style.css" # => /assets/dir/style.css # stylesheet_path "/dir/style.css" # => /dir/style.css # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css def stylesheet_path(source, options = {}) path_to_asset(source, {type: :stylesheet}.merge!(options)) end alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route # Computes the full URL to a stylesheet asset in the public stylesheets directory. # This will use +stylesheet_path+ internally, so most of their behaviors will be the same. def stylesheet_url(source, options = {}) url_to_asset(source, {type: :stylesheet}.merge!(options)) end alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route # Computes the path to an image asset. # Full paths from the document root will be passed through. # Used internally by +image_tag+ to build the image path: # # image_path("edit") # => "/assets/edit" # image_path("edit.png") # => "/assets/edit.png" # image_path("icons/edit.png") # => "/assets/icons/edit.png" # image_path("/icons/edit.png") # => "/icons/edit.png" # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" # # If you have images as application resources this method may conflict with their named routes. # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and # plugin authors are encouraged to do so. def image_path(source, options = {}) path_to_asset(source, {type: :image}.merge!(options)) end alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route # Computes the full URL to an image asset. # This will use +image_path+ internally, so most of their behaviors will be the same. def image_url(source, options = {}) url_to_asset(source, {type: :image}.merge!(options)) end alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route # Computes the path to a video asset in the public videos directory. # Full paths from the document root will be passed through. # Used internally by +video_tag+ to build the video path. # # video_path("hd") # => /videos/hd # video_path("hd.avi") # => /videos/hd.avi # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi # video_path("/trailers/hd.avi") # => /trailers/hd.avi # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi def video_path(source, options = {}) path_to_asset(source, {type: :video}.merge!(options)) end alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route # Computes the full URL to a video asset in the public videos directory. # This will use +video_path+ internally, so most of their behaviors will be the same. def video_url(source, options = {}) url_to_asset(source, {type: :video}.merge!(options)) end alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route # Computes the path to an audio asset in the public audios directory. # Full paths from the document root will be passed through. # Used internally by +audio_tag+ to build the audio path. # # audio_path("horse") # => /audios/horse # audio_path("horse.wav") # => /audios/horse.wav # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav # audio_path("/sounds/horse.wav") # => /sounds/horse.wav # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav def audio_path(source, options = {}) path_to_asset(source, {type: :audio}.merge!(options)) end alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route # Computes the full URL to an audio asset in the public audios directory. # This will use +audio_path+ internally, so most of their behaviors will be the same. def audio_url(source, options = {}) url_to_asset(source, {type: :audio}.merge!(options)) end alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route # Computes the path to a font asset. # Full paths from the document root will be passed through. # # font_path("font") # => /fonts/font # font_path("font.ttf") # => /fonts/font.ttf # font_path("dir/font.ttf") # => /fonts/dir/font.ttf # font_path("/dir/font.ttf") # => /dir/font.ttf # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf def font_path(source, options = {}) path_to_asset(source, {type: :font}.merge!(options)) end alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route # Computes the full URL to a font asset. # This will use +font_path+ internally, so most of their behaviors will be the same. def font_url(source, options = {}) url_to_asset(source, {type: :font}.merge!(options)) end alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route end end end rails-4.2.6/actionview/lib/action_view/helpers/atom_feed_helper.rb000066400000000000000000000203071266740050600253430ustar00rootroot00000000000000require 'set' module ActionView # = Action View Atom Feed Helpers module Helpers module AtomFeedHelper # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other # template languages). # # Full usage example: # # config/routes.rb: # Rails.application.routes.draw do # resources :posts # root to: "posts#index" # end # # app/controllers/posts_controller.rb: # class PostsController < ApplicationController::Base # # GET /posts.html # # GET /posts.atom # def index # @posts = Post.all # # respond_to do |format| # format.html # format.atom # end # end # end # # app/views/posts/index.atom.builder: # atom_feed do |feed| # feed.title("My great blog!") # feed.updated(@posts[0].created_at) if @posts.length > 0 # # @posts.each do |post| # feed.entry(post) do |entry| # entry.title(post.title) # entry.content(post.body, type: 'html') # # entry.author do |author| # author.name("DHH") # end # end # end # end # # The options for atom_feed are: # # * :language: Defaults to "en-US". # * :root_url: The HTML alternative that this feed is doubling for. Defaults to / on the current host. # * :url: The URL for this feed. Defaults to the current URL. # * :id: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}" # * :schema_date: The date at which the tag scheme for the feed was first used. A good default is the year you # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, # 2005 is used (as an "I don't care" value). # * :instruct: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]} # # Other namespaces can be added to the root element: # # app/views/posts/index.atom.builder: # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app', # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| # feed.title("My great blog!") # feed.updated((@posts.first.created_at)) # feed.tag!('openSearch:totalResults', 10) # # @posts.each do |post| # feed.entry(post) do |entry| # entry.title(post.title) # entry.content(post.body, type: 'html') # entry.tag!('app:edited', Time.now) # # entry.author do |author| # author.name("DHH") # end # end # end # end # # The Atom spec defines five elements (content rights title subtitle # summary) which may directly contain xhtml content if type: 'xhtml' # is specified as an attribute. If so, this helper will take care of # the enclosing div and xhtml namespace declaration. Example usage: # # entry.summary type: 'xhtml' do |xhtml| # xhtml.p pluralize(order.line_items.count, "line item") # xhtml.p "Shipped to #{order.address}" # xhtml.p "Paid by #{order.pay_type}" # end # # # atom_feed yields an +AtomFeedBuilder+ instance. Nested elements yield # an +AtomBuilder+ instance. def atom_feed(options = {}, &block) if options[:schema_date] options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime) else options[:schema_date] = "2005" # The Atom spec copyright date end xml = options.delete(:xml) || eval("xml", block.binding) xml.instruct! if options[:instruct] options[:instruct].each do |target,attrs| if attrs.respond_to?(:keys) xml.instruct!(target, attrs) elsif attrs.respond_to?(:each) attrs.each { |attr_group| xml.instruct!(target, attr_group) } end end end feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'} feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} xml.feed(feed_opts) do xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}") xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port)) xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) yield AtomFeedBuilder.new(xml, self, options) end end class AtomBuilder #:nodoc: XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set def initialize(xml) @xml = xml end private # Delegate to xml builder, first wrapping the element in a xhtml # namespaced div element if the method and arguments indicate # that an xhtml_block? is desired. def method_missing(method, *arguments, &block) if xhtml_block?(method, arguments) @xml.__send__(method, *arguments) do @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml| block.call(xhtml) end end else @xml.__send__(method, *arguments, &block) end end # True if the method name matches one of the five elements defined # in the Atom spec as potentially containing XHTML content and # if type: 'xhtml' is, in fact, specified. def xhtml_block?(method, arguments) if XHTML_TAG_NAMES.include?(method.to_s) last = arguments.last last.is_a?(Hash) && last[:type].to_s == 'xhtml' end end end class AtomFeedBuilder < AtomBuilder #:nodoc: def initialize(xml, view, feed_options = {}) @xml, @view, @feed_options = xml, view, feed_options end # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used. def updated(date_or_time = nil) @xml.updated((date_or_time || Time.now.utc).xmlschema) end # Creates an entry tag for a specific record and prefills the id using class and id. # # Options: # # * :published: Time first published. Defaults to the created_at attribute on the record if one such exists. # * :updated: Time of update. Defaults to the updated_at attribute on the record if one such exists. # * :url: The URL for this entry. Defaults to the polymorphic_url for the record. # * :id: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}" # * :type: The TYPE for this entry. Defaults to "text/html". def entry(record, options = {}) @xml.entry do @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") if options[:published] || (record.respond_to?(:created_at) && record.created_at) @xml.published((options[:published] || record.created_at).xmlschema) end if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at) @xml.updated((options[:updated] || record.updated_at).xmlschema) end type = options.fetch(:type, 'text/html') @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record)) yield AtomBuilder.new(@xml) end end end end end end rails-4.2.6/actionview/lib/action_view/helpers/cache_helper.rb000066400000000000000000000200231266740050600244560ustar00rootroot00000000000000module ActionView # = Action View Cache Helper module Helpers module CacheHelper # This helper exposes a method for caching fragments of a view # rather than an entire action or page. This technique is useful # caching pieces like menus, lists of new topics, static HTML # fragments, and so on. This method takes a block that contains # the content you wish to cache. # # The best way to use this is by doing key-based cache expiration # on top of a cache store like Memcached that'll automatically # kick out old entries. For more on key-based expiration, see: # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works # # When using this method, you list the cache dependency as the name of the cache, like so: # # <% cache project do %> # All the topics on this project # <%= render project.topics %> # <% end %> # # This approach will assume that when a new topic is added, you'll touch # the project. The cache key generated from this call will be something like: # # views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9 # ^class ^id ^updated_at ^template tree digest # # The cache is thus automatically bumped whenever the project updated_at is touched. # # If your template cache depends on multiple sources (try to avoid this to keep things simple), # you can name all these dependencies as part of an array: # # <% cache [ project, current_user ] do %> # All the topics on this project # <%= render project.topics %> # <% end %> # # This will include both records as part of the cache key and updating either of them will # expire the cache. # # ==== Template digest # # The template digest that's added to the cache key is computed by taking an md5 of the # contents of the entire template file. This ensures that your caches will automatically # expire when you change the template file. # # Note that the md5 is taken of the entire template file, not just what's within the # cache do/end call. So it's possible that changing something outside of that call will # still expire the cache. # # Additionally, the digestor will automatically look through your template file for # explicit and implicit dependencies, and include those as part of the digest. # # The digestor can be bypassed by passing skip_digest: true as an option to the cache call: # # <% cache project, skip_digest: true do %> # All the topics on this project # <%= render project.topics %> # <% end %> # # ==== Implicit dependencies # # Most template dependencies can be derived from calls to render in the template itself. # Here are some examples of render calls that Cache Digests knows how to decode: # # render partial: "comments/comment", collection: commentable.comments # render "comments/comments" # render 'comments/comments' # render('comments/comments') # # render "header" => render("comments/header") # # render(@topic) => render("topics/topic") # render(topics) => render("topics/topic") # render(message.topics) => render("topics/topic") # # It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived: # # render group_of_attachments # render @project.documents.where(published: true).order('created_at') # # You will have to rewrite those to the explicit form: # # render partial: 'attachments/attachment', collection: group_of_attachments # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at') # # === Explicit dependencies # # Some times you'll have template dependencies that can't be derived at all. This is typically # the case when you have template rendering that happens in helpers. Here's an example: # # <%= render_sortable_todolists @project.todolists %> # # You'll need to use a special comment format to call those out: # # <%# Template Dependency: todolists/todolist %> # <%= render_sortable_todolists @project.todolists %> # # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so. # You can only declare one template dependency per line. # # === External dependencies # # If you use a helper method, for example, inside of a cached block and you then update that helper, # you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file # must change. One recommendation is to simply be explicit in a comment, like: # # <%# Helper Dependency Updated: May 6, 2012 at 6pm %> # <%= some_helper_method(person) %> # # Now all you'll have to do is change that timestamp when the helper method changes. def cache(name = {}, options = nil, &block) if controller.respond_to?(:perform_caching) && controller.perform_caching safe_concat(fragment_for(cache_fragment_name(name, options), options, &block)) else yield end nil end # Cache fragments of a view if +condition+ is true # # <% cache_if admin?, project do %> # All the topics on this project # <%= render project.topics %> # <% end %> def cache_if(condition, name = {}, options = nil, &block) if condition cache(name, options, &block) else yield end nil end # Cache fragments of a view unless +condition+ is true # # <% cache_unless admin?, project do %> # All the topics on this project # <%= render project.topics %> # <% end %> def cache_unless(condition, name = {}, options = nil, &block) cache_if !condition, name, options, &block end # This helper returns the name of a cache key for a given fragment cache # call. By supplying skip_digest: true to cache, the digestion of cache # fragments can be manually bypassed. This is useful when cache fragments # cannot be manually expired unless you know the exact key which is the # case when using memcached. def cache_fragment_name(name = {}, options = nil) skip_digest = options && options[:skip_digest] if skip_digest name else fragment_name_with_digest(name) end end private def fragment_name_with_digest(name) #:nodoc: if @virtual_path names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name) digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies [ *names, digest ] else name end end # TODO: Create an object that has caching read/write on it def fragment_for(name = {}, options = nil, &block) #:nodoc: read_fragment_for(name, options) || write_fragment_for(name, options, &block) end def read_fragment_for(name, options) #:nodoc: controller.read_fragment(name, options) end def write_fragment_for(name, options) #:nodoc: # VIEW TODO: Make #capture usable outside of ERB # This dance is needed because Builder can't use capture pos = output_buffer.length yield output_safe = output_buffer.html_safe? fragment = output_buffer.slice!(pos..-1) if output_safe self.output_buffer = output_buffer.class.new(output_buffer) end controller.write_fragment(name, fragment, options) end end end end rails-4.2.6/actionview/lib/action_view/helpers/capture_helper.rb000066400000000000000000000167121266740050600250700ustar00rootroot00000000000000require 'active_support/core_ext/string/output_safety' module ActionView # = Action View Capture Helper module Helpers # CaptureHelper exposes methods to let you extract generated markup which # can be used in other parts of a template or layout file. # # It provides a method to capture blocks into variables through capture and # a way to capture a block of markup for use in a layout through content_for. module CaptureHelper # The capture method allows you to extract part of a template into a # variable. You can then use this variable anywhere in your templates or layout. # # The capture method can be used in ERB templates... # # <% @greeting = capture do %> # Welcome to my shiny new web page! The date and time is # <%= Time.now %> # <% end %> # # ...and Builder (RXML) templates. # # @timestamp = capture do # "The current timestamp is #{Time.now}." # end # # You can then use that variable anywhere else. For example: # # # <%= @greeting %> # # <%= @greeting %> # # def capture(*args) value = nil buffer = with_output_buffer { value = yield(*args) } if string = buffer.presence || value and string.is_a?(String) ERB::Util.html_escape string end end # Calling content_for stores a block of markup in an identifier for later use. # In order to access this stored content in other templates, helper modules # or the layout, you would pass the identifier as an argument to content_for. # # Note: yield can still be used to retrieve the stored content, but calling # yield doesn't work in helper modules, while content_for does. # # <% content_for :not_authorized do %> # alert('You are not authorized to do that!') # <% end %> # # You can then use content_for :not_authorized anywhere in your templates. # # <%= content_for :not_authorized if current_user.nil? %> # # This is equivalent to: # # <%= yield :not_authorized if current_user.nil? %> # # content_for, however, can also be used in helper modules. # # module StorageHelper # def stored_content # content_for(:storage) || "Your storage is empty" # end # end # # This helper works just like normal helpers. # # <%= stored_content %> # # You can also use the yield syntax alongside an existing call to # yield in a layout. For example: # # <%# This is the layout %> # # # My Website # <%= yield :script %> # # # <%= yield %> # # # # And now, we'll create a view that has a content_for call that # creates the script identifier. # # <%# This is our view %> # Please login! # # <% content_for :script do %> # # <% end %> # # Then, in another view, you could to do something like this: # # <%= link_to 'Logout', action: 'logout', remote: true %> # # <% content_for :script do %> # <%= javascript_include_tag :defaults %> # <% end %> # # That will place +script+ tags for your default set of JavaScript files on the page; # this technique is useful if you'll only be using these scripts in a few views. # # Note that content_for concatenates (default) the blocks it is given for a particular # identifier in order. For example: # # <% content_for :navigation do %> #
  • <%= link_to 'Home', action: 'index' %>
  • # <% end %> # # And in other place: # # <% content_for :navigation do %> #
  • <%= link_to 'Login', action: 'login' %>
  • # <% end %> # # Then, in another template or layout, this code would render both links in order: # #
      <%= content_for :navigation %>
    # # If the flush parameter is true content_for replaces the blocks it is given. For example: # # <% content_for :navigation do %> #
  • <%= link_to 'Home', action: 'index' %>
  • # <% end %> # # <%# Add some other content, or use a different template: %> # # <% content_for :navigation, flush: true do %> #
  • <%= link_to 'Login', action: 'login' %>
  • # <% end %> # # Then, in another template or layout, this code would render only the last link: # #
      <%= content_for :navigation %>
    # # Lastly, simple content can be passed as a parameter: # # <% content_for :script, javascript_include_tag(:defaults) %> # # WARNING: content_for is ignored in caches. So you shouldn't use it for elements that will be fragment cached. def content_for(name, content = nil, options = {}, &block) if content || block_given? if block_given? options = content if content content = capture(&block) end if content options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content) end nil else @view_flow.get(name).presence end end # The same as +content_for+ but when used with streaming flushes # straight back to the layout. In other words, if you want to # concatenate several times to the same buffer when rendering a given # template, you should use +content_for+, if not, use +provide+ to tell # the layout to stop looking for more contents. def provide(name, content = nil, &block) content = capture(&block) if block_given? result = @view_flow.append!(name, content) if content result unless content end # content_for? checks whether any content has been captured yet using `content_for`. # Useful to render parts of your layout differently based on what is in your views. # # <%# This is the layout %> # # # My Website # <%= yield :script %> # # # <%= yield %> # <%= yield :right_col %> # # def content_for?(name) @view_flow.get(name).present? end # Use an alternate output buffer for the duration of the block. # Defaults to a new empty string. def with_output_buffer(buf = nil) #:nodoc: unless buf buf = ActionView::OutputBuffer.new if output_buffer && output_buffer.respond_to?(:encoding) buf.force_encoding(output_buffer.encoding) end end self.output_buffer, old_buffer = buf, output_buffer yield output_buffer ensure self.output_buffer = old_buffer end end end end rails-4.2.6/actionview/lib/action_view/helpers/controller_helper.rb000066400000000000000000000015211266740050600256000ustar00rootroot00000000000000require 'active_support/core_ext/module/attr_internal' module ActionView module Helpers # This module keeps all methods and behavior in ActionView # that simply delegates to the controller. module ControllerHelper #:nodoc: attr_internal :controller, :request delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, :flash, :action_name, :controller_name, :controller_path, :to => :controller def assign_controller(controller) if @_controller = controller @_request = controller.request if controller.respond_to?(:request) @_config = controller.config.inheritable_copy if controller.respond_to?(:config) end end def logger controller.logger if controller.respond_to?(:logger) end end end end rails-4.2.6/actionview/lib/action_view/helpers/csrf_helper.rb000066400000000000000000000022371266740050600243570ustar00rootroot00000000000000module ActionView # = Action View CSRF Helper module Helpers module CsrfHelper # Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site # request forgery protection parameter and token, respectively. # # # <%= csrf_meta_tags %> # # # These are used to generate the dynamic forms that implement non-remote links with # :method. # # You don't need to use these tags for regular forms as they generate their own hidden fields. # # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically. # def csrf_meta_tags if protect_against_forgery? [ tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token), tag('meta', :name => 'csrf-token', :content => form_authenticity_token) ].join("\n").html_safe end end # For backwards compatibility. alias csrf_meta_tag csrf_meta_tags end end end rails-4.2.6/actionview/lib/action_view/helpers/date_helper.rb000066400000000000000000001604301266740050600243370ustar00rootroot00000000000000require 'date' require 'action_view/helpers/tag_helper' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/with_options' module ActionView module Helpers # = Action View Date Helpers # # The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time # elements. All of the select-type methods share a number of common options that are as follows: # # * :prefix - overwrites the default prefix of "date" used for the select names. So specifying "birthday" # would give \birthday[month] instead of \date[month] if passed to the select_month method. # * :include_blank - set to true if it should be possible to set an empty date. # * :discard_type - set to true if you want to discard the type part of the select name. If set to true, # the select_month method would use simply "date" (which can be overwritten using :prefix) instead # of \date[month]. module DateHelper MINUTES_IN_YEAR = 525600 MINUTES_IN_QUARTER_YEAR = 131400 MINUTES_IN_THREE_QUARTERS_YEAR = 394200 # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds. # Pass include_seconds: true if you want more detailed approximations when distance < 1 min, 29 secs. # Distances are reported based on the following table: # # 0 <-> 29 secs # => less than a minute # 30 secs <-> 1 min, 29 secs # => 1 minute # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour # 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours # 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day # 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days # 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month # 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months # 1 yr <-> 1 yr, 3 months # => about 1 year # 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year # 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years # 2 yrs <-> max time or date # => (same rules as 1 yr) # # With include_seconds: true and the difference < 1 minute 29 seconds: # 0-4 secs # => less than 5 seconds # 5-9 secs # => less than 10 seconds # 10-19 secs # => less than 20 seconds # 20-39 secs # => half a minute # 40-59 secs # => less than a minute # 60-89 secs # => 1 minute # # from_time = Time.now # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute # distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days # distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute # distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years # # to_time = Time.now + 6.years + 19.days # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years # distance_of_time_in_words(Time.now, Time.now) # => less than a minute def distance_of_time_in_words(from_time, to_time = 0, options = {}) options = { scope: :'datetime.distance_in_words' }.merge!(options) from_time = from_time.to_time if from_time.respond_to?(:to_time) to_time = to_time.to_time if to_time.respond_to?(:to_time) from_time, to_time = to_time, from_time if from_time > to_time distance_in_minutes = ((to_time - from_time)/60.0).round distance_in_seconds = (to_time - from_time).round I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale| case distance_in_minutes when 0..1 return distance_in_minutes == 0 ? locale.t(:less_than_x_minutes, :count => 1) : locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds] case distance_in_seconds when 0..4 then locale.t :less_than_x_seconds, :count => 5 when 5..9 then locale.t :less_than_x_seconds, :count => 10 when 10..19 then locale.t :less_than_x_seconds, :count => 20 when 20..39 then locale.t :half_a_minute when 40..59 then locale.t :less_than_x_minutes, :count => 1 else locale.t :x_minutes, :count => 1 end when 2...45 then locale.t :x_minutes, :count => distance_in_minutes when 45...90 then locale.t :about_x_hours, :count => 1 # 90 mins up to 24 hours when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round # 24 hours up to 42 hours when 1440...2520 then locale.t :x_days, :count => 1 # 42 hours up to 30 days when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round # 30 days up to 60 days when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round # 60 days up to 365 days when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round else if from_time.acts_like?(:time) && to_time.acts_like?(:time) fyear = from_time.year fyear += 1 if from_time.month >= 3 tyear = to_time.year tyear -= 1 if to_time.month < 3 leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} minute_offset_for_leap_year = leap_years * 1440 # Discount the leap year days when calculating year distance. # e.g. if there are 20 leap year days between 2 dates having the same day # and month then the based on 365 days calculation # the distance in years will come out to over 80 years when in written # English it would read better as about 80 years. minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year else minutes_with_offset = distance_in_minutes end remainder = (minutes_with_offset % MINUTES_IN_YEAR) distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR) if remainder < MINUTES_IN_QUARTER_YEAR locale.t(:about_x_years, :count => distance_in_years) elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR locale.t(:over_x_years, :count => distance_in_years) else locale.t(:almost_x_years, :count => distance_in_years + 1) end end end end # Like distance_of_time_in_words, but where to_time is fixed to Time.now. # # time_ago_in_words(3.minutes.from_now) # => 3 minutes # time_ago_in_words(3.minutes.ago) # => 3 minutes # time_ago_in_words(Time.now - 15.hours) # => about 15 hours # time_ago_in_words(Time.now) # => less than a minute # time_ago_in_words(Time.now, include_seconds: true) # => less than 5 seconds # # from_time = Time.now - 3.days - 14.minutes - 25.seconds # time_ago_in_words(from_time) # => 3 days # # from_time = (3.days + 14.minutes + 25.seconds).ago # time_ago_in_words(from_time) # => 3 days # # Note that you cannot pass a Numeric value to time_ago_in_words. # def time_ago_in_words(from_time, options = {}) distance_of_time_in_words(from_time, Time.now, options) end alias_method :distance_of_time_in_words_to_now, :time_ago_in_words # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based # attribute (identified by +method+) on an object assigned to the template (identified by +object+). # # ==== Options # * :use_month_numbers - Set to true if you want to use month numbers rather than month names (e.g. # "2" instead of "February"). # * :use_two_digit_numbers - Set to true if you want to display two digit month and day numbers (e.g. # "02" instead of "February" and "08" instead of "8"). # * :use_short_month - Set to true if you want to use abbreviated month names instead of full # month names (e.g. "Feb" instead of "February"). # * :add_month_numbers - Set to true if you want to use both month numbers and month names (e.g. # "2 - February" instead of "February"). # * :use_month_names - Set to an array with 12 month names if you want to customize month names. # Note: You can also use Rails' i18n functionality for this. # * :month_format_string - Set to a format string. The string gets passed keys +:number+ (integer) # and +:name+ (string). A format string would be something like "%{name} (%02d)" for example. # See Kernel.sprintf for documentation on format sequences. # * :date_separator - Specifies a string to separate the date fields. Default is "" (i.e. nothing). # * :start_year - Set the start year for the year select. Default is Date.today.year - 5 if # you are creating new record. While editing existing record, :start_year defaults to # the current selected year minus 5. # * :end_year - Set the end year for the year select. Default is Date.today.year + 5 if # you are creating new record. While editing existing record, :end_year defaults to # the current selected year plus 5. # * :discard_day - Set to true if you don't want to show a day select. This includes the day # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the # first of the given month in order to not create invalid dates like 31 February. # * :discard_month - Set to true if you don't want to show a month select. This includes the month # as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true. # * :discard_year - Set to true if you don't want to show a year select. This includes the year # as a hidden field instead of showing a select field. # * :order - Set to an array containing :day, :month and :year to # customize the order in which the select fields are shown. If you leave out any of the symbols, the respective # select will not be shown (like when you set discard_xxx: true. Defaults to the order defined in # the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails). # * :include_blank - Include a blank option in every select field so it's possible to set empty # dates. # * :default - Set a default date if the affected date isn't set or is nil. # * :selected - Set a date that overrides the actual value. # * :disabled - Set to true if you want show the select fields as disabled. # * :prompt - Set to true (for a generic prompt), a prompt string or a hash of prompt strings # for :year, :month, :day, :hour, :minute and :second. # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds) # or the given prompt string. # * :with_css_classes - Set to true if you want assign different styles for 'select' tags. This option # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags. # # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. # # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed. # # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute. # date_select("article", "written_on") # # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, # # with the year in the year drop down box starting at 1995. # date_select("article", "written_on", start_year: 1995) # # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, # # with the year in the year drop down box starting at 1995, numbers used for months instead of words, # # and without a day select box. # date_select("article", "written_on", start_year: 1995, use_month_numbers: true, # discard_day: true, include_blank: true) # # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, # # with two digit numbers used for months and days. # date_select("article", "written_on", use_two_digit_numbers: true) # # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute # # with the fields ordered as day, month, year rather than month, day, year. # date_select("article", "written_on", order: [:day, :month, :year]) # # # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute # # lacking a year field. # date_select("user", "birthday", order: [:month, :day]) # # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute # # which is initially set to the date 3 days from the current date # date_select("article", "written_on", default: 3.days.from_now) # # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute # # which is set in the form with todays date, regardless of the value in the Active Record object. # date_select("article", "written_on", selected: Date.today) # # # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute # # that will have a default day of 20. # date_select("credit_card", "bill_due", default: { day: 20 }) # # # Generates a date select with custom prompts. # date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' }) # # The selects are prepared for multi-parameter assignment to an Active Record object. # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that # all month choices are valid. def date_select(object_name, method, options = {}, html_options = {}) Tags::DateSelect.new(object_name, method, self, options, html_options).render end # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a # specified time-based attribute (identified by +method+) on an object assigned to the template (identified by # +object+). You can include the seconds with :include_seconds. You can get hours in the AM/PM format # with :ampm option. # # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option # :ignore_date is set to +true+. If you set the :ignore_date to +true+, you must have a # +date_select+ on the same method within the form otherwise an exception will be raised. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # # # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute. # time_select("article", "sunrise") # # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in # # the sunrise attribute. # time_select("article", "start_time", include_seconds: true) # # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45. # time_select 'game', 'game_time', {minute_step: 15} # # # Creates a time select tag with a custom prompt. Use prompt: true for generic prompts. # time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'}) # time_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours # time_select("article", "written_on", prompt: true) # generic prompts for all # # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM. # time_select 'game', 'game_time', {ampm: true} # # The selects are prepared for multi-parameter assignment to an Active Record object. # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that # all month choices are valid. def time_select(object_name, method, options = {}, html_options = {}) Tags::TimeSelect.new(object_name, method, self, options, html_options).render end # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a # specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified # by +object+). # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # # # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on # # attribute. # datetime_select("article", "written_on") # # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the # # article variable in the written_on attribute. # datetime_select("article", "written_on", start_year: 1995) # # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will # # be stored in the trip variable in the departing attribute. # datetime_select("trip", "departing", default: 3.days.from_now) # # # Generate a datetime select with hours in the AM/PM format # datetime_select("article", "written_on", ampm: true) # # # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable # # as the written_on attribute. # datetime_select("article", "written_on", discard_type: true) # # # Generates a datetime select with a custom prompt. Use prompt: true for generic prompts. # datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'}) # datetime_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours # datetime_select("article", "written_on", prompt: true) # generic prompts for all # # The selects are prepared for multi-parameter assignment to an Active Record object. def datetime_select(object_name, method, options = {}, html_options = {}) Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render end # Returns a set of HTML select-tags (one for year, month, day, hour, minute, and second) pre-selected with the # +datetime+. It's also possible to explicitly set the order of the tags using the :order option with # an array of symbols :year, :month and :day in the desired order. If you do not # supply a Symbol, it will be appended onto the :order passed in. You can also add # :date_separator, :datetime_separator and :time_separator keys to the +options+ to # control visual display of the elements. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # # my_date_time = Time.now + 4.days # # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today). # select_datetime(my_date_time) # # # Generates a datetime select that defaults to today (no specified datetime) # select_datetime() # # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) # # with the fields ordered year, month, day rather than month, day, year. # select_datetime(my_date_time, order: [:year, :month, :day]) # # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) # # with a '/' between each date field. # select_datetime(my_date_time, date_separator: '/') # # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) # # with a date fields separated by '/', time fields separated by '' and the date and time fields # # separated by a comma (','). # select_datetime(my_date_time, date_separator: '/', time_separator: '', datetime_separator: ',') # # # Generates a datetime select that discards the type of the field and defaults to the datetime in # # my_date_time (four days after today) # select_datetime(my_date_time, discard_type: true) # # # Generate a datetime field with hours in the AM/PM format # select_datetime(my_date_time, ampm: true) # # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) # # prefixed with 'payday' rather than 'date' # select_datetime(my_date_time, prefix: 'payday') # # # Generates a datetime select with a custom prompt. Use prompt: true for generic prompts. # select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'}) # select_datetime(my_date_time, prompt: {hour: true}) # generic prompt for hours # select_datetime(my_date_time, prompt: true) # generic prompts for all def select_datetime(datetime = Time.current, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_datetime end # Returns a set of HTML select-tags (one for year, month, and day) pre-selected with the +date+. # It's possible to explicitly set the order of the tags using the :order option with an array of # symbols :year, :month and :day in the desired order. # If the array passed to the :order option does not contain all the three symbols, all tags will be hidden. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # # my_date = Time.now + 6.days # # # Generates a date select that defaults to the date in my_date (six days after today). # select_date(my_date) # # # Generates a date select that defaults to today (no specified date). # select_date() # # # Generates a date select that defaults to the date in my_date (six days after today) # # with the fields ordered year, month, day rather than month, day, year. # select_date(my_date, order: [:year, :month, :day]) # # # Generates a date select that discards the type of the field and defaults to the date in # # my_date (six days after today). # select_date(my_date, discard_type: true) # # # Generates a date select that defaults to the date in my_date, # # which has fields separated by '/'. # select_date(my_date, date_separator: '/') # # # Generates a date select that defaults to the datetime in my_date (six days after today) # # prefixed with 'payday' rather than 'date'. # select_date(my_date, prefix: 'payday') # # # Generates a date select with a custom prompt. Use prompt: true for generic prompts. # select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'}) # select_date(my_date, prompt: {hour: true}) # generic prompt for hours # select_date(my_date, prompt: true) # generic prompts for all def select_date(date = Date.current, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_date end # Returns a set of HTML select-tags (one for hour and minute). # You can set :time_separator key to format the output, and # the :include_seconds option to include an input for seconds. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # # my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds # # # Generates a time select that defaults to the time in my_time. # select_time(my_time) # # # Generates a time select that defaults to the current time (no specified time). # select_time() # # # Generates a time select that defaults to the time in my_time, # # which has fields separated by ':'. # select_time(my_time, time_separator: ':') # # # Generates a time select that defaults to the time in my_time, # # that also includes an input for seconds. # select_time(my_time, include_seconds: true) # # # Generates a time select that defaults to the time in my_time, that has fields # # separated by ':' and includes an input for seconds. # select_time(my_time, time_separator: ':', include_seconds: true) # # # Generate a time select field with hours in the AM/PM format # select_time(my_time, ampm: true) # # # Generates a time select field with hours that range from 2 to 14 # select_time(my_time, start_hour: 2, end_hour: 14) # # # Generates a time select with a custom prompt. Use :prompt to true for generic prompts. # select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'}) # select_time(my_time, prompt: {hour: true}) # generic prompt for hours # select_time(my_time, prompt: true) # generic prompts for all def select_time(datetime = Time.current, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_time end # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. # The datetime can be either a +Time+ or +DateTime+ object or an integer. # Override the field name using the :field_name option, 'second' by default. # # my_time = Time.now + 16.minutes # # # Generates a select field for seconds that defaults to the seconds for the time in my_time. # select_second(my_time) # # # Generates a select field for seconds that defaults to the number given. # select_second(33) # # # Generates a select field for seconds that defaults to the seconds for the time in my_time # # that is named 'interval' rather than 'second'. # select_second(my_time, field_name: 'interval') # # # Generates a select field for seconds with a custom prompt. Use prompt: true for a # # generic prompt. # select_second(14, prompt: 'Choose seconds') def select_second(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_second end # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected. # Also can return a select tag with options by minute_step from 0 through 59 with the 00 minute # selected. The datetime can be either a +Time+ or +DateTime+ object or an integer. # Override the field name using the :field_name option, 'minute' by default. # # my_time = Time.now + 6.hours # # # Generates a select field for minutes that defaults to the minutes for the time in my_time. # select_minute(my_time) # # # Generates a select field for minutes that defaults to the number given. # select_minute(14) # # # Generates a select field for minutes that defaults to the minutes for the time in my_time # # that is named 'moment' rather than 'minute'. # select_minute(my_time, field_name: 'moment') # # # Generates a select field for minutes with a custom prompt. Use prompt: true for a # # generic prompt. # select_minute(14, prompt: 'Choose minutes') def select_minute(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_minute end # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. # The datetime can be either a +Time+ or +DateTime+ object or an integer. # Override the field name using the :field_name option, 'hour' by default. # # my_time = Time.now + 6.hours # # # Generates a select field for hours that defaults to the hour for the time in my_time. # select_hour(my_time) # # # Generates a select field for hours that defaults to the number given. # select_hour(13) # # # Generates a select field for hours that defaults to the hour for the time in my_time # # that is named 'stride' rather than 'hour'. # select_hour(my_time, field_name: 'stride') # # # Generates a select field for hours with a custom prompt. Use prompt: true for a # # generic prompt. # select_hour(13, prompt: 'Choose hour') # # # Generate a select field for hours in the AM/PM format # select_hour(my_time, ampm: true) # # # Generates a select field that includes options for hours from 2 to 14. # select_hour(my_time, start_hour: 2, end_hour: 14) def select_hour(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_hour end # Returns a select tag with options for each of the days 1 through 31 with the current day selected. # The date can also be substituted for a day number. # If you want to display days with a leading zero set the :use_two_digit_numbers key in +options+ to true. # Override the field name using the :field_name option, 'day' by default. # # my_date = Time.now + 2.days # # # Generates a select field for days that defaults to the day for the date in my_date. # select_day(my_date) # # # Generates a select field for days that defaults to the number given. # select_day(5) # # # Generates a select field for days that defaults to the number given, but displays it with two digits. # select_day(5, use_two_digit_numbers: true) # # # Generates a select field for days that defaults to the day for the date in my_date # # that is named 'due' rather than 'day'. # select_day(my_date, field_name: 'due') # # # Generates a select field for days with a custom prompt. Use prompt: true for a # # generic prompt. # select_day(5, prompt: 'Choose day') def select_day(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_day end # Returns a select tag with options for each of the months January through December with the current month # selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are # used as values (what's submitted to the server). It's also possible to use month numbers for the presentation # instead of names -- set the :use_month_numbers key in +options+ to true for this to happen. If you # want both numbers and names, set the :add_month_numbers key in +options+ to true. If you would prefer # to show month names as abbreviations, set the :use_short_month key in +options+ to true. If you want # to use your own month names, set the :use_month_names key in +options+ to an array of 12 month names. # If you want to display months with a leading zero set the :use_two_digit_numbers key in +options+ to true. # Override the field name using the :field_name option, 'month' by default. # # # Generates a select field for months that defaults to the current month that # # will use keys like "January", "March". # select_month(Date.today) # # # Generates a select field for months that defaults to the current month that # # is named "start" rather than "month". # select_month(Date.today, field_name: 'start') # # # Generates a select field for months that defaults to the current month that # # will use keys like "1", "3". # select_month(Date.today, use_month_numbers: true) # # # Generates a select field for months that defaults to the current month that # # will use keys like "1 - January", "3 - March". # select_month(Date.today, add_month_numbers: true) # # # Generates a select field for months that defaults to the current month that # # will use keys like "Jan", "Mar". # select_month(Date.today, use_short_month: true) # # # Generates a select field for months that defaults to the current month that # # will use keys like "Januar", "Marts." # select_month(Date.today, use_month_names: %w(Januar Februar Marts ...)) # # # Generates a select field for months that defaults to the current month that # # will use keys with two digit numbers like "01", "03". # select_month(Date.today, use_two_digit_numbers: true) # # # Generates a select field for months with a custom prompt. Use prompt: true for a # # generic prompt. # select_month(14, prompt: 'Choose month') def select_month(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_month end # Returns a select tag with options for each of the five years on each side of the current, which is selected. # The five year radius can be changed using the :start_year and :end_year keys in the # +options+. Both ascending and descending year lists are supported by making :start_year less than or # greater than :end_year. The date can also be substituted for a year given as a number. # Override the field name using the :field_name option, 'year' by default. # # # Generates a select field for years that defaults to the current year that # # has ascending year values. # select_year(Date.today, start_year: 1992, end_year: 2007) # # # Generates a select field for years that defaults to the current year that # # is named 'birth' rather than 'year'. # select_year(Date.today, field_name: 'birth') # # # Generates a select field for years that defaults to the current year that # # has descending year values. # select_year(Date.today, start_year: 2005, end_year: 1900) # # # Generates a select field for years that defaults to the year 2006 that # # has ascending year values. # select_year(2006, start_year: 2000, end_year: 2010) # # # Generates a select field for years with a custom prompt. Use prompt: true for a # # generic prompt. # select_year(14, prompt: 'Choose year') def select_year(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_year end # Returns an HTML time tag for the given date or time. # # time_tag Date.today # => # # time_tag Time.now # => # # time_tag Date.yesterday, 'Yesterday' # => # # time_tag Date.today, pubdate: true # => # # time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # => # # # <%= time_tag Time.now do %> # Right now # <% end %> # # => def time_tag(date_or_time, *args, &block) options = args.extract_options! format = options.delete(:format) || :long content = args.first || I18n.l(date_or_time, :format => format) datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601 content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block) end end class DateTimeSelector #:nodoc: include ActionView::Helpers::TagHelper DEFAULT_PREFIX = 'date'.freeze POSITION = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }.freeze AMPM_TRANSLATION = Hash[ [[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"], [4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"], [8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"], [12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"], [16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"], [20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]] ].freeze def initialize(datetime, options = {}, html_options = {}) @options = options.dup @html_options = html_options.dup @datetime = datetime @options[:datetime_separator] ||= ' — ' @options[:time_separator] ||= ' : ' end def select_datetime order = date_order.dup order -= [:hour, :minute, :second] @options[:discard_year] ||= true unless order.include?(:year) @options[:discard_month] ||= true unless order.include?(:month) @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) @options[:discard_minute] ||= true if @options[:discard_hour] @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute] set_day_if_discarded if @options[:tag] && @options[:ignore_date] select_time else [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } order += [:hour, :minute, :second] unless @options[:discard_hour] build_selects_from_types(order) end end def select_date order = date_order.dup @options[:discard_hour] = true @options[:discard_minute] = true @options[:discard_second] = true @options[:discard_year] ||= true unless order.include?(:year) @options[:discard_month] ||= true unless order.include?(:month) @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) set_day_if_discarded [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } build_selects_from_types(order) end def select_time order = [] @options[:discard_month] = true @options[:discard_year] = true @options[:discard_day] = true @options[:discard_second] ||= true unless @options[:include_seconds] order += [:year, :month, :day] unless @options[:ignore_date] order += [:hour, :minute] order << :second if @options[:include_seconds] build_selects_from_types(order) end def select_second if @options[:use_hidden] || @options[:discard_second] build_hidden(:second, sec) if @options[:include_seconds] else build_options_and_select(:second, sec) end end def select_minute if @options[:use_hidden] || @options[:discard_minute] build_hidden(:minute, min) else build_options_and_select(:minute, min, :step => @options[:minute_step]) end end def select_hour if @options[:use_hidden] || @options[:discard_hour] build_hidden(:hour, hour) else options = {} options[:ampm] = @options[:ampm] || false options[:start] = @options[:start_hour] || 0 options[:end] = @options[:end_hour] || 23 build_options_and_select(:hour, hour, options) end end def select_day if @options[:use_hidden] || @options[:discard_day] build_hidden(:day, day || 1) else build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers]) end end def select_month if @options[:use_hidden] || @options[:discard_month] build_hidden(:month, month || 1) else month_options = [] 1.upto(12) do |month_number| options = { :value => month_number } options[:selected] = "selected" if month == month_number month_options << content_tag(:option, month_name(month_number), options) + "\n" end build_select(:month, month_options.join) end end def select_year if !@datetime || @datetime == 0 val = '1' middle_year = Date.today.year else val = middle_year = year end if @options[:use_hidden] || @options[:discard_year] build_hidden(:year, val) else options = {} options[:start] = @options[:start_year] || middle_year - 5 options[:end] = @options[:end_year] || middle_year + 5 options[:step] = options[:start] < options[:end] ? 1 : -1 options[:leading_zeros] = false options[:max_years_allowed] = @options[:max_years_allowed] || 1000 if (options[:end] - options[:start]).abs > options[:max_years_allowed] raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter." end build_options_and_select(:year, val, options) end end private %w( sec min hour day month year ).each do |method| define_method(method) do @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime end end # If the day is hidden, the day should be set to the 1st so all month and year choices are # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid. def set_day_if_discarded if @datetime && @options[:discard_day] @datetime = @datetime.change(:day => 1) end end # Returns translated month names, but also ensures that a custom month # name array has a leading nil element. def month_names @month_names ||= begin month_names = @options[:use_month_names] || translated_month_names month_names.unshift(nil) if month_names.size < 13 month_names end end # Returns translated month names. # => [nil, "January", "February", "March", # "April", "May", "June", "July", # "August", "September", "October", # "November", "December"] # # If :use_short_month option is set # => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun", # "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] def translated_month_names key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names' I18n.translate(key, :locale => @options[:locale]) end # Looks up month names by number (1-based): # # month_name(1) # => "January" # # If the :use_month_numbers option is passed: # # month_name(1) # => 1 # # If the :use_two_month_numbers option is passed: # # month_name(1) # => '01' # # If the :add_month_numbers option is passed: # # month_name(1) # => "1 - January" # # If the :month_format_string option is passed: # # month_name(1) # => "January (01)" # # depending on the format string. def month_name(number) if @options[:use_month_numbers] number elsif @options[:use_two_digit_numbers] '%02d' % number elsif @options[:add_month_numbers] "#{number} - #{month_names[number]}" elsif format_string = @options[:month_format_string] format_string % {number: number, name: month_names[number]} else month_names[number] end end def date_order @date_order ||= @options[:order] || translated_date_order end def translated_date_order date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => []) date_order = date_order.map { |element| element.to_sym } forbidden_elements = date_order - [:year, :month, :day] if forbidden_elements.any? raise StandardError, "#{@options[:locale]}.date.order only accepts :year, :month and :day" end date_order end # Build full select tag from date type and options. def build_options_and_select(type, selected, options = {}) build_select(type, build_options(selected, options)) end # Build select option HTML from date value and options. # build_options(15, start: 1, end: 31) # => " # # ..." # # If use_two_digit_numbers: true option is passed # build_options(15, start: 1, end: 31, use_two_digit_numbers: true) # => " # # ..." # # If :step options is passed # build_options(15, start: 1, end: 31, step: 2) # => " # # ..." def build_options(selected, options = {}) options = { leading_zeros: true, ampm: false, use_two_digit_numbers: false }.merge!(options) start = options.delete(:start) || 0 stop = options.delete(:end) || 59 step = options.delete(:step) || 1 leading_zeros = options.delete(:leading_zeros) select_options = [] start.step(stop, step) do |i| value = leading_zeros ? sprintf("%02d", i) : i tag_options = { :value => value } tag_options[:selected] = "selected" if selected == i text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value text = options[:ampm] ? AMPM_TRANSLATION[i] : text select_options << content_tag(:option, text, tag_options) end (select_options.join("\n") + "\n").html_safe end # Builds select tag from date type and HTML select options. # build_select(:month, "...") # => "" def build_select(type, select_options_as_html) select_options = { :id => input_id_from_type(type), :name => input_name_from_type(type) }.merge!(@html_options) select_options[:disabled] = 'disabled' if @options[:disabled] select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes] select_html = "\n" select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] select_html << select_options_as_html (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe end # Builds a prompt option tag with supplied options or from default options. # prompt_option_tag(:month, prompt: 'Select month') # => "" def prompt_option_tag(type, options) prompt = case options when Hash default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false} default_options.merge!(options)[type.to_sym] when String options else I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale]) end prompt ? content_tag(:option, prompt, :value => '') : '' end # Builds hidden input tag for date part and value. # build_hidden(:year, 2008) # => "" def build_hidden(type, value) select_options = { :type => "hidden", :id => input_id_from_type(type), :name => input_name_from_type(type), :value => value }.merge!(@html_options.slice(:disabled)) select_options[:disabled] = 'disabled' if @options[:disabled] tag(:input, select_options) + "\n".html_safe end # Returns the name attribute for the input tag. # => post[written_on(1i)] def input_name_from_type(type) prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX prefix += "[#{@options[:index]}]" if @options.has_key?(:index) field_name = @options[:field_name] || type if @options[:include_position] field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)" end @options[:discard_type] ? prefix : "#{prefix}[#{field_name}]" end # Returns the id attribute for the input tag. # => "post_written_on_1i" def input_id_from_type(type) id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') id = @options[:namespace] + '_' + id if @options[:namespace] id end # Given an ordering of datetime components, create the selection HTML # and join them with their appropriate separators. def build_selects_from_types(order) select = '' first_visible = order.find { |type| !@options[:"discard_#{type}"] } order.reverse_each do |type| separator = separator(type) unless type == first_visible # don't add before first visible field select.insert(0, separator.to_s + send("select_#{type}").to_s) end select.html_safe end # Returns the separator for a given datetime component. def separator(type) return "" if @options[:use_hidden] case type when :year, :month, :day @options[:"discard_#{type}"] ? "" : @options[:date_separator] when :hour (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] when :minute, :second @options[:"discard_#{type}"] ? "" : @options[:time_separator] end end end class FormBuilder # Wraps ActionView::Helpers::DateHelper#date_select for form builders: # # <%= form_for @person do |f| %> # <%= f.date_select :birth_date %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def date_select(method, options = {}, html_options = {}) @template.date_select(@object_name, method, objectify_options(options), html_options) end # Wraps ActionView::Helpers::DateHelper#time_select for form builders: # # <%= form_for @race do |f| %> # <%= f.time_select :average_lap %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def time_select(method, options = {}, html_options = {}) @template.time_select(@object_name, method, objectify_options(options), html_options) end # Wraps ActionView::Helpers::DateHelper#datetime_select for form builders: # # <%= form_for @person do |f| %> # <%= f.datetime_select :last_request_at %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def datetime_select(method, options = {}, html_options = {}) @template.datetime_select(@object_name, method, objectify_options(options), html_options) end end end end rails-4.2.6/actionview/lib/action_view/helpers/debug_helper.rb000066400000000000000000000022561266740050600245110ustar00rootroot00000000000000module ActionView # = Action View Debug Helper # # Provides a set of methods for making it easier to debug Rails objects. module Helpers module DebugHelper include TagHelper # Returns a YAML representation of +object+ wrapped with
     and 
    . # If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead. # Useful for inspecting an object at the time of rendering. # # @user = User.new({ username: 'testing', password: 'xyz', age: 42}) # debug(@user) # # => #
    --- !ruby/object:User
          #   attributes:
          #     updated_at:
          #     username: testing
          #     age: 42
          #     password: xyz
          #     created_at:
          #   
    def debug(object) Marshal::dump(object) object = ERB::Util.html_escape(object.to_yaml) content_tag(:pre, object, :class => "debug_dump") rescue Exception # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback content_tag(:code, object.inspect, :class => "debug_dump") end end end end rails-4.2.6/actionview/lib/action_view/helpers/form_helper.rb000066400000000000000000002571411266740050600243730ustar00rootroot00000000000000require 'cgi' require 'action_view/helpers/date_helper' require 'action_view/helpers/tag_helper' require 'action_view/helpers/form_tag_helper' require 'action_view/helpers/active_model_helper' require 'action_view/model_naming' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/inflections' module ActionView # = Action View Form Helpers module Helpers # Form helpers are designed to make working with resources much easier # compared to using vanilla HTML. # # Typically, a form designed to create or update a resource reflects the # identity of the resource in several ways: (i) the url that the form is # sent to (the form element's +action+ attribute) should result in a request # being routed to the appropriate controller action (with the appropriate :id # parameter in the case of an existing resource), (ii) input fields should # be named in such a way that in the controller their values appear in the # appropriate places within the +params+ hash, and (iii) for an existing record, # when the form is initially displayed, input fields corresponding to attributes # of the resource should show the current values of those attributes. # # In Rails, this is usually achieved by creating the form using +form_for+ and # a number of related helper methods. +form_for+ generates an appropriate form # tag and yields a form builder object that knows the model the form is about. # Input fields are created by calling methods defined on the form builder, which # means they are able to generate the appropriate names and default values # corresponding to the model attributes, as well as convenient IDs, etc. # Conventions in the generated field names allow controllers to receive form data # nicely structured in +params+ with no effort on your side. # # For example, to create a new person you typically set up a new instance of # +Person+ in the PeopleController#new action, @person, and # in the view template pass that object to +form_for+: # # <%= form_for @person do |f| %> # <%= f.label :first_name %>: # <%= f.text_field :first_name %>
    # # <%= f.label :last_name %>: # <%= f.text_field :last_name %>
    # # <%= f.submit %> # <% end %> # # The HTML generated for this would be (modulus formatting): # #
    # # : #
    # # : #
    # # #
    # # As you see, the HTML reflects knowledge about the resource in several spots, # like the path the form should be submitted to, or the names of the input fields. # # In particular, thanks to the conventions followed in the generated field names, the # controller gets a nested hash params[:person] with the person attributes # set in the form. That hash is ready to be passed to Person.create: # # if @person = Person.create(params[:person]) # # success # else # # error handling # end # # Interestingly, the exact same view code in the previous example can be used to edit # a person. If @person is an existing record with name "John Smith" and ID 256, # the code above as is would yield instead: # #
    # # # : #
    # # : #
    # # #
    # # Note that the endpoint, default values, and submit button label are tailored for @person. # That works that way because the involved helpers know whether the resource is a new record or not, # and generate HTML accordingly. # # The controller would receive the form data again in params[:person], ready to be # passed to Person#update: # # if @person.update(params[:person]) # # success # else # # error handling # end # # That's how you typically work with resources. module FormHelper extend ActiveSupport::Concern include FormTagHelper include UrlHelper include ModelNaming # Creates a form that allows the user to create or update the attributes # of a specific model object. # # The method can be used in several slightly different ways, depending on # how much you wish to rely on Rails to infer automatically from the model # how the form should be constructed. For a generic model object, a form # can be created by passing +form_for+ a string or symbol representing # the object we are concerned with: # # <%= form_for :person do |f| %> # First name: <%= f.text_field :first_name %>
    # Last name : <%= f.text_field :last_name %>
    # Biography : <%= f.text_area :biography %>
    # Admin? : <%= f.check_box :admin %>
    # <%= f.submit %> # <% end %> # # The variable +f+ yielded to the block is a FormBuilder object that # incorporates the knowledge about the model object represented by # :person passed to +form_for+. Methods defined on the FormBuilder # are used to generate fields bound to this model. Thus, for example, # # <%= f.text_field :first_name %> # # will get expanded to # # <%= text_field :person, :first_name %> # which results in an HTML tag whose +name+ attribute is # person[first_name]. This means that when the form is submitted, # the value entered by the user will be available in the controller as # params[:person][:first_name]. # # For fields generated in this way using the FormBuilder, # if :person also happens to be the name of an instance variable # @person, the default value of the field shown when the form is # initially displayed (e.g. in the situation where you are editing an # existing record) will be the value of the corresponding attribute of # @person. # # The rightmost argument to +form_for+ is an # optional hash of options - # # * :url - The URL the form is to be submitted to. This may be # represented in the same way as values passed to +url_for+ or +link_to+. # So for example you may use a named route directly. When the model is # represented by a string or symbol, as in the example above, if the # :url option is not specified, by default the form will be # sent back to the current url (We will describe below an alternative # resource-oriented usage of +form_for+ in which the URL does not need # to be specified explicitly). # * :namespace - A namespace for your form to ensure uniqueness of # id attributes on form elements. The namespace attribute will be prefixed # with underscore on the generated HTML id. # * :method - The method to use when submitting the form, usually # either "get" or "post". If "patch", "put", "delete", or another verb # is used, a hidden input with name _method is added to # simulate the verb over post. # * :authenticity_token - Authenticity token to use in the form. # Use only if you need to pass custom authenticity token string, or to # not add authenticity_token field at all (by passing false). # Remote forms may omit the embedded authenticity token by setting # config.action_view.embed_authenticity_token_in_remote_forms = false. # This is helpful when you're fragment-caching the form. Remote forms # get the authenticity token from the meta tag, so embedding is # unnecessary unless you support browsers without JavaScript. # * :remote - If set to true, will allow the Unobtrusive # JavaScript drivers to control the submit behavior. By default this # behavior is an ajax submit. # * :enforce_utf8 - If set to false, a hidden input with name # utf8 is not output. # * :html - Optional HTML attributes for the form tag. # # Also note that +form_for+ doesn't create an exclusive scope. It's still # possible to use both the stand-alone FormHelper methods and methods # from FormTagHelper. For example: # # <%= form_for :person do |f| %> # First name: <%= f.text_field :first_name %> # Last name : <%= f.text_field :last_name %> # Biography : <%= text_area :person, :biography %> # Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %> # <%= f.submit %> # <% end %> # # This also works for the methods in FormOptionHelper and DateHelper that # are designed to work with an object as base, like # FormOptionHelper#collection_select and DateHelper#datetime_select. # # === #form_for with a model object # # In the examples above, the object to be created or edited was # represented by a symbol passed to +form_for+, and we noted that # a string can also be used equivalently. It is also possible, however, # to pass a model object itself to +form_for+. For example, if @post # is an existing record you wish to edit, you can create the form using # # <%= form_for @post do |f| %> # ... # <% end %> # # This behaves in almost the same way as outlined previously, with a # couple of small exceptions. First, the prefix used to name the input # elements within the form (hence the key that denotes them in the +params+ # hash) is actually derived from the object's _class_, e.g. params[:post] # if the object's class is +Post+. However, this can be overwritten using # the :as option, e.g. - # # <%= form_for(@person, as: :client) do |f| %> # ... # <% end %> # # would result in params[:client]. # # Secondly, the field values shown when the form is initially displayed # are taken from the attributes of the object passed to +form_for+, # regardless of whether the object is an instance # variable. So, for example, if we had a _local_ variable +post+ # representing an existing record, # # <%= form_for post do |f| %> # ... # <% end %> # # would produce a form with fields whose initial state reflect the current # values of the attributes of +post+. # # === Resource-oriented style # # In the examples just shown, although not indicated explicitly, we still # need to use the :url option in order to specify where the # form is going to be sent. However, further simplification is possible # if the record passed to +form_for+ is a _resource_, i.e. it corresponds # to a set of RESTful routes, e.g. defined using the +resources+ method # in config/routes.rb. In this case Rails will simply infer the # appropriate URL from the record itself. For example, # # <%= form_for @post do |f| %> # ... # <% end %> # # is then equivalent to something like: # # <%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %> # ... # <% end %> # # And for a new record # # <%= form_for(Post.new) do |f| %> # ... # <% end %> # # is equivalent to something like: # # <%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %> # ... # <% end %> # # However you can still overwrite individual conventions, such as: # # <%= form_for(@post, url: super_posts_path) do |f| %> # ... # <% end %> # # You can also set the answer format, like this: # # <%= form_for(@post, format: :json) do |f| %> # ... # <% end %> # # For namespaced routes, like +admin_post_url+: # # <%= form_for([:admin, @post]) do |f| %> # ... # <% end %> # # If your resource has associations defined, for example, you want to add comments # to the document given that the routes are set correctly: # # <%= form_for([@document, @comment]) do |f| %> # ... # <% end %> # # Where @document = Document.find(params[:id]) and # @comment = Comment.new. # # === Setting the method # # You can force the form to use the full array of HTTP verbs by setting # # method: (:get|:post|:patch|:put|:delete) # # in the options hash. If the verb is not GET or POST, which are natively # supported by HTML forms, the form will be set to POST and a hidden input # called _method will carry the intended verb for the server to interpret. # # === Unobtrusive JavaScript # # Specifying: # # remote: true # # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor. # Even though it's using JavaScript to serialize the form elements, the form submission will work just like # a regular submission as viewed by the receiving side (all elements available in params). # # Example: # # <%= form_for(@post, remote: true) do |f| %> # ... # <% end %> # # The HTML generated for this would be: # #
    # # ... #
    # # === Setting HTML options # # You can set data attributes directly by passing in a data hash, but all other HTML options must be wrapped in # the HTML key. Example: # # <%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %> # ... # <% end %> # # The HTML generated for this would be: # #
    # # ... #
    # # === Removing hidden model id's # # The form_for method automatically includes the model id as a hidden field in the form. # This is used to maintain the correlation between the form data and its associated model. # Some ORM systems do not use IDs on nested models so in this case you want to be able # to disable the hidden id. # # In the following example the Post model has many Comments stored within it in a NoSQL database, # thus there is no primary key for comments. # # Example: # # <%= form_for(@post) do |f| %> # <%= f.fields_for(:comments, include_id: false) do |cf| %> # ... # <% end %> # <% end %> # # === Customized form builders # # You can also build forms using a customized FormBuilder class. Subclass # FormBuilder and override or define some more helpers, then use your # custom builder. For example, let's say you made a helper to # automatically add labels to form inputs. # # <%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %> # <%= f.text_field :first_name %> # <%= f.text_field :last_name %> # <%= f.text_area :biography %> # <%= f.check_box :admin %> # <%= f.submit %> # <% end %> # # In this case, if you use this: # # <%= render f %> # # The rendered template is people/_labelling_form and the local # variable referencing the form builder is called # labelling_form. # # The custom FormBuilder class is automatically merged with the options # of a nested fields_for call, unless it's explicitly set. # # In many cases you will want to wrap the above in another helper, so you # could do something like the following: # # def labelled_form_for(record_or_name_or_array, *args, &block) # options = args.extract_options! # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block) # end # # If you don't need to attach a form to a model instance, then check out # FormTagHelper#form_tag. # # === Form to external resources # # When you build forms to external resources sometimes you need to set an authenticity token or just render a form # without it, for example when you submit data to a payment gateway number and types of fields could be limited. # # To set an authenticity token you need to pass an :authenticity_token parameter # # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| # ... # <% end %> # # If you don't want to an authenticity token field be rendered at all just pass false: # # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| # ... # <% end %> def form_for(record, options = {}, &block) raise ArgumentError, "Missing block" unless block_given? html_options = options[:html] ||= {} case record when String, Symbol object_name = record object = nil else object = record.is_a?(Array) ? record.last : record raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object object_name = options[:as] || model_name_from_record_or_class(object).param_key apply_form_for_options!(record, object, options) end html_options[:data] = options.delete(:data) if options.has_key?(:data) html_options[:remote] = options.delete(:remote) if options.has_key?(:remote) html_options[:method] = options.delete(:method) if options.has_key?(:method) html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8) html_options[:authenticity_token] = options.delete(:authenticity_token) builder = instantiate_builder(object_name, object, options) output = capture(builder, &block) html_options[:multipart] ||= builder.multipart? html_options = html_options_for_form(options[:url] || {}, html_options) form_tag_with_body(html_options, output) end def apply_form_for_options!(record, object, options) #:nodoc: object = convert_to_model(object) as = options[:as] namespace = options[:namespace] action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post] options[:html].reverse_merge!( class: as ? "#{action}_#{as}" : dom_class(object, action), id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence, method: method ) options[:url] ||= if options.key?(:format) polymorphic_path(record, format: options.delete(:format)) else polymorphic_path(record, {}) end end private :apply_form_for_options! # Creates a scope around a specific model object like form_for, but # doesn't create the form tags themselves. This makes fields_for suitable # for specifying additional model objects in the same form. # # Although the usage and purpose of +fields_for+ is similar to +form_for+'s, # its method signature is slightly different. Like +form_for+, it yields # a FormBuilder object associated with a particular model object to a block, # and within the block allows methods to be called on the builder to # generate fields associated with the model object. Fields may reflect # a model object in two ways - how they are named (hence how submitted # values appear within the +params+ hash in the controller) and what # default values are shown when the form the fields appear in is first # displayed. In order for both of these features to be specified independently, # both an object name (represented by either a symbol or string) and the # object itself can be passed to the method separately - # # <%= form_for @person do |person_form| %> # First name: <%= person_form.text_field :first_name %> # Last name : <%= person_form.text_field :last_name %> # # <%= fields_for :permission, @person.permission do |permission_fields| %> # Admin? : <%= permission_fields.check_box :admin %> # <% end %> # # <%= person_form.submit %> # <% end %> # # In this case, the checkbox field will be represented by an HTML +input+ # tag with the +name+ attribute permission[admin], and the submitted # value will appear in the controller as params[:permission][:admin]. # If @person.permission is an existing record with an attribute # +admin+, the initial state of the checkbox when first displayed will # reflect the value of @person.permission.admin. # # Often this can be simplified by passing just the name of the model # object to +fields_for+ - # # <%= fields_for :permission do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # # ...in which case, if :permission also happens to be the name of an # instance variable @permission, the initial state of the input # field will reflect the value of that variable's attribute @permission.admin. # # Alternatively, you can pass just the model object itself (if the first # argument isn't a string or symbol +fields_for+ will realize that the # name has been omitted) - # # <%= fields_for @person.permission do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # # and +fields_for+ will derive the required name of the field from the # _class_ of the model object, e.g. if @person.permission, is # of class +Permission+, the field will still be named permission[admin]. # # Note: This also works for the methods in FormOptionHelper and # DateHelper that are designed to work with an object as base, like # FormOptionHelper#collection_select and DateHelper#datetime_select. # # === Nested Attributes Examples # # When the object belonging to the current scope has a nested attribute # writer for a certain attribute, fields_for will yield a new scope # for that attribute. This allows you to create forms that set or change # the attributes of a parent object and its associations in one go. # # Nested attribute writers are normal setter methods named after an # association. The most common way of defining these writers is either # with +accepts_nested_attributes_for+ in a model definition or by # defining a method with the proper name. For example: the attribute # writer for the association :address is called # address_attributes=. # # Whether a one-to-one or one-to-many style form builder will be yielded # depends on whether the normal reader method returns a _single_ object # or an _array_ of objects. # # ==== One-to-one # # Consider a Person class which returns a _single_ Address from the # address reader method and responds to the # address_attributes= writer method: # # class Person # def address # @address # end # # def address_attributes=(attributes) # # Process the attributes hash # end # end # # This model can now be used with a nested fields_for, like so: # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :address do |address_fields| %> # Street : <%= address_fields.text_field :street %> # Zip code: <%= address_fields.text_field :zip_code %> # <% end %> # ... # <% end %> # # When address is already an association on a Person you can use # +accepts_nested_attributes_for+ to define the writer method for you: # # class Person < ActiveRecord::Base # has_one :address # accepts_nested_attributes_for :address # end # # If you want to destroy the associated model through the form, you have # to enable it first using the :allow_destroy option for # +accepts_nested_attributes_for+: # # class Person < ActiveRecord::Base # has_one :address # accepts_nested_attributes_for :address, allow_destroy: true # end # # Now, when you use a form element with the _destroy parameter, # with a value that evaluates to +true+, you will destroy the associated # model (eg. 1, '1', true, or 'true'): # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :address do |address_fields| %> # ... # Delete: <%= address_fields.check_box :_destroy %> # <% end %> # ... # <% end %> # # ==== One-to-many # # Consider a Person class which returns an _array_ of Project instances # from the projects reader method and responds to the # projects_attributes= writer method: # # class Person # def projects # [@project1, @project2] # end # # def projects_attributes=(attributes) # # Process the attributes hash # end # end # # Note that the projects_attributes= writer method is in fact # required for fields_for to correctly identify :projects as a # collection, and the correct indices to be set in the form markup. # # When projects is already an association on Person you can use # +accepts_nested_attributes_for+ to define the writer method for you: # # class Person < ActiveRecord::Base # has_many :projects # accepts_nested_attributes_for :projects # end # # This model can now be used with a nested fields_for. The block given to # the nested fields_for call will be repeated for each instance in the # collection: # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :projects do |project_fields| %> # <% if project_fields.object.active? %> # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> # ... # <% end %> # # It's also possible to specify the instance to be used: # # <%= form_for @person do |person_form| %> # ... # <% @person.projects.each do |project| %> # <% if project.active? %> # <%= person_form.fields_for :projects, project do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> # <% end %> # ... # <% end %> # # Or a collection to be used: # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> # ... # <% end %> # # If you want to destroy any of the associated models through the # form, you have to enable it first using the :allow_destroy # option for +accepts_nested_attributes_for+: # # class Person < ActiveRecord::Base # has_many :projects # accepts_nested_attributes_for :projects, allow_destroy: true # end # # This will allow you to specify which models to destroy in the # attributes hash by adding a form element for the _destroy # parameter with a value that evaluates to +true+ # (eg. 1, '1', true, or 'true'): # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :projects do |project_fields| %> # Delete: <%= project_fields.check_box :_destroy %> # <% end %> # ... # <% end %> # # When a collection is used you might want to know the index of each # object into the array. For this purpose, the index method # is available in the FormBuilder object. # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :projects do |project_fields| %> # Project #<%= project_fields.index %> # ... # <% end %> # ... # <% end %> # # Note that fields_for will automatically generate a hidden field # to store the ID of the record. There are circumstances where this # hidden field is not needed and you can pass include_id: false # to prevent fields_for from rendering it automatically. def fields_for(record_name, record_object = nil, options = {}, &block) builder = instantiate_builder(record_name, record_object, options) capture(builder, &block) end # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation # is found in the current I18n locale (through helpers.label..) or you specify it explicitly. # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged # onto the HTML as an HTML element attribute as in the example shown, except for the :value option, which is designed to # target labels for radio_button tags (where the value is used in the ID of the input tag). # # ==== Examples # label(:post, :title) # # => # # You can localize your labels based on model and attribute names. # For example you can define the following in your locale (e.g. en.yml) # # helpers: # label: # post: # body: "Write your entire text here" # # Which then will result in # # label(:post, :body) # # => # # Localization can also be based purely on the translation of the attribute-name # (if you are using ActiveRecord): # # activerecord: # attributes: # post: # cost: "Total cost" # # label(:post, :cost) # # => # # label(:post, :title, "A short title") # # => # # label(:post, :title, "A short title", class: "title_label") # # => # # label(:post, :privacy, "Public Post", value: "public") # # => # # label(:post, :terms) do # 'Accept Terms.'.html_safe # end # # => def label(object_name, method, content_or_options = nil, options = nil, &block) Tags::Label.new(object_name, method, self, content_or_options, options).render(&block) end # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example # shown. # # ==== Examples # text_field(:post, :title, size: 20) # # => # # text_field(:post, :title, class: "create_input") # # => # # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }") # # => # # text_field(:snippet, :code, size: 20, class: 'code_input') # # => def text_field(object_name, method, options = {}) Tags::TextField.new(object_name, method, self, options).render end # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example # shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired. # # ==== Examples # password_field(:login, :pass, size: 20) # # => # # password_field(:account, :secret, class: "form_input", value: @account.secret) # # => # # password_field(:user, :password, onchange: "if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }") # # => # # password_field(:account, :pin, size: 20, class: 'form_input') # # => def password_field(object_name, method, options = {}) Tags::PasswordField.new(object_name, method, self, options).render end # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example # shown. # # ==== Examples # hidden_field(:signup, :pass_confirm) # # => # # hidden_field(:post, :tag_list) # # => # # hidden_field(:user, :token) # # => def hidden_field(object_name, method, options = {}) Tags::HiddenField.new(object_name, method, self, options).render end # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example # shown. # # Using this method inside a +form_for+ block will set the enclosing form's encoding to multipart/form-data. # # ==== Options # * Creates standard HTML attributes for the tag. # * :disabled - If set to true, the user will not be able to use this input. # * :multiple - If set to true, *in most updated browsers* the user will be allowed to select multiple files. # * :accept - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. # # ==== Examples # file_field(:user, :avatar) # # => # # file_field(:post, :image, :multiple => true) # # => # # file_field(:post, :attached, accept: 'text/html') # # => # # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') # # => # # file_field(:attachment, :file, class: 'file_input') # # => def file_field(object_name, method, options = {}) Tags::FileField.new(object_name, method, self, options).render end # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+) # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a # hash with +options+. # # ==== Examples # text_area(:post, :body, cols: 20, rows: 40) # # => # # text_area(:comment, :text, size: "20x30") # # => # # text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input') # # => # # text_area(:entry, :body, size: "20x20", disabled: 'disabled') # # => def text_area(object_name, method, options = {}) Tags::TextArea.new(object_name, method, self, options).render end # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object. # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked. # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1 # while the default +unchecked_value+ is set to 0 which is convenient for boolean values. # # ==== Gotcha # # The HTML specification says unchecked check boxes are not successful, and # thus web browsers do not send them. Unfortunately this introduces a gotcha: # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid # invoice the user unchecks its check box, no +paid+ parameter is sent. So, # any mass-assignment idiom like # # @invoice.update(params[:invoice]) # # wouldn't update the flag. # # To prevent this the helper generates an auxiliary hidden field before # the very check box. The hidden field has the same name and its # attributes mimic an unchecked check box. # # This way, the client either sends only the hidden field (representing # the check box is unchecked), or both fields. Since the HTML specification # says key/value pairs have to be sent in the same order they appear in the # form, and parameters extraction gets the last occurrence of any repeated # key in the query string, that works for ordinary forms. # # Unfortunately that workaround does not work when the check box goes # within an array-like parameter, as in # # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> # <%= form.check_box :paid %> # ... # <% end %> # # because parameter name repetition is precisely what Rails seeks to distinguish # the elements of the array. For each item with a checked check box you # get an extra ghost item with only that attribute, assigned to "0". # # In that case it is preferable to either use +check_box_tag+ or to use # hashes instead of arrays. # # # Let's say that @post.validated? is 1: # check_box("post", "validated") # # => # # # # # Let's say that @puppy.gooddog is "no": # check_box("puppy", "gooddog", {}, "yes", "no") # # => # # # # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no") # # => # # def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render end # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the # radio button will be checked. # # To force the radio button to be checked pass checked: true in the # +options+ hash. You may pass HTML options there as well. # # # Let's say that @post.category returns "rails": # radio_button("post", "category", "rails") # radio_button("post", "category", "java") # # => # # # # radio_button("user", "receive_newsletter", "yes") # radio_button("user", "receive_newsletter", "no") # # => # # def radio_button(object_name, method, tag_value, options = {}) Tags::RadioButton.new(object_name, method, self, tag_value, options).render end # Returns a text_field of type "color". # # color_field("car", "color") # # => def color_field(object_name, method, options = {}) Tags::ColorField.new(object_name, method, self, options).render end # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by # some browsers. # # search_field(:user, :name) # # => # search_field(:user, :name, autosave: false) # # => # search_field(:user, :name, results: 3) # # => # # Assume request.host returns "www.example.com" # search_field(:user, :name, autosave: true) # # => # search_field(:user, :name, onsearch: true) # # => # search_field(:user, :name, autosave: false, onsearch: true) # # => # search_field(:user, :name, autosave: true, onsearch: true) # # => def search_field(object_name, method, options = {}) Tags::SearchField.new(object_name, method, self, options).render end # Returns a text_field of type "tel". # # telephone_field("user", "phone") # # => # def telephone_field(object_name, method, options = {}) Tags::TelField.new(object_name, method, self, options).render end # aliases telephone_field alias phone_field telephone_field # Returns a text_field of type "date". # # date_field("user", "born_on") # # => # # The default value is generated by trying to call "to_date" # on the object's value, which makes it behave as expected for instances # of DateTime and ActiveSupport::TimeWithZone. You can still override that # by passing the "value" option explicitly, e.g. # # @user.born_on = Date.new(1984, 1, 27) # date_field("user", "born_on", value: "1984-05-12") # # => # # You can create values for the "min" and "max" attributes by passing # instances of Date or Time to the options hash. # # date_field("user", "born_on", min: Date.today) # # => # # Alternatively, you can pass a String formatted as an ISO8601 date as the # values for "min" and "max." # # date_field("user", "born_on", min: "2014-05-20") # # => # def date_field(object_name, method, options = {}) Tags::DateField.new(object_name, method, self, options).render end # Returns a text_field of type "time". # # The default value is generated by trying to call +strftime+ with "%T.%L" # on the objects's value. It is still possible to override that # by passing the "value" option. # # === Options # * Accepts same options as time_field_tag # # === Example # time_field("task", "started_at") # # => # # You can create values for the "min" and "max" attributes by passing # instances of Date or Time to the options hash. # # time_field("task", "started_at", min: Time.now) # # => # # Alternatively, you can pass a String formatted as an ISO8601 time as the # values for "min" and "max." # # time_field("task", "started_at", min: "01:00:00") # # => # def time_field(object_name, method, options = {}) Tags::TimeField.new(object_name, method, self, options).render end # Returns a text_field of type "datetime". # # datetime_field("user", "born_on") # # => # # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z" # on the object's value, which makes it behave as expected for instances # of DateTime and ActiveSupport::TimeWithZone. # # @user.born_on = Date.new(1984, 1, 12) # datetime_field("user", "born_on") # # => # # You can create values for the "min" and "max" attributes by passing # instances of Date or Time to the options hash. # # datetime_field("user", "born_on", min: Date.today) # # => # # Alternatively, you can pass a String formatted as an ISO8601 datetime # with UTC offset as the values for "min" and "max." # # datetime_field("user", "born_on", min: "2014-05-20T00:00:00+0000") # # => # def datetime_field(object_name, method, options = {}) Tags::DatetimeField.new(object_name, method, self, options).render end # Returns a text_field of type "datetime-local". # # datetime_local_field("user", "born_on") # # => # # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T" # on the object's value, which makes it behave as expected for instances # of DateTime and ActiveSupport::TimeWithZone. # # @user.born_on = Date.new(1984, 1, 12) # datetime_local_field("user", "born_on") # # => # # You can create values for the "min" and "max" attributes by passing # instances of Date or Time to the options hash. # # datetime_local_field("user", "born_on", min: Date.today) # # => # # Alternatively, you can pass a String formatted as an ISO8601 datetime as # the values for "min" and "max." # # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00") # # => # def datetime_local_field(object_name, method, options = {}) Tags::DatetimeLocalField.new(object_name, method, self, options).render end # Returns a text_field of type "month". # # month_field("user", "born_on") # # => # # The default value is generated by trying to call +strftime+ with "%Y-%m" # on the object's value, which makes it behave as expected for instances # of DateTime and ActiveSupport::TimeWithZone. # # @user.born_on = Date.new(1984, 1, 27) # month_field("user", "born_on") # # => # def month_field(object_name, method, options = {}) Tags::MonthField.new(object_name, method, self, options).render end # Returns a text_field of type "week". # # week_field("user", "born_on") # # => # # The default value is generated by trying to call +strftime+ with "%Y-W%W" # on the object's value, which makes it behave as expected for instances # of DateTime and ActiveSupport::TimeWithZone. # # @user.born_on = Date.new(1984, 5, 12) # week_field("user", "born_on") # # => # def week_field(object_name, method, options = {}) Tags::WeekField.new(object_name, method, self, options).render end # Returns a text_field of type "url". # # url_field("user", "homepage") # # => # def url_field(object_name, method, options = {}) Tags::UrlField.new(object_name, method, self, options).render end # Returns a text_field of type "email". # # email_field("user", "address") # # => # def email_field(object_name, method, options = {}) Tags::EmailField.new(object_name, method, self, options).render end # Returns an input tag of type "number". # # ==== Options # * Accepts same options as number_field_tag def number_field(object_name, method, options = {}) Tags::NumberField.new(object_name, method, self, options).render end # Returns an input tag of type "range". # # ==== Options # * Accepts same options as range_field_tag def range_field(object_name, method, options = {}) Tags::RangeField.new(object_name, method, self, options).render end private def instantiate_builder(record_name, record_object, options) case record_name when String, Symbol object = record_object object_name = record_name else object = record_name object_name = model_name_from_record_or_class(object).param_key end builder = options[:builder] || default_form_builder_class builder.new(object_name, object, self, options) end def default_form_builder_class builder = ActionView::Base.default_form_builder builder.respond_to?(:constantize) ? builder.constantize : builder end end # A +FormBuilder+ object is associated with a particular model object and # allows you to generate fields associated with the model object. The # +FormBuilder+ object is yielded when using +form_for+ or +fields_for+. # For example: # # <%= form_for @person do |person_form| %> # Name: <%= person_form.text_field :name %> # Admin: <%= person_form.check_box :admin %> # <% end %> # # In the above block, a +FormBuilder+ object is yielded as the # +person_form+ variable. This allows you to generate the +text_field+ # and +check_box+ fields by specifying their eponymous methods, which # modify the underlying template and associates the +@person+ model object # with the form. # # The +FormBuilder+ object can be thought of as serving as a proxy for the # methods in the +FormHelper+ module. This class, however, allows you to # call methods with the model object you are building the form for. # # You can create your own custom FormBuilder templates by subclassing this # class. For example: # # class MyFormBuilder < ActionView::Helpers::FormBuilder # def div_radio_button(method, tag_value, options = {}) # @template.content_tag(:div, # @template.radio_button( # @object_name, method, tag_value, objectify_options(options) # ) # ) # end # end # # The above code creates a new method +div_radio_button+ which wraps a div # around the new radio button. Note that when options are passed in, you # must call +objectify_options+ in order for the model object to get # correctly passed to the method. If +objectify_options+ is not called, # then the newly created helper will not be linked back to the model. # # The +div_radio_button+ code from above can now be used as follows: # # <%= form_for @person, :builder => MyFormBuilder do |f| %> # I am a child: <%= f.div_radio_button(:admin, "child") %> # I am an adult: <%= f.div_radio_button(:admin, "adult") %> # <% end -%> # # The standard set of helper methods for form building are located in the # +field_helpers+ class attribute. class FormBuilder include ModelNaming # The methods which wrap a form helper call. class_attribute :field_helpers self.field_helpers = [:fields_for, :label, :text_field, :password_field, :hidden_field, :file_field, :text_area, :check_box, :radio_button, :color_field, :search_field, :telephone_field, :phone_field, :date_field, :time_field, :datetime_field, :datetime_local_field, :month_field, :week_field, :url_field, :email_field, :number_field, :range_field] attr_accessor :object_name, :object, :options attr_reader :multipart, :index alias :multipart? :multipart def multipart=(multipart) @multipart = multipart if parent_builder = @options[:parent_builder] parent_builder.multipart = multipart end end def self._to_partial_path @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '') end def to_partial_path self.class._to_partial_path end def to_model self end def initialize(object_name, object, template, options) @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options @default_options = @options ? @options.slice(:index, :namespace) : {} if @object_name.to_s.match(/\[\]$/) if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) @auto_index = object.to_param else raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" end end @multipart = nil @index = options[:index] || options[:child_index] end (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{selector}(method, options = {}) # def text_field(method, options = {}) @template.send( # @template.send( #{selector.inspect}, # "text_field", @object_name, # @object_name, method, # method, objectify_options(options)) # objectify_options(options)) end # end RUBY_EVAL end # Creates a scope around a specific model object like form_for, but # doesn't create the form tags themselves. This makes fields_for suitable # for specifying additional model objects in the same form. # # Although the usage and purpose of +fields_for+ is similar to +form_for+'s, # its method signature is slightly different. Like +form_for+, it yields # a FormBuilder object associated with a particular model object to a block, # and within the block allows methods to be called on the builder to # generate fields associated with the model object. Fields may reflect # a model object in two ways - how they are named (hence how submitted # values appear within the +params+ hash in the controller) and what # default values are shown when the form the fields appear in is first # displayed. In order for both of these features to be specified independently, # both an object name (represented by either a symbol or string) and the # object itself can be passed to the method separately - # # <%= form_for @person do |person_form| %> # First name: <%= person_form.text_field :first_name %> # Last name : <%= person_form.text_field :last_name %> # # <%= fields_for :permission, @person.permission do |permission_fields| %> # Admin? : <%= permission_fields.check_box :admin %> # <% end %> # # <%= person_form.submit %> # <% end %> # # In this case, the checkbox field will be represented by an HTML +input+ # tag with the +name+ attribute permission[admin], and the submitted # value will appear in the controller as params[:permission][:admin]. # If @person.permission is an existing record with an attribute # +admin+, the initial state of the checkbox when first displayed will # reflect the value of @person.permission.admin. # # Often this can be simplified by passing just the name of the model # object to +fields_for+ - # # <%= fields_for :permission do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # # ...in which case, if :permission also happens to be the name of an # instance variable @permission, the initial state of the input # field will reflect the value of that variable's attribute @permission.admin. # # Alternatively, you can pass just the model object itself (if the first # argument isn't a string or symbol +fields_for+ will realize that the # name has been omitted) - # # <%= fields_for @person.permission do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # # and +fields_for+ will derive the required name of the field from the # _class_ of the model object, e.g. if @person.permission, is # of class +Permission+, the field will still be named permission[admin]. # # Note: This also works for the methods in FormOptionHelper and # DateHelper that are designed to work with an object as base, like # FormOptionHelper#collection_select and DateHelper#datetime_select. # # === Nested Attributes Examples # # When the object belonging to the current scope has a nested attribute # writer for a certain attribute, fields_for will yield a new scope # for that attribute. This allows you to create forms that set or change # the attributes of a parent object and its associations in one go. # # Nested attribute writers are normal setter methods named after an # association. The most common way of defining these writers is either # with +accepts_nested_attributes_for+ in a model definition or by # defining a method with the proper name. For example: the attribute # writer for the association :address is called # address_attributes=. # # Whether a one-to-one or one-to-many style form builder will be yielded # depends on whether the normal reader method returns a _single_ object # or an _array_ of objects. # # ==== One-to-one # # Consider a Person class which returns a _single_ Address from the # address reader method and responds to the # address_attributes= writer method: # # class Person # def address # @address # end # # def address_attributes=(attributes) # # Process the attributes hash # end # end # # This model can now be used with a nested fields_for, like so: # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :address do |address_fields| %> # Street : <%= address_fields.text_field :street %> # Zip code: <%= address_fields.text_field :zip_code %> # <% end %> # ... # <% end %> # # When address is already an association on a Person you can use # +accepts_nested_attributes_for+ to define the writer method for you: # # class Person < ActiveRecord::Base # has_one :address # accepts_nested_attributes_for :address # end # # If you want to destroy the associated model through the form, you have # to enable it first using the :allow_destroy option for # +accepts_nested_attributes_for+: # # class Person < ActiveRecord::Base # has_one :address # accepts_nested_attributes_for :address, allow_destroy: true # end # # Now, when you use a form element with the _destroy parameter, # with a value that evaluates to +true+, you will destroy the associated # model (eg. 1, '1', true, or 'true'): # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :address do |address_fields| %> # ... # Delete: <%= address_fields.check_box :_destroy %> # <% end %> # ... # <% end %> # # ==== One-to-many # # Consider a Person class which returns an _array_ of Project instances # from the projects reader method and responds to the # projects_attributes= writer method: # # class Person # def projects # [@project1, @project2] # end # # def projects_attributes=(attributes) # # Process the attributes hash # end # end # # Note that the projects_attributes= writer method is in fact # required for fields_for to correctly identify :projects as a # collection, and the correct indices to be set in the form markup. # # When projects is already an association on Person you can use # +accepts_nested_attributes_for+ to define the writer method for you: # # class Person < ActiveRecord::Base # has_many :projects # accepts_nested_attributes_for :projects # end # # This model can now be used with a nested fields_for. The block given to # the nested fields_for call will be repeated for each instance in the # collection: # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :projects do |project_fields| %> # <% if project_fields.object.active? %> # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> # ... # <% end %> # # It's also possible to specify the instance to be used: # # <%= form_for @person do |person_form| %> # ... # <% @person.projects.each do |project| %> # <% if project.active? %> # <%= person_form.fields_for :projects, project do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> # <% end %> # ... # <% end %> # # Or a collection to be used: # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> # ... # <% end %> # # If you want to destroy any of the associated models through the # form, you have to enable it first using the :allow_destroy # option for +accepts_nested_attributes_for+: # # class Person < ActiveRecord::Base # has_many :projects # accepts_nested_attributes_for :projects, allow_destroy: true # end # # This will allow you to specify which models to destroy in the # attributes hash by adding a form element for the _destroy # parameter with a value that evaluates to +true+ # (eg. 1, '1', true, or 'true'): # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :projects do |project_fields| %> # Delete: <%= project_fields.check_box :_destroy %> # <% end %> # ... # <% end %> # # When a collection is used you might want to know the index of each # object into the array. For this purpose, the index method # is available in the FormBuilder object. # # <%= form_for @person do |person_form| %> # ... # <%= person_form.fields_for :projects do |project_fields| %> # Project #<%= project_fields.index %> # ... # <% end %> # ... # <% end %> # # Note that fields_for will automatically generate a hidden field # to store the ID of the record. There are circumstances where this # hidden field is not needed and you can pass include_id: false # to prevent fields_for from rendering it automatically. def fields_for(record_name, record_object = nil, fields_options = {}, &block) fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? fields_options[:builder] ||= options[:builder] fields_options[:namespace] = options[:namespace] fields_options[:parent_builder] = self case record_name when String, Symbol if nested_attributes_association?(record_name) return fields_for_with_nested_attributes(record_name, record_object, fields_options, block) end else record_object = record_name.is_a?(Array) ? record_name.last : record_name record_name = model_name_from_record_or_class(record_object).param_key end index = if options.has_key?(:index) options[:index] elsif defined?(@auto_index) self.object_name = @object_name.to_s.sub(/\[\]$/,"") @auto_index end record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]" fields_options[:child_index] = index @template.fields_for(record_name, record_object, fields_options, &block) end # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation # is found in the current I18n locale (through helpers.label..) or you specify it explicitly. # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged # onto the HTML as an HTML element attribute as in the example shown, except for the :value option, which is designed to # target labels for radio_button tags (where the value is used in the ID of the input tag). # # ==== Examples # label(:post, :title) # # => # # You can localize your labels based on model and attribute names. # For example you can define the following in your locale (e.g. en.yml) # # helpers: # label: # post: # body: "Write your entire text here" # # Which then will result in # # label(:post, :body) # # => # # Localization can also be based purely on the translation of the attribute-name # (if you are using ActiveRecord): # # activerecord: # attributes: # post: # cost: "Total cost" # # label(:post, :cost) # # => # # label(:post, :title, "A short title") # # => # # label(:post, :title, "A short title", class: "title_label") # # => # # label(:post, :privacy, "Public Post", value: "public") # # => # # label(:post, :terms) do # 'Accept Terms.'.html_safe # end def label(method, text = nil, options = {}, &block) @template.label(@object_name, method, text, objectify_options(options), &block) end # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object. # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked. # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1 # while the default +unchecked_value+ is set to 0 which is convenient for boolean values. # # ==== Gotcha # # The HTML specification says unchecked check boxes are not successful, and # thus web browsers do not send them. Unfortunately this introduces a gotcha: # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid # invoice the user unchecks its check box, no +paid+ parameter is sent. So, # any mass-assignment idiom like # # @invoice.update(params[:invoice]) # # wouldn't update the flag. # # To prevent this the helper generates an auxiliary hidden field before # the very check box. The hidden field has the same name and its # attributes mimic an unchecked check box. # # This way, the client either sends only the hidden field (representing # the check box is unchecked), or both fields. Since the HTML specification # says key/value pairs have to be sent in the same order they appear in the # form, and parameters extraction gets the last occurrence of any repeated # key in the query string, that works for ordinary forms. # # Unfortunately that workaround does not work when the check box goes # within an array-like parameter, as in # # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> # <%= form.check_box :paid %> # ... # <% end %> # # because parameter name repetition is precisely what Rails seeks to distinguish # the elements of the array. For each item with a checked check box you # get an extra ghost item with only that attribute, assigned to "0". # # In that case it is preferable to either use +check_box_tag+ or to use # hashes instead of arrays. # # # Let's say that @post.validated? is 1: # check_box("post", "validated") # # => # # # # # Let's say that @puppy.gooddog is "no": # check_box("puppy", "gooddog", {}, "yes", "no") # # => # # # # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no") # # => # # def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value) end # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the # radio button will be checked. # # To force the radio button to be checked pass checked: true in the # +options+ hash. You may pass HTML options there as well. # # # Let's say that @post.category returns "rails": # radio_button("post", "category", "rails") # radio_button("post", "category", "java") # # => # # # # radio_button("user", "receive_newsletter", "yes") # radio_button("user", "receive_newsletter", "no") # # => # # def radio_button(method, tag_value, options = {}) @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example # shown. # # ==== Examples # hidden_field(:signup, :pass_confirm) # # => # # hidden_field(:post, :tag_list) # # => # # hidden_field(:user, :token) # # => # def hidden_field(method, options = {}) @emitted_hidden_id = true if method == :id @template.hidden_field(@object_name, method, objectify_options(options)) end # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example # shown. # # Using this method inside a +form_for+ block will set the enclosing form's encoding to multipart/form-data. # # ==== Options # * Creates standard HTML attributes for the tag. # * :disabled - If set to true, the user will not be able to use this input. # * :multiple - If set to true, *in most updated browsers* the user will be allowed to select multiple files. # * :accept - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. # # ==== Examples # file_field(:user, :avatar) # # => # # file_field(:post, :image, :multiple => true) # # => # # file_field(:post, :attached, accept: 'text/html') # # => # # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') # # => # # file_field(:attachment, :file, class: 'file_input') # # => def file_field(method, options = {}) self.multipart = true @template.file_field(@object_name, method, objectify_options(options)) end # Add the submit button for the given form. When no value is given, it checks # if the object is a new resource or not to create the proper label: # # <%= form_for @post do |f| %> # <%= f.submit %> # <% end %> # # In the example above, if @post is a new record, it will use "Create Post" as # submit button label, otherwise, it uses "Update Post". # # Those labels can be customized using I18n, under the helpers.submit key and accept # the %{model} as translation interpolation: # # en: # helpers: # submit: # create: "Create a %{model}" # update: "Confirm changes to %{model}" # # It also searches for a key specific for the given object: # # en: # helpers: # submit: # post: # create: "Add %{model}" # def submit(value=nil, options={}) value, options = nil, value if value.is_a?(Hash) value ||= submit_default_value @template.submit_tag(value, options) end # Add the submit button for the given form. When no value is given, it checks # if the object is a new resource or not to create the proper label: # # <%= form_for @post do |f| %> # <%= f.button %> # <% end %> # # In the example above, if @post is a new record, it will use "Create Post" as # button label, otherwise, it uses "Update Post". # # Those labels can be customized using I18n, under the helpers.submit key # (the same as submit helper) and accept the %{model} as translation interpolation: # # en: # helpers: # submit: # create: "Create a %{model}" # update: "Confirm changes to %{model}" # # It also searches for a key specific for the given object: # # en: # helpers: # submit: # post: # create: "Add %{model}" # # ==== Examples # button("Create a post") # # => # # button do # content_tag(:strong, 'Ask me!') # end # # => # def button(value = nil, options = {}, &block) value, options = nil, value if value.is_a?(Hash) value ||= submit_default_value @template.button_tag(value, options, &block) end def emitted_hidden_id? @emitted_hidden_id ||= nil end private def objectify_options(options) @default_options.merge(options.merge(object: @object)) end def submit_default_value object = convert_to_model(@object) key = object ? (object.persisted? ? :update : :create) : :submit model = if object.respond_to?(:model_name) object.model_name.human else @object_name.to_s.humanize end defaults = [] defaults << :"helpers.submit.#{object_name}.#{key}" defaults << :"helpers.submit.#{key}" defaults << "#{key.to_s.humanize} #{model}" I18n.t(defaults.shift, model: model, default: defaults) end def nested_attributes_association?(association_name) @object.respond_to?("#{association_name}_attributes=") end def fields_for_with_nested_attributes(association_name, association, options, block) name = "#{object_name}[#{association_name}_attributes]" association = convert_to_model(association) if association.respond_to?(:persisted?) association = [association] if @object.send(association_name).respond_to?(:to_ary) elsif !association.respond_to?(:to_ary) association = @object.send(association_name) end if association.respond_to?(:to_ary) explicit_child_index = options[:child_index] output = ActiveSupport::SafeBuffer.new association.each do |child| options[:child_index] = nested_child_index(name) unless explicit_child_index output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block) end output elsif association fields_for_nested_model(name, association, options, block) end end def fields_for_nested_model(name, object, fields_options, block) object = convert_to_model(object) emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) { options.fetch(:include_id, true) } @template.fields_for(name, object, fields_options) do |f| output = @template.capture(f, &block) output.concat f.hidden_field(:id) if output && emit_hidden_id && !f.emitted_hidden_id? output end end def nested_child_index(name) @nested_child_index[name] ||= -1 @nested_child_index[name] += 1 end end end ActiveSupport.on_load(:action_view) do cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do ::ActionView::Helpers::FormBuilder end end end rails-4.2.6/actionview/lib/action_view/helpers/form_options_helper.rb000066400000000000000000001226151266740050600261430ustar00rootroot00000000000000require 'cgi' require 'erb' require 'action_view/helpers/form_helper' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/wrap' module ActionView # = Action View Form Option Helpers module Helpers # Provides a number of methods for turning different kinds of containers into a set of option tags. # # The collection_select, select and time_zone_select methods take an options parameter, a hash: # # * :include_blank - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element. # # select("post", "category", Post::CATEGORIES, {include_blank: true}) # # could become: # # # # Another common case is a select tag for a belongs_to-associated object. # # Example with @post.person_id => 2: # # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'}) # # could become: # # # # * :prompt - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string. # # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'}) # # could become: # # # # * :index - like the other form helpers, +select+ can accept an :index option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this # option to be in the +html_options+ parameter. # # select("album[]", "genre", %w[rap rock country], {}, { index: nil }) # # becomes: # # # # * :disabled - can be a single value or an array of values that will be disabled options in the final output. # # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'}) # # could become: # # # # When used with the collection_select helper, :disabled can also be a Proc that identifies those options that should be disabled. # # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }}) # # If the categories "2008 stuff" and "Christmas" return true when the method archived? is called, this would return: # # module FormOptionsHelper # ERB::Util can mask some helpers like textilize. Make sure to include them. include TextHelper # Create a select tag and a series of contained option tags for the provided object and method. # The option currently held by the object will be selected, provided that the object is available. # # There are two possible formats for the +choices+ parameter, corresponding to other helpers' output: # # * A flat collection (see +options_for_select+). # # * A nested collection (see +grouped_options_for_select+). # # For example: # # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }) # # would become: # # # # assuming the associated person has ID 1. # # This can be used to provide a default set of options in the standard way: before rendering the create form, a # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved # to the database. Instead, a second model object is created when the create request is received. # This allows the user to submit a form page more than once with the expected results of creating multiple records. # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms. # # By default, post.person_id is the selected option. Specify selected: value to use a different selection # or selected: nil to leave all options unselected. Similarly, you can specify values to be disabled in the option # tags by specifying the :disabled option. This can either be a single value or an array of values to be disabled. # # A block can be passed to +select+ to customize how the options tags will be rendered. This # is useful when the options tag has complex attributes. # # select(report, "campaign_ids") do # available_campaigns.each do |c| # content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json }) # end # end # # ==== Gotcha # # The HTML specification says when +multiple+ parameter passed to select and all options got deselected # web browsers do not send any value to server. Unfortunately this introduces a gotcha: # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So, # any mass-assignment idiom like # # @user.update(params[:user]) # # wouldn't update roles. # # To prevent this the helper generates an auxiliary hidden field before # every multiple select. The hidden field has the same name as multiple select and blank value. # # Note: The client either sends only the hidden field (representing # the deselected multiple select box), or both fields. This means that the resulting array # always contains a blank string. # # In case if you don't want the helper to generate this hidden field you can specify # include_hidden: false option. # def select(object, method, choices = nil, options = {}, html_options = {}, &block) Tags::Select.new(object, method, self, choices, options, html_options, &block).render end # Returns # # # # # def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {}) Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render end # Returns # # # # # # # # # # def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render end # Returns select and option tags for the given object and method, using # #time_zone_options_for_select to generate the list of option tags. # # In addition to the :include_blank option documented above, # this method also supports a :model option, which defaults # to ActiveSupport::TimeZone. This may be used by users to specify a # different time zone model object. (See +time_zone_options_for_select+ # for more information.) # # You can also supply an array of ActiveSupport::TimeZone objects # as +priority_zones+, so that they will be listed above the rest of the # (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience # for obtaining a list of the US time zones, or a Regexp to select the zones # of your choice) # # Finally, this method supports a :default option, which selects # a default ActiveSupport::TimeZone if the object's time zone is +nil+. # # time_zone_select( "user", "time_zone", nil, include_blank: true) # # time_zone_select( "user", "time_zone", nil, default: "Pacific Time (US & Canada)" ) # # time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, default: "Pacific Time (US & Canada)") # # time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ]) # # time_zone_select( "user", 'time_zone', /Australia/) # # time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone) def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {}) Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render end # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+ # may also be an array of values to be selected when using a multiple select. # # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]]) # # => # # => # # options_for_select([ "VISA", "MasterCard" ], "MasterCard") # # => # # => # # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40") # # => # # => # # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"]) # # => # # => # # => # # You can optionally provide HTML attributes as the last element of the array. # # options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"]) # # => # # => # # => # # options_for_select([["Dollar", "$", {class: "bold"}], ["Kroner", "DKK", {onclick: "alert('HI');"}]]) # # => # # => # # If you wish to specify disabled option tags, set +selected+ to be a hash, with :disabled being either a value # or array of values to be disabled. In this case, you can use :selected to specify selected option tags. # # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: "Super Platinum") # # => # # => # # => # # => # # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: ["Advanced", "Super Platinum"]) # # => # # => # # => # # => # # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], selected: "Free", disabled: "Super Platinum") # # => # # => # # => # # => # # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. def options_for_select(container, selected = nil) return container if String === container selected, disabled = extract_selected_and_disabled(selected).map do |r| Array(r).map { |item| item.to_s } end container.map do |element| html_attributes = option_html_attributes(element) text, value = option_text_and_value(element).map { |item| item.to_s } html_attributes[:selected] ||= option_value_selected?(value, selected) html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled) html_attributes[:value] = value content_tag_string(:option, text, html_attributes) end.join("\n").html_safe end # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. # # options_from_collection_for_select(@people, 'id', 'name') # # => # # This is more often than not used inside a #select_tag like this example: # # select_tag 'person', options_from_collection_for_select(@people, 'id', 'name') # # If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+ # will be selected option tag(s). # # If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous # function are the selected values. # # +selected+ can also be a hash, specifying both :selected and/or :disabled values as required. # # Be sure to specify the same class as the +value_method+ when specifying selected or disabled options. # Failure to do this will produce undesired results. Example: # options_from_collection_for_select(@people, 'id', 'name', '1') # Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string) # options_from_collection_for_select(@people, 'id', 'name', 1) # should produce the desired results. def options_from_collection_for_select(collection, value_method, text_method, selected = nil) options = collection.map do |element| [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)] end selected, disabled = extract_selected_and_disabled(selected) select_deselect = { selected: extract_values_from_collection(collection, value_method, selected), disabled: extract_values_from_collection(collection, value_method, disabled) } options_for_select(options, select_deselect) end # Returns a string of tags, like options_from_collection_for_select, but # groups them by tags based on the object relationships of the arguments. # # Parameters: # * +collection+ - An array of objects representing the tags. # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an # array of child objects representing the tags. # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a # string to be used as the +label+ attribute for its tag. # * +option_key_method+ - The name of a method which, when called on a child object of a member of # +collection+, returns a value to be used as the +value+ attribute for its tag. # * +option_value_method+ - The name of a method which, when called on a child object of a member of # +collection+, returns a value to be used as the contents of its tag. # * +selected_key+ - A value equal to the +value+ attribute for one of the tags, # which will have the +selected+ attribute set. Corresponds to the return value of one of the calls # to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are # to be specified. # # Example object structure for use with this method: # # class Continent < ActiveRecord::Base # has_many :countries # # attribs: id, name # end # # class Country < ActiveRecord::Base # belongs_to :continent # # attribs: id, name, continent_id # end # # Sample usage: # option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3) # # Possible output: # # # # ... # # # # # # ... # # # Note: Only the and tags are returned, so you still have to # wrap the output in an appropriate tag. def grouped_options_for_select(grouped_options, selected_key = nil, options = {}) prompt = options[:prompt] divider = options[:divider] body = "".html_safe if prompt body.safe_concat content_tag(:option, prompt_text(prompt), value: "") end grouped_options.each do |container| html_attributes = option_html_attributes(container) if divider label = divider else label, container = container end html_attributes = { label: label }.merge!(html_attributes) body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), html_attributes) end body end # Returns a string of option tags for pretty much any time zone in the # world. Supply a ActiveSupport::TimeZone name as +selected+ to have it # marked as the selected option tag. You can also supply an array of # ActiveSupport::TimeZone objects as +priority_zones+, so that they will # be listed above the rest of the (long) list. (You can use # ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list # of the US time zones, or a Regexp to select the zones of your choice) # # The +selected+ parameter must be either +nil+, or a string that names # a ActiveSupport::TimeZone. # # By default, +model+ is the ActiveSupport::TimeZone constant (which can # be obtained in Active Record as a value object). The only requirement # is that the +model+ parameter be an object that responds to +all+, and # returns an array of objects that represent time zones. # # NOTE: Only the option tags are returned, you have to wrap this call in # a regular HTML select tag. def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone) zone_options = "".html_safe zones = model.all convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } } if priority_zones if priority_zones.is_a?(Regexp) priority_zones = zones.select { |z| z =~ priority_zones } end zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected) zone_options.safe_concat content_tag(:option, '-------------', value: '', disabled: true) zone_options.safe_concat "\n" zones = zones - priority_zones end zone_options.safe_concat options_for_select(convert_zones[zones], selected) end # Returns radio button tags for the collection of existing return values # of +method+ for +object+'s class. The value returned from calling # +method+ on the instance +object+ will be selected. If calling +method+ # returns +nil+, no selection is made. # # The :value_method and :text_method parameters are # methods to be called on each member of +collection+. The return values # are used as the +value+ attribute and contents of each radio button tag, # respectively. They can also be any object that responds to +call+, such # as a +proc+, that will be called for each member of the +collection+ to # retrieve the value/text. # # Example object structure for use with this method: # class Post < ActiveRecord::Base # belongs_to :author # end # class Author < ActiveRecord::Base # has_many :posts # def name_with_initial # "#{first_name.first}. #{last_name}" # end # end # # Sample usage (selecting the associated Author for an instance of Post, @post): # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) # # If @post.author_id is already 1, this would return: # # # # # # # # It is also possible to customize the way the elements will be shown by # giving a block to the method: # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| # b.label { b.radio_button } # end # # The argument passed to the block is a special kind of builder for this # collection, which has the ability to generate the label and radio button # for the current item in the collection, with proper text and value. # Using it, you can change the label and radio button display order or # even use the label as wrapper, as in the example above. # # The builder methods label and radio_button also accept # extra HTML options: # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| # b.label(class: "radio_button") { b.radio_button(class: "radio_button") } # end # # There are also three special methods available: object, text and # value, which are the current item being rendered, its text and value methods, # respectively. You can use them like this: # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| # b.label(:"data-value" => b.value) { b.radio_button + b.text } # end def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block) end # Returns check box tags for the collection of existing return values of # +method+ for +object+'s class. The value returned from calling +method+ # on the instance +object+ will be selected. If calling +method+ returns # +nil+, no selection is made. # # The :value_method and :text_method parameters are # methods to be called on each member of +collection+. The return values # are used as the +value+ attribute and contents of each check box tag, # respectively. They can also be any object that responds to +call+, such # as a +proc+, that will be called for each member of the +collection+ to # retrieve the value/text. # # Example object structure for use with this method: # class Post < ActiveRecord::Base # has_and_belongs_to_many :authors # end # class Author < ActiveRecord::Base # has_and_belongs_to_many :posts # def name_with_initial # "#{first_name.first}. #{last_name}" # end # end # # Sample usage (selecting the associated Author for an instance of Post, @post): # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) # # If @post.author_ids is already [1], this would return: # # # # # # # # # It is also possible to customize the way the elements will be shown by # giving a block to the method: # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| # b.label { b.check_box } # end # # The argument passed to the block is a special kind of builder for this # collection, which has the ability to generate the label and check box # for the current item in the collection, with proper text and value. # Using it, you can change the label and check box display order or even # use the label as wrapper, as in the example above. # # The builder methods label and check_box also accept # extra HTML options: # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| # b.label(class: "check_box") { b.check_box(class: "check_box") } # end # # There are also three special methods available: object, text and # value, which are the current item being rendered, its text and value methods, # respectively. You can use them like this: # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| # b.label(:"data-value" => b.value) { b.check_box + b.text } # end def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block) end private def option_html_attributes(element) if Array === element element.select { |e| Hash === e }.reduce({}, :merge!) else {} end end def option_text_and_value(option) # Options are [text, value] pairs or strings used for both. if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last) option = option.reject { |e| Hash === e } if Array === option [option.first, option.last] else [option, option] end end def option_value_selected?(value, selected) Array(selected).include? value end def extract_selected_and_disabled(selected) if selected.is_a?(Proc) [selected, nil] else selected = Array.wrap(selected) options = selected.extract_options!.symbolize_keys selected_items = options.fetch(:selected, selected) [selected_items, options[:disabled]] end end def extract_values_from_collection(collection, value_method, selected) if selected.is_a?(Proc) collection.map do |element| element.send(value_method) if selected.call(element) end.compact else selected end end def value_for_collection(item, value) value.respond_to?(:call) ? value.call(item) : item.send(value) end def prompt_text(prompt) prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', default: 'Please select') end end class FormBuilder # Wraps ActionView::Helpers::FormOptionsHelper#select for form builders: # # <%= form_for @post do |f| %> # <%= f.select :person_id, Person.all.collect { |p| [ p.name, p.id ] }, include_blank: true %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def select(method, choices = nil, options = {}, html_options = {}, &block) @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block) end # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders: # # <%= form_for @post do |f| %> # <%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) end # Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders: # # <%= form_for @city do |f| %> # <%= f.grouped_collection_select :country_id, @continents, :countries, :name, :id, :name %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options)) end # Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders: # # <%= form_for @user do |f| %> # <%= f.time_zone_select :time_zone, nil, include_blank: true %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end # Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders: # # <%= form_for @post do |f| %> # <%= f.collection_check_boxes :author_ids, Author.all, :id, :name_with_initial %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block) @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) end # Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders: # # <%= form_for @post do |f| %> # <%= f.collection_radio_buttons :author_id, Author.all, :id, :name_with_initial %> # <%= f.submit %> # <% end %> # # Please refer to the documentation of the base helper for details. def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block) @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) end end end end rails-4.2.6/actionview/lib/action_view/helpers/form_tag_helper.rb000066400000000000000000001217501266740050600252220ustar00rootroot00000000000000require 'cgi' require 'action_view/helpers/tag_helper' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/module/attribute_accessors' module ActionView # = Action View Form Tag Helpers module Helpers # Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like # FormHelper does. Instead, you provide the names and values manually. # # NOTE: The HTML options disabled, readonly, and multiple can all be treated as booleans. So specifying # disabled: true will give disabled="disabled". module FormTagHelper extend ActiveSupport::Concern include UrlHelper include TextHelper mattr_accessor :embed_authenticity_token_in_remote_forms self.embed_authenticity_token_in_remote_forms = false # Starts a form tag that points the action to an url configured with url_for_options just like # ActionController::Base#url_for. The method for the form defaults to POST. # # ==== Options # * :multipart - If set to true, the enctype is set to "multipart/form-data". # * :method - The method to use when submitting the form, usually either "get" or "post". # If "patch", "put", "delete", or another verb is used, a hidden input with name _method # is added to simulate the verb over post. # * :authenticity_token - Authenticity token to use in the form. Use only if you need to # pass custom authenticity token string, or to not add authenticity_token field at all # (by passing false). Remote forms may omit the embedded authenticity token # by setting config.action_view.embed_authenticity_token_in_remote_forms = false. # This is helpful when you're fragment-caching the form. Remote forms get the # authenticity token from the meta tag, so embedding is unnecessary unless you # support browsers without JavaScript. # * :remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the # submit behavior. By default this behavior is an ajax submit. # * :enforce_utf8 - If set to false, a hidden input with name utf8 is not output. # * Any other key creates standard HTML attributes for the tag. # # ==== Examples # form_tag('/posts') # # =>
    # # form_tag('/posts/1', method: :put) # # => ... ... # # form_tag('/upload', multipart: true) # # => # # <%= form_tag('/posts') do -%> #
    <%= submit_tag 'Save' %>
    # <% end -%> # # =>
    # # <%= form_tag('/posts', remote: true) %> # # =>
    # # form_tag('http://far.away.com/form', authenticity_token: false) # # form without authenticity token # # form_tag('http://far.away.com/form', authenticity_token: "cf50faa3fe97702ca1ae") # # form with custom authenticity token # def form_tag(url_for_options = {}, options = {}, &block) html_options = html_options_for_form(url_for_options, options) if block_given? form_tag_with_body(html_options, capture(&block)) else form_tag_html(html_options) end end # Creates a dropdown selection box, or if the :multiple option is set to true, a multiple # choice selection box. # # Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or # associated records. option_tags is a string containing the option tags for the select box. # # ==== Options # * :multiple - If set to true the selection will allow multiple choices. # * :disabled - If set to true, the user will not be able to use this input. # * :include_blank - If set to true, an empty option will be created. If set to a string, the string will be used as the option's content and the value will be empty. # * :prompt - Create a prompt option with blank value and the text asking user to select something. # * Any other key creates standard HTML attributes for the tag. # # ==== Examples # select_tag "people", options_from_collection_for_select(@people, "id", "name") # # # # select_tag "people", options_from_collection_for_select(@people, "id", "name", "1") # # # # select_tag "people", "".html_safe # # => # # select_tag "count", "".html_safe # # => # # select_tag "colors", "".html_safe, multiple: true # # => # # select_tag "locations", "".html_safe # # => # # select_tag "access", "".html_safe, multiple: true, class: 'form_input', id: 'unique_id' # # => # # select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: true # # => # # select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: "All" # # => # # select_tag "people", options_from_collection_for_select(@people, "id", "name"), prompt: "Select something" # # => # # select_tag "destination", "".html_safe, disabled: true # # => # # select_tag "credit_card", options_for_select([ "VISA", "MasterCard" ], "MasterCard") # # => def select_tag(name, option_tags = nil, options = {}) option_tags ||= "" html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name if options.include?(:include_blank) include_blank = options.delete(:include_blank) if include_blank == true include_blank = '' end if include_blank option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags) end end if prompt = options.delete(:prompt) option_tags = content_tag(:option, prompt, value: '').safe_concat(option_tags) end content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) end # Creates a standard text field; use these text fields to input smaller chunks of text like a username # or a search query. # # ==== Options # * :disabled - If set to true, the user will not be able to use this input. # * :size - The number of visible characters that will fit in the input. # * :maxlength - The maximum number of characters that the browser will allow the user to enter. # * :placeholder - The text contained in the field by default which is removed when the field receives focus. # * Any other key creates standard HTML attributes for the tag. # # ==== Examples # text_field_tag 'name' # # => # # text_field_tag 'query', 'Enter your search query here' # # => # # text_field_tag 'search', nil, placeholder: 'Enter search term...' # # => # # text_field_tag 'request', nil, class: 'special_input' # # => # # text_field_tag 'address', '', size: 75 # # => # # text_field_tag 'zip', nil, maxlength: 5 # # => # # text_field_tag 'payment_amount', '$0.00', disabled: true # # => # # text_field_tag 'ip', '0.0.0.0', maxlength: 15, size: 20, class: "ip-input" # # => def text_field_tag(name, value = nil, options = {}) tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys) end # Creates a label element. Accepts a block. # # ==== Options # * Creates standard HTML attributes for the tag. # # ==== Examples # label_tag 'name' # # => # # label_tag 'name', 'Your name' # # => # # label_tag 'name', nil, class: 'small_label' # # => def label_tag(name = nil, content_or_options = nil, options = nil, &block) if block_given? && content_or_options.is_a?(Hash) options = content_or_options = content_or_options.stringify_keys else options ||= {} options = options.stringify_keys end options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for") content_tag :label, content_or_options || name.to_s.humanize, options, &block end # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or # data that should be hidden from the user. # # ==== Options # * Creates standard HTML attributes for the tag. # # ==== Examples # hidden_field_tag 'tags_list' # # => # # hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@' # # => # # hidden_field_tag 'collected_input', '', onchange: "alert('Input collected!')" # # => def hidden_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :hidden)) end # Creates a file upload field. If you are using file uploads then you will also need # to set the multipart option for the form tag: # # <%= form_tag '/upload', multipart: true do %> # <%= file_field_tag "file" %> # <%= submit_tag %> # <% end %> # # The specified URL will then be passed a File object containing the selected file, or if the field # was left blank, a StringIO object. # # ==== Options # * Creates standard HTML attributes for the tag. # * :disabled - If set to true, the user will not be able to use this input. # * :multiple - If set to true, *in most updated browsers* the user will be allowed to select multiple files. # * :accept - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. # # ==== Examples # file_field_tag 'attachment' # # => # # file_field_tag 'avatar', class: 'profile_input' # # => # # file_field_tag 'picture', disabled: true # # => # # file_field_tag 'resume', value: '~/resume.doc' # # => # # file_field_tag 'user_pic', accept: 'image/png,image/gif,image/jpeg' # # => # # file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html' # # => def file_field_tag(name, options = {}) text_field_tag(name, nil, options.merge(type: :file)) end # Creates a password field, a masked text field that will hide the users input behind a mask character. # # ==== Options # * :disabled - If set to true, the user will not be able to use this input. # * :size - The number of visible characters that will fit in the input. # * :maxlength - The maximum number of characters that the browser will allow the user to enter. # * Any other key creates standard HTML attributes for the tag. # # ==== Examples # password_field_tag 'pass' # # => # # password_field_tag 'secret', 'Your secret here' # # => # # password_field_tag 'masked', nil, class: 'masked_input_field' # # => # # password_field_tag 'token', '', size: 15 # # => # # password_field_tag 'key', nil, maxlength: 16 # # => # # password_field_tag 'confirm_pass', nil, disabled: true # # => # # password_field_tag 'pin', '1234', maxlength: 4, size: 6, class: "pin_input" # # => def password_field_tag(name = "password", value = nil, options = {}) text_field_tag(name, value, options.merge(type: :password)) end # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions. # # ==== Options # * :size - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10"). # * :rows - Specify the number of rows in the textarea # * :cols - Specify the number of columns in the textarea # * :disabled - If set to true, the user will not be able to use this input. # * :escape - By default, the contents of the text input are HTML escaped. # If you need unescaped contents, set this to false. # * Any other key creates standard HTML attributes for the tag. # # ==== Examples # text_area_tag 'post' # # => # # text_area_tag 'bio', @user.bio # # => # # text_area_tag 'body', nil, rows: 10, cols: 25 # # => # # text_area_tag 'body', nil, size: "25x10" # # => # # text_area_tag 'description', "Description goes here.", disabled: true # # => # # text_area_tag 'comment', nil, class: 'comment_input' # # => def text_area_tag(name, content = nil, options = {}) options = options.stringify_keys if size = options.delete("size") options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) end escape = options.delete("escape") { true } content = ERB::Util.html_escape(content) if escape content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options) end # Creates a check box form input tag. # # ==== Options # * :disabled - If set to true, the user will not be able to use this input. # * Any other key creates standard HTML options for the tag. # # ==== Examples # check_box_tag 'accept' # # => # # check_box_tag 'rock', 'rock music' # # => # # check_box_tag 'receive_email', 'yes', true # # => # # check_box_tag 'tos', 'yes', false, class: 'accept_tos' # # => # # check_box_tag 'eula', 'accepted', false, disabled: true # # => def check_box_tag(name, value = "1", checked = false, options = {}) html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys) html_options["checked"] = "checked" if checked tag :input, html_options end # Creates a radio button; use groups of radio buttons named the same to allow users to # select from a group of options. # # ==== Options # * :disabled - If set to true, the user will not be able to use this input. # * Any other key creates standard HTML options for the tag. # # ==== Examples # radio_button_tag 'gender', 'male' # # => # # radio_button_tag 'receive_updates', 'no', true # # => # # radio_button_tag 'time_slot', "3:00 p.m.", false, disabled: true # # => # # radio_button_tag 'color', "green", true, class: "color_input" # # => def radio_button_tag(name, value, checked = false, options = {}) html_options = { "type" => "radio", "name" => name, "id" => "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}", "value" => value }.update(options.stringify_keys) html_options["checked"] = "checked" if checked tag :input, html_options end # Creates a submit button with the text value as the caption. # # ==== Options # * :data - This option can be used to add custom data attributes. # * :disabled - If true, the user will not be able to use this input. # * Any other key creates standard HTML options for the tag. # # ==== Data attributes # # * confirm: 'question?' - If present the unobtrusive JavaScript # drivers will provide a prompt with the question specified. If the user accepts, # the form is processed normally, otherwise no action is taken. # * :disable_with - Value of this parameter will be used as the value for a # disabled version of the submit button when the form is submitted. This feature is # provided by the unobtrusive JavaScript driver. # # ==== Examples # submit_tag # # => # # submit_tag "Edit this article" # # => # # submit_tag "Save edits", disabled: true # # => # # submit_tag "Complete sale", data: { disable_with: "Please wait..." } # # => # # submit_tag nil, class: "form_submit" # # => # # submit_tag "Edit", class: "edit_button" # # => # # submit_tag "Save", data: { confirm: "Are you sure?" } # # => # def submit_tag(value = "Save changes", options = {}) options = options.stringify_keys tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options) end # Creates a button element that defines a submit button, # resetbutton or a generic button which can be used in # JavaScript, for example. You can use the button tag as a regular # submit tag but it isn't supported in legacy browsers. However, # the button tag allows richer labels such as images and emphasis, # so this helper will also accept a block. # # ==== Options # * :data - This option can be used to add custom data attributes. # * :disabled - If true, the user will not be able to # use this input. # * Any other key creates standard HTML options for the tag. # # ==== Data attributes # # * confirm: 'question?' - If present, the # unobtrusive JavaScript drivers will provide a prompt with # the question specified. If the user accepts, the form is # processed normally, otherwise no action is taken. # * :disable_with - Value of this parameter will be # used as the value for a disabled version of the submit # button when the form is submitted. This feature is provided # by the unobtrusive JavaScript driver. # # ==== Examples # button_tag # # => # # button_tag(type: 'button') do # content_tag(:strong, 'Ask me!') # end # # => # # button_tag "Checkout", data: { disable_with: "Please wait..." } # # => # def button_tag(content_or_options = nil, options = nil, &block) if content_or_options.is_a? Hash options = content_or_options else options ||= {} end options = { 'name' => 'button', 'type' => 'submit' }.merge!(options.stringify_keys) if block_given? content_tag :button, options, &block else content_tag :button, content_or_options || 'Button', options end end # Displays an image which when clicked will submit the form. # # source is passed to AssetTagHelper#path_to_image # # ==== Options # * :data - This option can be used to add custom data attributes. # * :disabled - If set to true, the user will not be able to use this input. # * Any other key creates standard HTML options for the tag. # # ==== Data attributes # # * confirm: 'question?' - This will add a JavaScript confirm # prompt with the question specified. If the user accepts, the form is # processed normally, otherwise no action is taken. # # ==== Examples # image_submit_tag("login.png") # # => # # image_submit_tag("purchase.png", disabled: true) # # => # # image_submit_tag("search.png", class: 'search_button', alt: 'Find') # # => # # image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button") # # => # # image_submit_tag("save.png", data: { confirm: "Are you sure?" }) # # => def image_submit_tag(source, options = {}) options = options.stringify_keys tag :input, { "alt" => image_alt(source), "type" => "image", "src" => path_to_image(source) }.update(options) end # Creates a field set for grouping HTML form elements. # # legend will become the fieldset's title (optional as per W3C). # options accept the same values as tag. # # ==== Examples # <%= field_set_tag do %> #

    <%= text_field_tag 'name' %>

    # <% end %> # # =>

    # # <%= field_set_tag 'Your details' do %> #

    <%= text_field_tag 'name' %>

    # <% end %> # # =>
    Your details

    # # <%= field_set_tag nil, class: 'format' do %> #

    <%= text_field_tag 'name' %>

    # <% end %> # # =>

    def field_set_tag(legend = nil, options = nil, &block) output = tag(:fieldset, options, true) output.safe_concat(content_tag(:legend, legend)) unless legend.blank? output.concat(capture(&block)) if block_given? output.safe_concat("") end # Creates a text field of type "color". # # ==== Options # * Accepts the same options as text_field_tag. # # ==== Examples # color_field_tag 'name' # # => # # color_field_tag 'color', '#DEF726' # # => # # color_field_tag 'color', nil, class: 'special_input' # # => # # color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true # # => def color_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :color)) end # Creates a text field of type "search". # # ==== Options # * Accepts the same options as text_field_tag. # # ==== Examples # search_field_tag 'name' # # => # # search_field_tag 'search', 'Enter your search query here' # # => # # search_field_tag 'search', nil, class: 'special_input' # # => # # search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true # # => def search_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :search)) end # Creates a text field of type "tel". # # ==== Options # * Accepts the same options as text_field_tag. # # ==== Examples # telephone_field_tag 'name' # # => # # telephone_field_tag 'tel', '0123456789' # # => # # telephone_field_tag 'tel', nil, class: 'special_input' # # => # # telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true # # => def telephone_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :tel)) end alias phone_field_tag telephone_field_tag # Creates a text field of type "date". # # ==== Options # * Accepts the same options as text_field_tag. # # ==== Examples # date_field_tag 'name' # # => # # date_field_tag 'date', '01/01/2014' # # => # # date_field_tag 'date', nil, class: 'special_input' # # => # # date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true # # => def date_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :date)) end # Creates a text field of type "time". # # === Options # * :min - The minimum acceptable value. # * :max - The maximum acceptable value. # * :step - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def time_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :time)) end # Creates a text field of type "datetime". # # === Options # * :min - The minimum acceptable value. # * :max - The maximum acceptable value. # * :step - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def datetime_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :datetime)) end # Creates a text field of type "datetime-local". # # === Options # * :min - The minimum acceptable value. # * :max - The maximum acceptable value. # * :step - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def datetime_local_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: 'datetime-local')) end # Creates a text field of type "month". # # === Options # * :min - The minimum acceptable value. # * :max - The maximum acceptable value. # * :step - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def month_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :month)) end # Creates a text field of type "week". # # === Options # * :min - The minimum acceptable value. # * :max - The maximum acceptable value. # * :step - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def week_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :week)) end # Creates a text field of type "url". # # ==== Options # * Accepts the same options as text_field_tag. # # ==== Examples # url_field_tag 'name' # # => # # url_field_tag 'url', 'http://rubyonrails.org' # # => # # url_field_tag 'url', nil, class: 'special_input' # # => # # url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true # # => def url_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :url)) end # Creates a text field of type "email". # # ==== Options # * Accepts the same options as text_field_tag. # # ==== Examples # email_field_tag 'name' # # => # # email_field_tag 'email', 'email@example.com' # # => # # email_field_tag 'email', nil, class: 'special_input' # # => # # email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true # # => def email_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.merge(type: :email)) end # Creates a number field. # # ==== Options # * :min - The minimum acceptable value. # * :max - The maximum acceptable value. # * :in - A range specifying the :min and # :max values. # * :within - Same as :in. # * :step - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. # # ==== Examples # number_field_tag 'quantity' # # => # # number_field_tag 'quantity', '1' # # => # # number_field_tag 'quantity', nil, class: 'special_input' # # => # # number_field_tag 'quantity', nil, min: 1 # # => # # number_field_tag 'quantity', nil, max: 9 # # => # # number_field_tag 'quantity', nil, in: 1...10 # # => # # number_field_tag 'quantity', nil, within: 1...10 # # => # # number_field_tag 'quantity', nil, min: 1, max: 10 # # => # # number_field_tag 'quantity', nil, min: 1, max: 10, step: 2 # # => # # number_field_tag 'quantity', '1', class: 'special_input', disabled: true # # => def number_field_tag(name, value = nil, options = {}) options = options.stringify_keys options["type"] ||= "number" if range = options.delete("in") || options.delete("within") options.update("min" => range.min, "max" => range.max) end text_field_tag(name, value, options) end # Creates a range form element. # # ==== Options # * Accepts the same options as number_field_tag. def range_field_tag(name, value = nil, options = {}) number_field_tag(name, value, options.merge(type: :range)) end # Creates the hidden UTF8 enforcer tag. Override this method in a helper # to customize the tag. def utf8_enforcer_tag # Use raw HTML to ensure the value is written as an HTML entity; it # needs to be the right character regardless of which encoding the # browser infers. ''.html_safe end private def html_options_for_form(url_for_options, options) options.stringify_keys.tap do |html_options| html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") # The following URL is unescaped, this is just a hash of options, and it is the # responsibility of the caller to escape all the values. html_options["action"] = url_for(url_for_options) html_options["accept-charset"] = "UTF-8" html_options["data-remote"] = true if html_options.delete("remote") if html_options["data-remote"] && !embed_authenticity_token_in_remote_forms && html_options["authenticity_token"].blank? # The authenticity token is taken from the meta tag in this case html_options["authenticity_token"] = false elsif html_options["authenticity_token"] == true # Include the default authenticity_token, which is only generated when its set to nil, # but we needed the true value to override the default of no authenticity_token on data-remote. html_options["authenticity_token"] = nil end end end def extra_tags_for_form(html_options) authenticity_token = html_options.delete("authenticity_token") method = html_options.delete("method").to_s method_tag = case method when /^get$/i # must be case-insensitive, but can't use downcase as might be nil html_options["method"] = "get" '' when /^post$/i, "", nil html_options["method"] = "post" token_tag(authenticity_token) else html_options["method"] = "post" method_tag(method) + token_tag(authenticity_token) end if html_options.delete("enforce_utf8") { true } utf8_enforcer_tag + method_tag else method_tag end end def form_tag_html(html_options) extra_tags = extra_tags_for_form(html_options) tag(:form, html_options, true) + extra_tags end def form_tag_with_body(html_options, content) output = form_tag_html(html_options) output << content output.safe_concat("
    ") end # see http://www.w3.org/TR/html4/types.html#type-name def sanitize_to_id(name) name.to_s.delete(']').tr('^-a-zA-Z0-9:.', "_") end end end end rails-4.2.6/actionview/lib/action_view/helpers/javascript_helper.rb000066400000000000000000000047611266740050600255740ustar00rootroot00000000000000require 'action_view/helpers/tag_helper' module ActionView module Helpers module JavaScriptHelper JS_ESCAPE_MAP = { '\\' => '\\\\', ' '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" } JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '
' JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '
' # Escapes carriage returns and single and double quotes for JavaScript segments. # # Also available through the alias j(). This is particularly helpful in JavaScript # responses, like: # # $('some_element').replaceWith('<%=j render 'some/element_template' %>'); def escape_javascript(javascript) if javascript result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] } javascript.html_safe? ? result.html_safe : result else '' end end alias_method :j, :escape_javascript # Returns a JavaScript tag with the +content+ inside. Example: # javascript_tag "alert('All is good')" # # Returns: # # # +html_options+ may be a hash of attributes for the \ # # Instead of passing the content as an argument, you can also use a block # in which case, you pass your +html_options+ as the first parameter. # # <%= javascript_tag defer: 'defer' do -%> # alert('All is good') # <% end -%> def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block) content = if block_given? html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) capture(&block) else content_or_options_with_block end content_tag(:script, javascript_cdata_section(content), html_options) end def javascript_cdata_section(content) #:nodoc: "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe end end end end rails-4.2.6/actionview/lib/action_view/helpers/number_helper.rb000066400000000000000000000511061266740050600247110ustar00rootroot00000000000000# encoding: utf-8 require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/string/output_safety' require 'active_support/number_helper' module ActionView # = Action View Number Helpers module Helpers #:nodoc: # Provides methods for converting numbers into formatted strings. # Methods are provided for phone numbers, currency, percentage, # precision, positional notation, file size and pretty printing. # # Most methods expect a +number+ argument, and will return it # unchanged if can't be converted into a valid number. module NumberHelper # Raised when argument +number+ param given to the helpers is invalid and # the option :raise is set to +true+. class InvalidNumberError < StandardError attr_accessor :number def initialize(number) @number = number end end # Formats a +number+ into a US phone number (e.g., (555) # 123-9876). You can customize the format in the +options+ hash. # # ==== Options # # * :area_code - Adds parentheses around the area code. # * :delimiter - Specifies the delimiter to use # (defaults to "-"). # * :extension - Specifies an extension to add to the # end of the generated number. # * :country_code - Sets the country code for the phone # number. # * :raise - If true, raises +InvalidNumberError+ when # the argument is invalid. # # ==== Examples # # number_to_phone(5551234) # => 555-1234 # number_to_phone("5551234") # => 555-1234 # number_to_phone(1235551234) # => 123-555-1234 # number_to_phone(1235551234, area_code: true) # => (123) 555-1234 # number_to_phone(1235551234, delimiter: " ") # => 123 555 1234 # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555 # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234 # number_to_phone("123a456") # => 123a456 # number_to_phone("1234a567", raise: true) # => InvalidNumberError # # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".") # # => +1.123.555.1234 x 1343 def number_to_phone(number, options = {}) return unless number options = options.symbolize_keys parse_float(number, true) if options.delete(:raise) ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options)) end # Formats a +number+ into a currency string (e.g., $13.65). You # can customize the format in the +options+ hash. # # ==== Options # # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :precision - Sets the level of precision (defaults # to 2). # * :unit - Sets the denomination of the currency # (defaults to "$"). # * :separator - Sets the separator between the units # (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults # to ","). # * :format - Sets the format for non-negative numbers # (defaults to "%u%n"). Fields are %u for the # currency, and %n for the number. # * :negative_format - Sets the format for negative # numbers (defaults to prepending an hyphen to the formatted # number given by :format). Accepts the same fields # than :format, except %n is here the # absolute value of the number. # * :raise - If true, raises +InvalidNumberError+ when # the argument is invalid. # # ==== Examples # # number_to_currency(1234567890.50) # => $1,234,567,890.50 # number_to_currency(1234567890.506) # => $1,234,567,890.51 # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506 # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 € # number_to_currency("123a456") # => $123a456 # # number_to_currency("123a456", raise: true) # => InvalidNumberError # # number_to_currency(-1234567890.50, negative_format: "(%u%n)") # # => ($1,234,567,890.50) # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "") # # => R$1234567890,50 # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u") # # => 1234567890,50 R$ def number_to_currency(number, options = {}) delegate_number_helper_method(:number_to_currency, number, options) end # Formats a +number+ as a percentage string (e.g., 65%). You can # customize the format in the +options+ hash. # # ==== Options # # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :precision - Sets the precision of the number # (defaults to 3). # * :significant - If +true+, precision will be the # # of significant_digits. If +false+, the # of fractional # digits (defaults to +false+). # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults # to ""). # * :strip_insignificant_zeros - If +true+ removes # insignificant zeros after the decimal separator (defaults to # +false+). # * :format - Specifies the format of the percentage # string The number field is %n (defaults to "%n%"). # * :raise - If true, raises +InvalidNumberError+ when # the argument is invalid. # # ==== Examples # # number_to_percentage(100) # => 100.000% # number_to_percentage("98") # => 98.000% # number_to_percentage(100, precision: 0) # => 100% # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% # number_to_percentage(1000, locale: :fr) # => 1 000,000% # number_to_percentage("98a") # => 98a% # number_to_percentage(100, format: "%n %") # => 100 % # # number_to_percentage("98a", raise: true) # => InvalidNumberError def number_to_percentage(number, options = {}) delegate_number_helper_method(:number_to_percentage, number, options) end # Formats a +number+ with grouped thousands using +delimiter+ # (e.g., 12,324). You can customize the format in the +options+ # hash. # # ==== Options # # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :delimiter - Sets the thousands delimiter (defaults # to ","). # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). # * :raise - If true, raises +InvalidNumberError+ when # the argument is invalid. # # ==== Examples # # number_with_delimiter(12345678) # => 12,345,678 # number_with_delimiter("123456") # => 123,456 # number_with_delimiter(12345678.05) # => 12,345,678.05 # number_with_delimiter(12345678, delimiter: ".") # => 12.345.678 # number_with_delimiter(12345678, delimiter: ",") # => 12,345,678 # number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05 # number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05 # number_with_delimiter("112a") # => 112a # number_with_delimiter(98765432.98, delimiter: " ", separator: ",") # # => 98 765 432,98 # # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError def number_with_delimiter(number, options = {}) delegate_number_helper_method(:number_to_delimited, number, options) end # Formats a +number+ with the specified level of # :precision (e.g., 112.32 has a precision of 2 if # +:significant+ is +false+, and 5 if +:significant+ is +true+). # You can customize the format in the +options+ hash. # # ==== Options # # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :precision - Sets the precision of the number # (defaults to 3). # * :significant - If +true+, precision will be the # # of significant_digits. If +false+, the # of fractional # digits (defaults to +false+). # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults # to ""). # * :strip_insignificant_zeros - If +true+ removes # insignificant zeros after the decimal separator (defaults to # +false+). # * :raise - If true, raises +InvalidNumberError+ when # the argument is invalid. # # ==== Examples # # number_with_precision(111.2345) # => 111.235 # number_with_precision(111.2345, precision: 2) # => 111.23 # number_with_precision(13, precision: 5) # => 13.00000 # number_with_precision(389.32314, precision: 0) # => 389 # number_with_precision(111.2345, significant: true) # => 111 # number_with_precision(111.2345, precision: 1, significant: true) # => 100 # number_with_precision(13, precision: 5, significant: true) # => 13.000 # number_with_precision(111.234, locale: :fr) # => 111,234 # # number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true) # # => 13 # # number_with_precision(389.32314, precision: 4, significant: true) # => 389.3 # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.') # # => 1.111,23 def number_with_precision(number, options = {}) delegate_number_helper_method(:number_to_rounded, number, options) end # Formats the bytes in +number+ into a more understandable # representation (e.g., giving it 1500 yields 1.5 KB). This # method is useful for reporting file sizes to users. You can # customize the format in the +options+ hash. # # See number_to_human if you want to pretty-print a # generic number. # # ==== Options # # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :precision - Sets the precision of the number # (defaults to 3). # * :significant - If +true+, precision will be the # # of significant_digits. If +false+, the # of fractional # digits (defaults to +true+) # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults # to ""). # * :strip_insignificant_zeros - If +true+ removes # insignificant zeros after the decimal separator (defaults to # +true+) # * :prefix - If +:si+ formats the number using the SI # prefix (defaults to :binary) # * :raise - If true, raises +InvalidNumberError+ when # the argument is invalid. # # ==== Examples # # number_to_human_size(123) # => 123 Bytes # number_to_human_size(1234) # => 1.21 KB # number_to_human_size(12345) # => 12.1 KB # number_to_human_size(1234567) # => 1.18 MB # number_to_human_size(1234567890) # => 1.15 GB # number_to_human_size(1234567890123) # => 1.12 TB # number_to_human_size(1234567, precision: 2) # => 1.2 MB # number_to_human_size(483989, precision: 2) # => 470 KB # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB" # number_to_human_size(524288000, precision: 5) # => "500 MB" def number_to_human_size(number, options = {}) delegate_number_helper_method(:number_to_human_size, number, options) end # Pretty prints (formats and approximates) a number in a way it # is more readable by humans (eg.: 1200000000 becomes "1.2 # Billion"). This is useful for numbers that can get very large # (and too hard to read). # # See number_to_human_size if you want to print a file # size. # # You can also define you own unit-quantifier names if you want # to use other decimal units (eg.: 1500 becomes "1.5 # kilometers", 0.150 becomes "150 milliliters", etc). You may # define a wide range of unit quantifiers, even fractional ones # (centi, deci, mili, etc). # # ==== Options # # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :precision - Sets the precision of the number # (defaults to 3). # * :significant - If +true+, precision will be the # # of significant_digits. If +false+, the # of fractional # digits (defaults to +true+) # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults # to ""). # * :strip_insignificant_zeros - If +true+ removes # insignificant zeros after the decimal separator (defaults to # +true+) # * :units - A Hash of unit quantifier names. Or a # string containing an i18n scope where to find this hash. It # might have the following keys: # * *integers*: :unit, :ten, # :hundred, :thousand, :million, # :billion, :trillion, # :quadrillion # * *fractionals*: :deci, :centi, # :mili, :micro, :nano, # :pico, :femto # * :format - Sets the format of the output string # (defaults to "%n %u"). The field types are: # * %u - The quantifier (ex.: 'thousand') # * %n - The number # * :raise - If true, raises +InvalidNumberError+ when # the argument is invalid. # # ==== Examples # # number_to_human(123) # => "123" # number_to_human(1234) # => "1.23 Thousand" # number_to_human(12345) # => "12.3 Thousand" # number_to_human(1234567) # => "1.23 Million" # number_to_human(1234567890) # => "1.23 Billion" # number_to_human(1234567890123) # => "1.23 Trillion" # number_to_human(1234567890123456) # => "1.23 Quadrillion" # number_to_human(1234567890123456789) # => "1230 Quadrillion" # number_to_human(489939, precision: 2) # => "490 Thousand" # number_to_human(489939, precision: 4) # => "489.9 Thousand" # number_to_human(1234567, precision: 4, # significant: false) # => "1.2346 Million" # number_to_human(1234567, precision: 1, # separator: ',', # significant: false) # => "1,2 Million" # # number_to_human(500000000, precision: 5) # => "500 Million" # number_to_human(12345012345, significant: false) # => "12.345 Billion" # # Non-significant zeros after the decimal separator are stripped # out by default (set :strip_insignificant_zeros to # +false+ to change that): # # number_to_human(12.00001) # => "12" # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0" # # ==== Custom Unit Quantifiers # # You can also use your own custom unit quantifiers: # number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt" # # If in your I18n locale you have: # distance: # centi: # one: "centimeter" # other: "centimeters" # unit: # one: "meter" # other: "meters" # thousand: # one: "kilometer" # other: "kilometers" # billion: "gazillion-distance" # # Then you could do: # # number_to_human(543934, units: :distance) # => "544 kilometers" # number_to_human(54393498, units: :distance) # => "54400 kilometers" # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance" # number_to_human(343, units: :distance, precision: 1) # => "300 meters" # number_to_human(1, units: :distance) # => "1 meter" # number_to_human(0.34, units: :distance) # => "34 centimeters" # def number_to_human(number, options = {}) delegate_number_helper_method(:number_to_human, number, options) end private def delegate_number_helper_method(method, number, options) return unless number options = escape_unsafe_options(options.symbolize_keys) wrap_with_output_safety_handling(number, options.delete(:raise)) { ActiveSupport::NumberHelper.public_send(method, number, options) } end def escape_unsafe_options(options) options[:format] = ERB::Util.html_escape(options[:format]) if options[:format] options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format] options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units] options end def escape_units(units) Hash[units.map do |k, v| [k, ERB::Util.html_escape(v)] end] end def wrap_with_output_safety_handling(number, raise_on_invalid, &block) valid_float = valid_float?(number) raise InvalidNumberError, number if raise_on_invalid && !valid_float formatted_number = yield if valid_float || number.html_safe? formatted_number.html_safe else formatted_number end end def valid_float?(number) !parse_float(number, false).nil? end def parse_float(number, raise_error) Float(number) rescue ArgumentError, TypeError raise InvalidNumberError, number if raise_error end end end end rails-4.2.6/actionview/lib/action_view/helpers/output_safety_helper.rb000066400000000000000000000025541266740050600263370ustar00rootroot00000000000000require 'active_support/core_ext/string/output_safety' module ActionView #:nodoc: # = Action View Raw Output Helper module Helpers #:nodoc: module OutputSafetyHelper # This method outputs without escaping a string. Since escaping tags is # now default, this can be used when you don't want Rails to automatically # escape tags. This is not recommended if the data is coming from the user's # input. # # For example: # # raw @user.name # # => 'Jimmy Tables' def raw(stringish) stringish.to_s.html_safe end # This method returns an HTML safe string similar to what Array#join # would return. The array is flattened, and all items, including # the supplied separator, are HTML escaped unless they are HTML # safe, and the returned string is marked as HTML safe. # # safe_join(["

    foo

    ".html_safe, "

    bar

    "], "
    ") # # => "

    foo

    <br /><p>bar</p>" # # safe_join(["

    foo

    ".html_safe, "

    bar

    ".html_safe], "
    ".html_safe) # # => "

    foo


    bar

    " # def safe_join(array, sep=$,) sep = ERB::Util.unwrapped_html_escape(sep) array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe end end end end rails-4.2.6/actionview/lib/action_view/helpers/record_tag_helper.rb000066400000000000000000000075321266740050600255360ustar00rootroot00000000000000require 'action_view/record_identifier' module ActionView # = Action View Record Tag Helpers module Helpers module RecordTagHelper include ActionView::RecordIdentifier # Produces a wrapper DIV element with id and class parameters that # relate to the specified Active Record object. Usage example: # # <%= div_for(@person, class: "foo") do %> # <%= @person.name %> # <% end %> # # produces: # #
    Joe Bloggs
    # # You can also pass an array of Active Record objects, which will then # get iterated over and yield each record as an argument for the block. # For example: # # <%= div_for(@people, class: "foo") do |person| %> # <%= person.name %> # <% end %> # # produces: # #
    Joe Bloggs
    #
    Jane Bloggs
    # def div_for(record, *args, &block) content_tag_for(:div, record, *args, &block) end # content_tag_for creates an HTML element with id and class parameters # that relate to the specified Active Record object. For example: # # <%= content_tag_for(:tr, @person) do %> # <%= @person.first_name %> # <%= @person.last_name %> # <% end %> # # would produce the following HTML (assuming @person is an instance of # a Person object, with an id value of 123): # # .... # # If you require the HTML id attribute to have a prefix, you can specify it: # # <%= content_tag_for(:tr, @person, :foo) do %> ... # # produces: # # ... # # You can also pass an array of objects which this method will loop through # and yield the current object to the supplied block, reducing the need for # having to iterate through the object (using each) beforehand. # For example (assuming @people is an array of Person objects): # # <%= content_tag_for(:tr, @people) do |person| %> # <%= person.first_name %> # <%= person.last_name %> # <% end %> # # produces: # # ... # ... # # content_tag_for also accepts a hash of options, which will be converted to # additional HTML attributes. If you specify a :class value, it will be combined # with the default class name for your object. For example: # # <%= content_tag_for(:li, @person, class: "bar") %>... # # produces: # #
  • ... # def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block) options, prefix = prefix, nil if prefix.is_a?(Hash) Array(single_or_multiple_records).map do |single_record| content_tag_for_single_record(tag_name, single_record, prefix, options, &block) end.join("\n").html_safe end private # Called by content_tag_for internally to render a content tag # for each record. def content_tag_for_single_record(tag_name, record, prefix, options, &block) options = options ? options.dup : {} options[:class] = [ dom_class(record, prefix), options[:class] ].compact options[:id] = dom_id(record, prefix) if block_given? content_tag(tag_name, capture(record, &block), options) else content_tag(tag_name, "", options) end end end end end rails-4.2.6/actionview/lib/action_view/helpers/rendering_helper.rb000066400000000000000000000072661266740050600254060ustar00rootroot00000000000000module ActionView module Helpers # = Action View Rendering # # Implements methods that allow rendering from a view context. # In order to use this module, all you need is to implement # view_renderer that returns an ActionView::Renderer object. module RenderingHelper # Returns the result of a render that's dictated by the options hash. The primary options are: # # * :partial - See ActionView::PartialRenderer. # * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those. # * :inline - Renders an inline template similar to how it's done in the controller. # * :text - Renders the text passed in out. # * :plain - Renders the text passed in out. Setting the content # type as text/plain. # * :html - Renders the HTML safe string passed in out, otherwise # performs HTML escape on the string first. Setting the content type as # text/html. # * :body - Renders the text passed in, and inherits the content # type of text/html from ActionDispatch::Response # object. # # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter # as the locals hash. def render(options = {}, locals = {}, &block) case options when Hash if block_given? view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block) else view_renderer.render(self, options) end else view_renderer.render_partial(self, :partial => options, :locals => locals) end end # Overwrites _layout_for in the context object so it supports the case a block is # passed to a partial. Returns the contents that are yielded to a layout, given a # name or a block. # # You can think of a layout as a method that is called with a block. If the user calls # yield :some_name, the block, by default, returns content_for(:some_name). # If the user calls simply +yield+, the default block returns content_for(:layout). # # The user can override this default by passing a block to the layout: # # # The template # <%= render layout: "my_layout" do %> # Content # <% end %> # # # The layout # # <%= yield %> # # # In this case, instead of the default block, which would return content_for(:layout), # this method returns the block that was passed in to render :layout, and the response # would be # # # Content # # # Finally, the block can take block arguments, which can be passed in by +yield+: # # # The template # <%= render layout: "my_layout" do |customer| %> # Hello <%= customer.name %> # <% end %> # # # The layout # # <%= yield Struct.new(:name).new("David") %> # # # In this case, the layout would receive the block passed into render :layout, # and the struct specified would be passed into the block as an argument. The result # would be # # # Hello David # # def _layout_for(*args, &block) name = args.first if block && !name.is_a?(Symbol) capture(*args, &block) else super end end end end end rails-4.2.6/actionview/lib/action_view/helpers/sanitize_helper.rb000066400000000000000000000147501266740050600252530ustar00rootroot00000000000000require 'active_support/core_ext/object/try' require 'active_support/deprecation' require 'rails-html-sanitizer' module ActionView # = Action View Sanitize Helpers module Helpers # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. # These helper methods extend Action View making them callable within your template files. module SanitizeHelper extend ActiveSupport::Concern # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted. # # It also strips href/src attributes with unsafe protocols like # javascript:, while also protecting against attempts to use Unicode, # ASCII, and hex character references to work around these protocol filters. # # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information. # # Custom sanitization rules can also be provided. # # Please note that sanitizing user-provided text does not guarantee that the # resulting markup is valid or even well-formed. For example, the output may still # contain unescaped characters like <, >, or &. # # ==== Options # # * :tags - An array of allowed tags. # * :attributes - An array of allowed attributes. # * :scrubber - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer] # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that # defines custom sanitization rules. A custom scrubber takes precedence over # custom tags and attributes. # # ==== Examples # # Normal use: # # <%= sanitize @comment.body %> # # Providing custom whitelisted tags and attributes: # # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %> # # Providing a custom Rails::Html scrubber: # # class CommentScrubber < Rails::Html::PermitScrubber # def allowed_node?(node) # !%w(form script comment blockquote).include?(node.name) # end # # def skip_node?(node) # node.text? # end # # def scrub_attribute?(name) # name == 'style' # end # end # # <%= sanitize @comment.body, scrubber: CommentScrubber.new %> # # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for # documentation about Rails::Html scrubbers. # # Providing a custom Loofah::Scrubber: # # scrubber = Loofah::Scrubber.new do |node| # node.remove if node.name == 'script' # end # # <%= sanitize @comment.body, scrubber: scrubber %> # # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more # information about defining custom Loofah::Scrubber objects. # # To set the default allowed tags or attributes across your application: # # # In config/application.rb # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a'] # config.action_view.sanitized_allowed_attributes = ['href', 'title'] def sanitize(html, options = {}) self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe) end # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute. def sanitize_css(style) self.class.white_list_sanitizer.sanitize_css(style) end # Strips all HTML tags from +html+, including comments. # # strip_tags("Strip these tags!") # # => Strip these tags! # # strip_tags("Bold no more! See more here...") # # => Bold no more! See more here... # # strip_tags("
    Welcome to my website!
    ") # # => Welcome to my website! def strip_tags(html) self.class.full_sanitizer.sanitize(html, encode_special_chars: false) end # Strips all link tags from +html+ leaving just the link text. # # strip_links('Ruby on Rails') # # => Ruby on Rails # # strip_links('Please e-mail me at me@email.com.') # # => Please e-mail me at me@email.com. # # strip_links('Blog: Visit.') # # => Blog: Visit. def strip_links(html) self.class.link_sanitizer.sanitize(html) end module ClassMethods #:nodoc: attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer # Vendors the full, link and white list sanitizers. # Provided strictly for compabitility and can be removed in Rails 5. def sanitizer_vendor Rails::Html::Sanitizer end def sanitized_allowed_tags sanitizer_vendor.white_list_sanitizer.allowed_tags end def sanitized_allowed_attributes sanitizer_vendor.white_list_sanitizer.allowed_attributes end # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with # any object that responds to +sanitize+. # # class Application < Rails::Application # config.action_view.full_sanitizer = MySpecialSanitizer.new # end # def full_sanitizer @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new end # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+. # Replace with any object that responds to +sanitize+. # # class Application < Rails::Application # config.action_view.link_sanitizer = MySpecialSanitizer.new # end # def link_sanitizer @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new end # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+. # Replace with any object that responds to +sanitize+. # # class Application < Rails::Application # config.action_view.white_list_sanitizer = MySpecialSanitizer.new # end # def white_list_sanitizer @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tag_helper.rb000066400000000000000000000166671266740050600242110ustar00rootroot00000000000000require 'active_support/core_ext/string/output_safety' require 'set' module ActionView # = Action View Tag Helpers module Helpers #:nodoc: # Provides methods to generate HTML tags programmatically when you can't use # a Builder. By default, they output XHTML compliant tags. module TagHelper extend ActiveSupport::Concern include CaptureHelper include OutputSafetyHelper BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer autoplay controls loop selected hidden scoped async defer reversed ismap seamless muted required autofocus novalidate formnovalidate open pubdate itemscope allowfullscreen default inert sortable truespeed typemustmatch).to_set BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym }) TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set PRE_CONTENT_STRINGS = { :textarea => "\n" } # Returns an empty HTML tag of type +name+ which by default is XHTML # compliant. Set +open+ to true to create an open tag compatible # with HTML 4.0 and below. Add HTML attributes by passing an attributes # hash to +options+. Set +escape+ to false to disable attribute value # escaping. # # ==== Options # You can use symbols or strings for the attribute names. # # Use +true+ with boolean attributes that can render with no value, like # +disabled+ and +readonly+. # # HTML5 data-* attributes can be set with a single +data+ key # pointing to a hash of sub-attributes. # # To play nicely with JavaScript conventions sub-attributes are dasherized. # For example, a key +user_id+ would render as data-user-id and # thus accessed as dataset.userId. # # Values are encoded to JSON, with the exception of strings, symbols and # BigDecimals. # This may come in handy when using jQuery's HTML5-aware .data() # from 1.4.3. # # ==== Examples # tag("br") # # =>
    # # tag("br", nil, true) # # =>
    # # tag("input", type: 'text', disabled: true) # # => # # tag("input", type: 'text', class: ["strong", "highlight"]) # # => # # tag("img", src: "open & shut.png") # # => # # tag("img", {src: "open & shut.png"}, false, false) # # => # # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)}) # # =>
    def tag(name, options = nil, open = false, escape = true) "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe end # Returns an HTML block tag of type +name+ surrounding the +content+. Add # HTML attributes by passing an attributes hash to +options+. # Instead of passing the content as an argument, you can also use a block # in which case, you pass your +options+ as the second parameter. # Set escape to false to disable attribute value escaping. # # ==== Options # The +options+ hash can be used with attributes with no value like (disabled and # readonly), which you can give a value of true in the +options+ hash. You can use # symbols or strings for the attribute names. # # ==== Examples # content_tag(:p, "Hello world!") # # =>

    Hello world!

    # content_tag(:div, content_tag(:p, "Hello world!"), class: "strong") # # =>

    Hello world!

    # content_tag(:div, "Hello world!", class: ["strong", "highlight"]) # # =>
    Hello world!
    # content_tag("select", options, multiple: true) # # => # # <%= content_tag :div, class: "strong" do -%> # Hello world! # <% end -%> # # =>
    Hello world!
    def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) if block_given? options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) content_tag_string(name, capture(&block), options, escape) else content_tag_string(name, content_or_options_with_block, options, escape) end end # Returns a CDATA section with the given +content+. CDATA sections # are used to escape blocks of text containing characters which would # otherwise be recognized as markup. CDATA sections begin with the string # and end with (and may not contain) the string ]]>. # # cdata_section("") # # => ]]> # # cdata_section(File.read("hello_world.txt")) # # => # # cdata_section("hello]]>world") # # => world]]> def cdata_section(content) splitted = content.to_s.gsub(/\]\]\>/, ']]]]>') "".html_safe end # Returns an escaped version of +html+ without affecting existing escaped entities. # # escape_once("1 < 2 & 3") # # => "1 < 2 & 3" # # escape_once("<< Accept & Checkout") # # => "<< Accept & Checkout" def escape_once(html) ERB::Util.html_escape_once(html) end private def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options content = ERB::Util.unwrapped_html_escape(content) if escape "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}".html_safe end def tag_options(options, escape = true) return if options.blank? attrs = [] options.each_pair do |key, value| if TAG_PREFIXES.include?(key) && value.is_a?(Hash) value.each_pair do |k, v| attrs << prefix_tag_option(key, k, v, escape) end elsif BOOLEAN_ATTRIBUTES.include?(key) attrs << boolean_tag_option(key) if value elsif !value.nil? attrs << tag_option(key, value, escape) end end " #{attrs * ' '}" unless attrs.empty? end def prefix_tag_option(prefix, key, value, escape) key = "#{prefix}-#{key.to_s.dasherize}" unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal) value = value.to_json end tag_option(key, value, escape) end def boolean_tag_option(key) %(#{key}="#{key}") end def tag_option(key, value, escape) if value.is_a?(Array) value = escape ? safe_join(value, " ") : value.join(" ") else value = escape ? ERB::Util.unwrapped_html_escape(value) : value end %(#{key}="#{value}") end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags.rb000066400000000000000000000021351266740050600230160ustar00rootroot00000000000000module ActionView module Helpers module Tags #:nodoc: extend ActiveSupport::Autoload eager_autoload do autoload :Base autoload :Translator autoload :CheckBox autoload :CollectionCheckBoxes autoload :CollectionRadioButtons autoload :CollectionSelect autoload :ColorField autoload :DateField autoload :DateSelect autoload :DatetimeField autoload :DatetimeLocalField autoload :DatetimeSelect autoload :EmailField autoload :FileField autoload :GroupedCollectionSelect autoload :HiddenField autoload :Label autoload :MonthField autoload :NumberField autoload :PasswordField autoload :RadioButton autoload :RangeField autoload :SearchField autoload :Select autoload :TelField autoload :TextArea autoload :TextField autoload :TimeField autoload :TimeSelect autoload :TimeZoneSelect autoload :UrlField autoload :WeekField end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/000077500000000000000000000000001266740050600224705ustar00rootroot00000000000000rails-4.2.6/actionview/lib/action_view/helpers/tags/base.rb000066400000000000000000000133401266740050600237300ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class Base # :nodoc: include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper include FormOptionsHelper attr_reader :object def initialize(object_name, method_name, template_object, options = {}) @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup @template_object = template_object @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]") @object = retrieve_object(options.delete(:object)) @options = options @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match end # This is what child classes implement. def render raise NotImplementedError, "Subclasses must implement a render method" end private def value(object) object.public_send @method_name if object end def value_before_type_cast(object) unless object.nil? method_before_type_cast = @method_name + "_before_type_cast" if value_came_from_user?(object) && object.respond_to?(method_before_type_cast) object.public_send(method_before_type_cast) else value(object) end end end def value_came_from_user?(object) method_name = "#{@method_name}_came_from_user?" !object.respond_to?(method_name) || object.public_send(method_name) end def retrieve_object(object) if object object elsif @template_object.instance_variable_defined?("@#{@object_name}") @template_object.instance_variable_get("@#{@object_name}") end rescue NameError # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil. nil end def retrieve_autoindex(pre_match) object = self.object || @template_object.instance_variable_get("@#{pre_match}") if object && object.respond_to?(:to_param) object.to_param else raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" end end def add_default_name_and_id_for_value(tag_value, options) if tag_value.nil? add_default_name_and_id(options) else specified_id = options["id"] add_default_name_and_id(options) if specified_id.blank? && options["id"].present? options["id"] += "_#{sanitized_value(tag_value)}" end end end def add_default_name_and_id(options) if options.has_key?("index") options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) } options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) } options.delete("index") elsif defined?(@auto_index) options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) } options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } else options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) } options["id"] = options.fetch("id"){ tag_id } end options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence end def tag_name(multiple = false) "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" end def tag_name_with_index(index, multiple = false) "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" end def tag_id "#{sanitized_object_name}_#{sanitized_method_name}" end def tag_id_with_index(index) "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" end def sanitized_object_name @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") end def sanitized_method_name @sanitized_method_name ||= @method_name.sub(/\?$/,"") end def sanitized_value(value) value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase end def select_content_tag(option_tags, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options) value = options.fetch(:selected) { value(object) } select = content_tag("select", add_options(option_tags, options, value), html_options) if html_options["multiple"] && options.fetch(:include_hidden, true) tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select else select end end def select_not_required?(html_options) !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1 end def add_options(option_tags, options, value = nil) if options[:include_blank] option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags end if value.blank? && options[:prompt] option_tags = content_tag_string('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags end option_tags end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/check_box.rb000066400000000000000000000035311266740050600247440ustar00rootroot00000000000000require 'action_view/helpers/tags/checkable' module ActionView module Helpers module Tags # :nodoc: class CheckBox < Base #:nodoc: include Checkable def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options) @checked_value = checked_value @unchecked_value = unchecked_value super(object_name, method_name, template_object, options) end def render options = @options.stringify_keys options["type"] = "checkbox" options["value"] = @checked_value options["checked"] = "checked" if input_checked?(object, options) if options["multiple"] add_default_name_and_id_for_value(@checked_value, options) options.delete("multiple") else add_default_name_and_id(options) end include_hidden = options.delete("include_hidden") { true } checkbox = tag("input", options) if include_hidden hidden = hidden_field_for_checkbox(options) hidden + checkbox else checkbox end end private def checked?(value) case value when TrueClass, FalseClass value == !!@checked_value when NilClass false when String value == @checked_value else if value.respond_to?(:include?) value.include?(@checked_value) else value.to_i == @checked_value.to_i end end end def hidden_field_for_checkbox(options) @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/checkable.rb000066400000000000000000000005771266740050600247270ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: module Checkable # :nodoc: def input_checked?(object, options) if options.has_key?("checked") checked = options.delete "checked" checked == true || checked == "checked" else checked?(value(object)) end end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb000066400000000000000000000032541266740050600275110ustar00rootroot00000000000000require 'action_view/helpers/tags/collection_helpers' module ActionView module Helpers module Tags # :nodoc: class CollectionCheckBoxes < Base # :nodoc: include CollectionHelpers class CheckBoxBuilder < Builder # :nodoc: def check_box(extra_html_options={}) html_options = extra_html_options.merge(@input_html_options) @template_object.check_box(@object_name, @method_name, html_options, @value, nil) end end def render(&block) rendered_collection = render_collection do |item, value, text, default_html_options| default_html_options[:multiple] = true builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options) if block_given? @template_object.capture(builder, &block) else render_component(builder) end end # Append a hidden field to make sure something will be sent back to the # server if all check boxes are unchecked. if @options.fetch(:include_hidden, true) rendered_collection + hidden_field else rendered_collection end end private def render_component(builder) builder.check_box + builder.label end def hidden_field hidden_name = @html_options[:name] hidden_name ||= if @options.has_key?(:index) "#{tag_name_with_index(@options[:index])}[]" else "#{tag_name}[]" end @template_object.hidden_field_tag(hidden_name, "", id: nil) end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/collection_helpers.rb000066400000000000000000000057631266740050600267050ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: module CollectionHelpers # :nodoc: class Builder # :nodoc: attr_reader :object, :text, :value def initialize(template_object, object_name, method_name, object, sanitized_attribute_name, text, value, input_html_options) @template_object = template_object @object_name = object_name @method_name = method_name @object = object @sanitized_attribute_name = sanitized_attribute_name @text = text @value = value @input_html_options = input_html_options end def label(label_html_options={}, &block) html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options) @template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block) end end def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) @collection = collection @value_method = value_method @text_method = text_method @html_options = html_options super(object_name, method_name, template_object, options) end private def instantiate_builder(builder_class, item, value, text, html_options) builder_class.new(@template_object, @object_name, @method_name, item, sanitize_attribute_name(value), text, value, html_options) end # Generate default options for collection helpers, such as :checked and # :disabled. def default_html_options_for_collection(item, value) #:nodoc: html_options = @html_options.dup [:checked, :selected, :disabled, :readonly].each do |option| current_value = @options[option] next if current_value.nil? accept = if current_value.respond_to?(:call) current_value.call(item) else Array(current_value).map(&:to_s).include?(value.to_s) end if accept html_options[option] = true elsif option == :checked html_options[option] = false end end html_options[:object] = @object html_options end def sanitize_attribute_name(value) #:nodoc: "#{sanitized_method_name}_#{sanitized_value(value)}" end def render_collection #:nodoc: @collection.map do |item| value = value_for_collection(item, @value_method) text = value_for_collection(item, @text_method) default_html_options = default_html_options_for_collection(item, value) additional_html_options = option_html_attributes(item) yield item, value, text, default_html_options.merge(additional_html_options) end.join.html_safe end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb000066400000000000000000000020121266740050600300770ustar00rootroot00000000000000require 'action_view/helpers/tags/collection_helpers' module ActionView module Helpers module Tags # :nodoc: class CollectionRadioButtons < Base # :nodoc: include CollectionHelpers class RadioButtonBuilder < Builder # :nodoc: def radio_button(extra_html_options={}) html_options = extra_html_options.merge(@input_html_options) @template_object.radio_button(@object_name, @method_name, @value, html_options) end end def render(&block) render_collection do |item, value, text, default_html_options| builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options) if block_given? @template_object.capture(builder, &block) else render_component(builder) end end end private def render_component(builder) builder.radio_button + builder.label end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/collection_select.rb000066400000000000000000000015601266740050600265110ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class CollectionSelect < Base #:nodoc: def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) @collection = collection @value_method = value_method @text_method = text_method @html_options = html_options super(object_name, method_name, template_object, options) end def render option_tags_options = { :selected => @options.fetch(:selected) { value(@object) }, :disabled => @options[:disabled] } select_content_tag( options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options), @options, @html_options ) end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/color_field.rb000066400000000000000000000010561266740050600253000ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class ColorField < TextField # :nodoc: def render options = @options.stringify_keys options["value"] ||= validate_color_string(value(object)) @options = options super end private def validate_color_string(string) regex = /#[0-9a-fA-F]{6}/ if regex.match(string) string.downcase else "#000000" end end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/date_field.rb000066400000000000000000000003661266740050600251020ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class DateField < DatetimeField # :nodoc: private def format_date(value) value.try(:strftime, "%Y-%m-%d") end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/date_select.rb000066400000000000000000000041071266740050600252730ustar00rootroot00000000000000require 'active_support/core_ext/time/calculations' module ActionView module Helpers module Tags # :nodoc: class DateSelect < Base # :nodoc: def initialize(object_name, method_name, template_object, options, html_options) @html_options = html_options super(object_name, method_name, template_object, options) end def render error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe) end class << self def select_type @select_type ||= self.name.split("::").last.sub("Select", "").downcase end end private def select_type self.class.select_type end def datetime_selector(options, html_options) datetime = options.fetch(:selected) { value(object) || default_datetime(options) } @auto_index ||= nil options = options.dup options[:field_name] = @method_name options[:include_position] = true options[:prefix] ||= @object_name options[:index] = @auto_index if @auto_index && !options.has_key?(:index) DateTimeSelector.new(datetime, options, html_options) end def default_datetime(options) return if options[:include_blank] || options[:prompt] case options[:default] when nil Time.current when Date, Time options[:default] else default = options[:default].dup # Rename :minute and :second to :min and :sec default[:min] ||= default[:minute] default[:sec] ||= default[:second] time = Time.current [:year, :month, :day, :hour, :min, :sec].each do |key| default[key] ||= time.send(key) end Time.utc( default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec] ) end end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/datetime_field.rb000066400000000000000000000013701266740050600257550ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class DatetimeField < TextField # :nodoc: def render options = @options.stringify_keys options["value"] ||= format_date(value(object)) options["min"] = format_date(datetime_value(options["min"])) options["max"] = format_date(datetime_value(options["max"])) @options = options super end private def format_date(value) value.try(:strftime, "%Y-%m-%dT%T.%L%z") end def datetime_value(value) if value.is_a? String DateTime.parse(value) rescue nil else value end end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/datetime_local_field.rb000066400000000000000000000005711266740050600271310ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class DatetimeLocalField < DatetimeField # :nodoc: class << self def field_type @field_type ||= "datetime-local" end end private def format_date(value) value.try(:strftime, "%Y-%m-%dT%T") end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/datetime_select.rb000066400000000000000000000002131266740050600261440ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class DatetimeSelect < DateSelect # :nodoc: end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/email_field.rb000066400000000000000000000002061266740050600252450ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class EmailField < TextField # :nodoc: end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/file_field.rb000066400000000000000000000002051266740050600250740ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class FileField < TextField # :nodoc: end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb000066400000000000000000000021351266740050600302350ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class GroupedCollectionSelect < Base # :nodoc: def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) @collection = collection @group_method = group_method @group_label_method = group_label_method @option_key_method = option_key_method @option_value_method = option_value_method @html_options = html_options super(object_name, method_name, template_object, options) end def render option_tags_options = { :selected => @options.fetch(:selected) { value(@object) }, :disabled => @options[:disabled] } select_content_tag( option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options ) end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/hidden_field.rb000066400000000000000000000002071266740050600254120ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class HiddenField < TextField # :nodoc: end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/label.rb000066400000000000000000000044011266740050600240730ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class Label < Base # :nodoc: class LabelBuilder # :nodoc: attr_reader :object def initialize(template_object, object_name, method_name, object, tag_value) @template_object = template_object @object_name = object_name @method_name = method_name @object = object @tag_value = tag_value end def translation method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name content ||= Translator .new(object, @object_name, method_and_value, "helpers.label") .translate content ||= @method_name.humanize content end end def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil) options ||= {} content_is_options = content_or_options.is_a?(Hash) if content_is_options options.merge! content_or_options @content = nil else @content = content_or_options end super(object_name, method_name, template_object, options) end def render(&block) options = @options.stringify_keys tag_value = options.delete("value") name_and_id = options.dup if name_and_id["for"] name_and_id["id"] = name_and_id["for"] else name_and_id.delete("id") end add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options.delete("namespace") options["for"] = name_and_id["id"] unless options.key?("for") builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value) content = if block_given? @template_object.capture(builder, &block) elsif @content.present? @content.to_s else render_component(builder) end label_tag(name_and_id["id"], content, options) end private def render_component(builder) builder.translation end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/month_field.rb000066400000000000000000000003641266740050600253100ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class MonthField < DatetimeField # :nodoc: private def format_date(value) value.try(:strftime, "%Y-%m") end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/number_field.rb000066400000000000000000000006301266740050600254470ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class NumberField < TextField # :nodoc: def render options = @options.stringify_keys if range = options.delete("in") || options.delete("within") options.update("min" => range.min, "max" => range.max) end @options = options super end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/password_field.rb000066400000000000000000000003561266740050600260260ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class PasswordField < TextField # :nodoc: def render @options = {:value => nil}.merge!(@options) super end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/placeholderable.rb000066400000000000000000000012321266740050600261210ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: module Placeholderable # :nodoc: def initialize(*) super if tag_value = @options[:placeholder] placeholder = tag_value if tag_value.is_a?(String) method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}" placeholder ||= Tags::Translator .new(object, @object_name, method_and_value, "helpers.placeholder") .translate placeholder ||= @method_name.humanize @options[:placeholder] = placeholder end end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/radio_button.rb000066400000000000000000000015001266740050600255020ustar00rootroot00000000000000require 'action_view/helpers/tags/checkable' module ActionView module Helpers module Tags # :nodoc: class RadioButton < Base # :nodoc: include Checkable def initialize(object_name, method_name, template_object, tag_value, options) @tag_value = tag_value super(object_name, method_name, template_object, options) end def render options = @options.stringify_keys options["type"] = "radio" options["value"] = @tag_value options["checked"] = "checked" if input_checked?(object, options) add_default_name_and_id_for_value(@tag_value, options) tag("input", options) end private def checked?(value) value.to_s == @tag_value.to_s end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/range_field.rb000066400000000000000000000002101266740050600252450ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class RangeField < NumberField # :nodoc: end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/search_field.rb000066400000000000000000000011021266740050600254170ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class SearchField < TextField # :nodoc: def render super do |options| if options["autosave"] if options["autosave"] == true options["autosave"] = request.host.split(".").reverse.join(".") end options["results"] ||= 10 end if options["onsearch"] options["incremental"] = true unless options.has_key?("incremental") end end end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/select.rb000066400000000000000000000022761266740050600243030ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class Select < Base # :nodoc: def initialize(object_name, method_name, template_object, choices, options, html_options) @choices = block_given? ? template_object.capture { yield || "" } : choices @choices = @choices.to_a if @choices.is_a?(Range) @html_options = html_options super(object_name, method_name, template_object, options) end def render option_tags_options = { :selected => @options.fetch(:selected) { value(@object) }, :disabled => @options[:disabled] } option_tags = if grouped_choices? grouped_options_for_select(@choices, option_tags_options) else options_for_select(@choices, option_tags_options) end select_content_tag(option_tags, @options, @html_options) end private # Grouped choices look like this: # # [nil, []] # { nil => [] } def grouped_choices? !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/tel_field.rb000066400000000000000000000002041266740050600247400ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class TelField < TextField # :nodoc: end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/text_area.rb000066400000000000000000000011071266740050600247700ustar00rootroot00000000000000require 'action_view/helpers/tags/placeholderable' module ActionView module Helpers module Tags # :nodoc: class TextArea < Base # :nodoc: include Placeholderable def render options = @options.stringify_keys add_default_name_and_id(options) if size = options.delete("size") options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) end content_tag("textarea", options.delete("value") { value_before_type_cast(object) }, options) end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/text_field.rb000066400000000000000000000015541266740050600251510ustar00rootroot00000000000000require 'action_view/helpers/tags/placeholderable' module ActionView module Helpers module Tags # :nodoc: class TextField < Base # :nodoc: include Placeholderable def render options = @options.stringify_keys options["size"] = options["maxlength"] unless options.key?("size") options["type"] ||= field_type options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file" yield options if block_given? add_default_name_and_id(options) tag("input", options) end class << self def field_type @field_type ||= self.name.split("::").last.sub("Field", "").downcase end end private def field_type self.class.field_type end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/time_field.rb000066400000000000000000000003631266740050600251200ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class TimeField < DatetimeField # :nodoc: private def format_date(value) value.try(:strftime, "%T.%L") end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/time_select.rb000066400000000000000000000002071266740050600253110ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class TimeSelect < DateSelect # :nodoc: end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/time_zone_select.rb000066400000000000000000000012031266740050600263410ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class TimeZoneSelect < Base # :nodoc: def initialize(object_name, method_name, template_object, priority_zones, options, html_options) @priority_zones = priority_zones @html_options = html_options super(object_name, method_name, template_object, options) end def render select_content_tag( time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options ) end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/translator.rb000066400000000000000000000021611266740050600252060ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class Translator # :nodoc: def initialize(object, object_name, method_and_value, scope) @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1') @method_and_value = method_and_value @scope = scope @model = object.respond_to?(:to_model) ? object.to_model : nil end def translate translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence translated_attribute || human_attribute_name end protected attr_reader :object_name, :method_and_value, :scope, :model private def i18n_default if model key = model.model_name.i18n_key ["#{key}.#{method_and_value}".to_sym, ""] else "" end end def human_attribute_name if model && model.class.respond_to?(:human_attribute_name) model.class.human_attribute_name(method_and_value) end end end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/url_field.rb000066400000000000000000000002041266740050600247560ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class UrlField < TextField # :nodoc: end end end end rails-4.2.6/actionview/lib/action_view/helpers/tags/week_field.rb000066400000000000000000000003641266740050600251160ustar00rootroot00000000000000module ActionView module Helpers module Tags # :nodoc: class WeekField < DatetimeField # :nodoc: private def format_date(value) value.try(:strftime, "%Y-W%V") end end end end end rails-4.2.6/actionview/lib/action_view/helpers/text_helper.rb000066400000000000000000000431471266740050600244130ustar00rootroot00000000000000require 'active_support/core_ext/string/filters' require 'active_support/core_ext/array/extract_options' module ActionView # = Action View Text Helpers module Helpers #:nodoc: # The TextHelper module provides a set of methods for filtering, formatting # and transforming strings, which can reduce the amount of inline Ruby code in # your views. These helper methods extend Action View making them callable # within your template files. # # ==== Sanitization # # Most text helpers by default sanitize the given content, but do not escape it. # This means HTML tags will appear in the page but all malicious code will be removed. # Let's look at some examples using the +simple_format+ method: # # simple_format('Example') # # => "

    Example

    " # # simple_format('Example') # # => "

    Example

    " # # If you want to escape all content, you should invoke the +h+ method before # calling the text helper. # # simple_format h('Example') # # => "

    <a href=\"http://example.com/\">Example</a>

    " module TextHelper extend ActiveSupport::Concern include SanitizeHelper include TagHelper include OutputSafetyHelper # The preferred method of outputting text in your views is to use the # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods # do not operate as expected in an eRuby code block. If you absolutely must # output text within a non-output code block (i.e., <% %>), you can use the concat method. # # <% # concat "hello" # # is the equivalent of <%= "hello" %> # # if logged_in # concat "Logged in!" # else # concat link_to('login', action: :login) # end # # will either display "Logged in!" or a login link # %> def concat(string) output_buffer << string end def safe_concat(string) output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string) end # Truncates a given +text+ after a given :length if +text+ is longer than :length # (defaults to 30). The last characters will be replaced with the :omission (defaults to "...") # for a total length not exceeding :length. # # Pass a :separator to truncate +text+ at a natural break. # # Pass a block if you want to show extra content when the text is truncated. # # The result is marked as HTML-safe, but it is escaped by default, unless :escape is # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation # may produce invalid HTML (such as unbalanced or incomplete tags). # # truncate("Once upon a time in a world far far away") # # => "Once upon a time in a world..." # # truncate("Once upon a time in a world far far away", length: 17) # # => "Once upon a ti..." # # truncate("Once upon a time in a world far far away", length: 17, separator: ' ') # # => "Once upon a..." # # truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)') # # => "And they f... (continued)" # # truncate("

    Once upon a time in a world far far away

    ") # # => "<p>Once upon a time in a wo..." # # truncate("

    Once upon a time in a world far far away

    ", escape: false) # # => "

    Once upon a time in a wo..." # # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" } # # => "Once upon a time in a wo...Continue" def truncate(text, options = {}, &block) if text length = options.fetch(:length, 30) content = text.truncate(length, options) content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content) content << capture(&block) if block_given? && text.length > length content end end # Highlights one or more +phrases+ everywhere in +text+ by inserting it into # a :highlighter string. The highlighter can be specialized by passing :highlighter # as a single-quoted string with \1 where the phrase is to be inserted (defaults to # '\1') or passing a block that receives each matched term. # # highlight('You searched for: rails', 'rails') # # => You searched for: rails # # highlight('You searched for: rails', /for|rails/) # # => You searched for: rails # # highlight('You searched for: ruby, rails, dhh', 'actionpack') # # => You searched for: ruby, rails, dhh # # highlight('You searched for: rails', ['for', 'rails'], highlighter: '\1') # # => You searched for: rails # # highlight('You searched for: rails', 'rails', highlighter: '\1') # # => You searched for: rails # # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) } # # => You searched for: rails def highlight(text, phrases, options = {}) text = sanitize(text) if options.fetch(:sanitize, true) if text.blank? || phrases.blank? text || "" else match = Array(phrases).map do |p| Regexp === p ? p.to_s : Regexp.escape(p) end.join('|') if block_given? text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found } else highlighter = options.fetch(:highlighter, '\1') text.gsub(/(#{match})(?![^<]*?>)/i, highlighter) end end.html_safe end # Extracts an excerpt from +text+ that matches the first instance of +phrase+. # The :radius option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters # defined in :radius (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, # then the :omission option (which defaults to "...") will be prepended/appended accordingly. Use the # :separator option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+ # isn't found, nil is returned. # # excerpt('This is an example', 'an', radius: 5) # # => ...s is an exam... # # excerpt('This is an example', 'is', radius: 5) # # => This is a... # # excerpt('This is an example', 'is') # # => This is an example # # excerpt('This next thing is an example', 'ex', radius: 2) # # => ...next... # # excerpt('This is also an example', 'an', radius: 8, omission: ' ') # # => is also an example # # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1) # # => ...a very beautiful... def excerpt(text, phrase, options = {}) return unless text && phrase separator = options.fetch(:separator, nil) || "" case phrase when Regexp regex = phrase else regex = /#{Regexp.escape(phrase)}/i end return unless matches = text.match(regex) phrase = matches[0] unless separator.empty? text.split(separator).each do |value| if value.match(regex) regex = phrase = value break end end end first_part, second_part = text.split(phrase, 2) prefix, first_part = cut_excerpt_part(:first, first_part, separator, options) postfix, second_part = cut_excerpt_part(:second, second_part, separator, options) affix = [first_part, separator, phrase, separator, second_part].join.strip [prefix, affix, postfix].join end # Attempts to pluralize the +singular+ word unless +count+ is 1. If # +plural+ is supplied, it will use that when count is > 1, otherwise # it will use the Inflector to determine the plural form. # # pluralize(1, 'person') # # => 1 person # # pluralize(2, 'person') # # => 2 people # # pluralize(3, 'person', 'users') # # => 3 users # # pluralize(0, 'person') # # => 0 people def pluralize(count, singular, plural = nil) word = if (count == 1 || count =~ /^1(\.0+)?$/) singular else plural || singular.pluralize end "#{count || 0} #{word}" end # Wraps the +text+ into lines no longer than +line_width+ width. This method # breaks on the first whitespace character that does not exceed +line_width+ # (which is 80 by default). # # word_wrap('Once upon a time') # # => Once upon a time # # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...') # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined... # # word_wrap('Once upon a time', line_width: 8) # # => Once\nupon a\ntime # # word_wrap('Once upon a time', line_width: 1) # # => Once\nupon\na\ntime def word_wrap(text, options = {}) line_width = options.fetch(:line_width, 80) text.split("\n").collect! do |line| line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line end * "\n" end # Returns +text+ transformed into HTML using simple formatting rules. # Two or more consecutive newlines(\n\n) are considered as a # paragraph and wrapped in

    tags. One newline (\n) is # considered as a linebreak and a
    tag is appended. This # method does not remove the newlines from the +text+. # # You can pass any HTML attributes into html_options. These # will be added to all created paragraphs. # # ==== Options # * :sanitize - If +false+, does not sanitize +text+. # * :wrapper_tag - String representing the wrapper tag, defaults to "p" # # ==== Examples # my_text = "Here is some basic text...\n...with a line break." # # simple_format(my_text) # # => "

    Here is some basic text...\n
    ...with a line break.

    " # # simple_format(my_text, {}, wrapper_tag: "div") # # => "
    Here is some basic text...\n
    ...with a line break.
    " # # more_text = "We want to put a paragraph...\n\n...right there." # # simple_format(more_text) # # => "

    We want to put a paragraph...

    \n\n

    ...right there.

    " # # simple_format("Look ma! A class!", class: 'description') # # => "

    Look ma! A class!

    " # # simple_format("Unblinkable.") # # => "

    Unblinkable.

    " # # simple_format("Blinkable! It's true.", {}, sanitize: false) # # => "

    Blinkable! It's true.

    " def simple_format(text, html_options = {}, options = {}) wrapper_tag = options.fetch(:wrapper_tag, :p) text = sanitize(text) if options.fetch(:sanitize, true) paragraphs = split_paragraphs(text) if paragraphs.empty? content_tag(wrapper_tag, nil, html_options) else paragraphs.map! { |paragraph| content_tag(wrapper_tag, raw(paragraph), html_options) }.join("\n\n").html_safe end end # Creates a Cycle object whose _to_s_ method cycles through elements of an # array every time it is called. This can be used for example, to alternate # classes for table rows. You can use named cycles to allow nesting in loops. # Passing a Hash as the last parameter with a :name key will create a # named cycle. The default name for a cycle without a +:name+ key is # "default". You can manually reset a cycle by calling reset_cycle # and passing the name of the cycle. The current cycle string can be obtained # anytime using the current_cycle method. # # # Alternate CSS classes for even and odd numbers... # @items = [1,2,3,4] # # <% @items.each do |item| %> # "> # # # <% end %> #
    item
    # # # # Cycle CSS classes for rows, and text colors for values within each row # @items = x = [{first: 'Robert', middle: 'Daniel', last: 'James'}, # {first: 'Emily', middle: 'Shannon', maiden: 'Pike', last: 'Hicks'}, # {first: 'June', middle: 'Dae', last: 'Jones'}] # <% @items.each do |item| %> # "> # # <% item.values.each do |value| %> # <%# Create a named cycle "colors" %> # "> # <%= value %> # # <% end %> # <% reset_cycle("colors") %> # # # <% end %> def cycle(first_value, *values) options = values.extract_options! name = options.fetch(:name, 'default') values.unshift(*first_value) cycle = get_cycle(name) unless cycle && cycle.values == values cycle = set_cycle(name, Cycle.new(*values)) end cycle.to_s end # Returns the current cycle string after a cycle has been started. Useful # for complex table highlighting or any other design need which requires # the current cycle string in more than one place. # # # Alternate background colors # @items = [1,2,3,4] # <% @items.each do |item| %> #
    "> # <%= item %> #
    # <% end %> def current_cycle(name = "default") cycle = get_cycle(name) cycle.current_value if cycle end # Resets a cycle so that it starts from the first element the next time # it is called. Pass in +name+ to reset a named cycle. # # # Alternate CSS classes for even and odd numbers... # @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]] # # <% @items.each do |item| %> # "> # <% item.each do |value| %> # "> # <%= value %> # # <% end %> # # <% reset_cycle("colors") %> # # <% end %> #
    def reset_cycle(name = "default") cycle = get_cycle(name) cycle.reset if cycle end class Cycle #:nodoc: attr_reader :values def initialize(first_value, *values) @values = values.unshift(first_value) reset end def reset @index = 0 end def current_value @values[previous_index].to_s end def to_s value = @values[@index].to_s @index = next_index return value end private def next_index step_index(1) end def previous_index step_index(-1) end def step_index(n) (@index + n) % @values.size end end private # The cycle helpers need to store the cycles in a place that is # guaranteed to be reset every time a page is rendered, so it # uses an instance variable of ActionView::Base. def get_cycle(name) @_cycles = Hash.new unless defined?(@_cycles) return @_cycles[name] end def set_cycle(name, cycle_object) @_cycles = Hash.new unless defined?(@_cycles) @_cycles[name] = cycle_object end def split_paragraphs(text) return [] if text.blank? text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t| t.gsub!(/([^\n]\n)(?=[^\n])/, '\1
    ') || t end end def cut_excerpt_part(part_position, part, separator, options) return "", "" unless part radius = options.fetch(:radius, 100) omission = options.fetch(:omission, "...") part = part.split(separator) part.delete("") affix = part.size > radius ? omission : "" part = if part_position == :first drop_index = [part.length - radius, 0].max part.drop(drop_index) else part.first(radius) end return affix, part.join(separator) end end end end rails-4.2.6/actionview/lib/action_view/helpers/translation_helper.rb000066400000000000000000000113421266740050600257550ustar00rootroot00000000000000require 'action_view/helpers/tag_helper' require 'active_support/core_ext/string/access' require 'i18n/exceptions' module ActionView # = Action View Translation Helpers module Helpers module TranslationHelper include TagHelper # Delegates to I18n#translate but also performs three additional functions. # # First, it will ensure that any thrown +MissingTranslation+ messages will be turned # into inline spans that: # # * have a "translation-missing" class set, # * contain the missing key as a title attribute and # * a titleized version of the last key segment as a text. # # E.g. the value returned for a missing translation key :"blog.post.title" will be # Title. # This way your views will display rather reasonable strings but it will still # be easy to spot missing translations. # # Second, it'll scope the key by the current partial if the key starts # with a period. So if you call translate(".foo") from the # people/index.html.erb template, you'll actually be calling # I18n.translate("people.index.foo"). This makes it less repetitive # to translate many keys within the same partials and gives you a simple framework # for scoping them consistently. If you don't prepend the key with a period, # nothing is converted. # # Third, it'll mark the translation as safe HTML if the key has the suffix # "_html" or the last element of the key is the word "html". For example, # calling translate("footer_html") or translate("footer.html") will return # a safe HTML string that won't be escaped by other HTML helper methods. This # naming convention helps to identify translations that include HTML tags so that # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) options = options.dup has_default = options.has_key?(:default) remaining_defaults = Array(options.delete(:default)).compact if has_default && !remaining_defaults.first.kind_of?(Symbol) options[:default] = remaining_defaults end # If the user has explicitly decided to NOT raise errors, pass that option to I18n. # Otherwise, tell I18n to raise an exception, which we rescue further in this method. # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default. if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?) raise_error = false i18n_raise = false else raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations i18n_raise = true end if html_safe_translation_key?(key) html_safe_options = options.dup options.except(*I18n::RESERVED_KEYS).each do |name, value| unless name == :count && value.is_a?(Numeric) html_safe_options[name] = ERB::Util.html_escape(value.to_s) end end translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise)) translation.respond_to?(:html_safe) ? translation.html_safe : translation else I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise)) end rescue I18n::MissingTranslationData => e if remaining_defaults.present? translate remaining_defaults.shift, options.merge(default: remaining_defaults) else raise e if raise_error keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}") end end alias :t :translate # Delegates to I18n.localize with no additional functionality. # # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize # for more information. def localize(*args) I18n.localize(*args) end alias :l :localize private def scope_key_by_partial(key) if key.to_s.first == "." if @virtual_path @virtual_path.gsub(%r{/_?}, ".") + key.to_s else raise "Cannot use t(#{key.inspect}) shortcut because path is not available" end else key end end def html_safe_translation_key?(key) key.to_s =~ /(\b|_|\.)html$/ end end end end rails-4.2.6/actionview/lib/action_view/helpers/url_helper.rb000066400000000000000000000663341266740050600242340ustar00rootroot00000000000000require 'action_view/helpers/javascript_helper' require 'active_support/core_ext/array/access' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/string/output_safety' module ActionView # = Action View URL Helpers module Helpers #:nodoc: # Provides a set of methods for making links and getting URLs that # depend on the routing subsystem (see ActionDispatch::Routing). # This allows you to use the same format for links in views # and controllers. module UrlHelper # This helper may be included in any class that includes the # URL helpers of a routes (routes.url_helpers). Some methods # provided here will only work in the context of a request # (link_to_unless_current, for instance), which must be provided # as a method called #request on the context. BUTTON_TAG_METHOD_VERBS = %w{patch put delete} extend ActiveSupport::Concern include TagHelper module ClassMethods def _url_for_modules ActionView::RoutingUrlFor end end # Basic implementation of url_for to allow use helpers without routes existence def url_for(options = nil) # :nodoc: case options when String options when :back _back_url else raise ArgumentError, "arguments passed to url_for can't be handled. Please require " + "routes or provide your own implementation" end end def _back_url # :nodoc: referrer = controller.respond_to?(:request) && controller.request.env["HTTP_REFERER"] referrer || 'javascript:history.back()' end protected :_back_url # Creates a link tag of the given +name+ using a URL created by the set of +options+. # See the valid options in the documentation for +url_for+. It's also possible to # pass a String instead of an options hash, which generates a link tag that uses the # value of the String as the href for the link. Using a :back Symbol instead # of an options hash will generate a link to the referrer (a JavaScript back link # will be used in place of a referrer if none exists). If +nil+ is passed as the name # the value of the link itself will become the name. # # ==== Signatures # # link_to(body, url, html_options = {}) # # url is a String; you can use URL helpers like # # posts_path # # link_to(body, url_options = {}, html_options = {}) # # url_options, except :method, is passed to url_for # # link_to(options = {}, html_options = {}) do # # name # end # # link_to(url, html_options = {}) do # # name # end # # ==== Options # * :data - This option can be used to add custom data attributes. # * method: symbol of HTTP verb - This modifier will dynamically # create an HTML form and immediately submit the form for processing using # the HTTP verb specified. Useful for having links perform a POST operation # in dangerous actions like deleting a record (which search bots can follow # while spidering your site). Supported verbs are :post, :delete, :patch, and :put. # Note that if the user has JavaScript disabled, the request will fall back # to using GET. If href: '#' is used and the user has JavaScript # disabled clicking the link will have no effect. If you are relying on the # POST behavior, you should check for it in your controller's action by using # the request object's methods for post?, delete?, patch?, or put?. # * remote: true - This will allow the unobtrusive JavaScript # driver to make an Ajax request to the URL in question instead of following # the link. The drivers each provide mechanisms for listening for the # completion of the Ajax request and performing JavaScript operations once # they're complete # # ==== Data attributes # # * confirm: 'question?' - This will allow the unobtrusive JavaScript # driver to prompt with the question specified (in this case, the # resulting text would be question?. If the user accepts, the # link is processed normally, otherwise no action is taken. # * :disable_with - Value of this parameter will be # used as the value for a disabled version of the submit # button when the form is submitted. This feature is provided # by the unobtrusive JavaScript driver. # # ==== Examples # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base # your application on resources and use # # link_to "Profile", profile_path(@profile) # # => Profile # # or the even pithier # # link_to "Profile", @profile # # => Profile # # in place of the older more verbose, non-resource-oriented # # link_to "Profile", controller: "profiles", action: "show", id: @profile # # => Profile # # Similarly, # # link_to "Profiles", profiles_path # # => Profiles # # is better than # # link_to "Profiles", controller: "profiles" # # => Profiles # # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: # # <%= link_to(@profile) do %> # <%= @profile.name %> -- Check it out! # <% end %> # # => # David -- Check it out! # # # Classes and ids for CSS are easy to produce: # # link_to "Articles", articles_path, id: "news", class: "article" # # => Articles # # Be careful when using the older argument style, as an extra literal hash is needed: # # link_to "Articles", { controller: "articles" }, id: "news", class: "article" # # => Articles # # Leaving the hash off gives the wrong link: # # link_to "WRONG!", controller: "articles", id: "news", class: "article" # # => WRONG! # # +link_to+ can also produce links with anchors or query strings: # # link_to "Comment wall", profile_path(@profile, anchor: "wall") # # => Comment wall # # link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails" # # => Ruby on Rails search # # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux") # # => Nonsense search # # The only option specific to +link_to+ (:method) is used as follows: # # link_to("Destroy", "http://www.example.com", method: :delete) # # => Destroy # # You can also use custom data attributes using the :data option: # # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } # # => Visit Other Site def link_to(name = nil, options = nil, html_options = nil, &block) html_options, options, name = options, name, block if block_given? options ||= {} html_options = convert_options_to_data_attributes(options, html_options) url = url_for(options) html_options['href'] ||= url content_tag(:a, name || url, html_options, &block) end # Generates a form containing a single button that submits to the URL created # by the set of +options+. This is the safest method to ensure links that # cause changes to your data are not triggered by search bots or accelerators. # If the HTML button does not work with your layout, you can also consider # using the +link_to+ method with the :method modifier as described in # the +link_to+ documentation. # # By default, the generated form element has a class name of button_to # to allow styling of the form itself and its children. This can be changed # using the :form_class modifier within +html_options+. You can control # the form submission and input element behavior using +html_options+. # This method accepts the :method modifier described in the +link_to+ documentation. # If no :method modifier is given, it will default to performing a POST operation. # You can also disable the button by passing disabled: true in +html_options+. # If you are using RESTful routes, you can pass the :method # to change the HTTP verb used to submit the form. # # ==== Options # The +options+ hash accepts the same options as +url_for+. # # There are a few special +html_options+: # * :method - Symbol of HTTP verb. Supported verbs are :post, :get, # :delete, :patch, and :put. By default it will be :post. # * :disabled - If set to true, it will generate a disabled button. # * :data - This option can be used to add custom data attributes. # * :remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the # submit behavior. By default this behavior is an ajax submit. # * :form - This hash will be form attributes # * :form_class - This controls the class of the form within which the submit button will # be placed # * :params - Hash of parameters to be rendered as hidden fields within the form. # # ==== Data attributes # # * :confirm - This will use the unobtrusive JavaScript driver to # prompt with the question specified. If the user accepts, the link is # processed normally, otherwise no action is taken. # * :disable_with - Value of this parameter will be # used as the value for a disabled version of the submit # button when the form is submitted. This feature is provided # by the unobtrusive JavaScript driver. # # ==== Examples # <%= button_to "New", action: "new" %> # # => "
    # # # #
    " # # <%= button_to "New", new_articles_path %> # # => "
    # # # #
    " # # <%= button_to [:make_happy, @user] do %> # Make happy <%= @user.name %> # <% end %> # # => "
    # # # #
    " # # <%= button_to "New", { action: "new" }, form_class: "new-thing" %> # # => "
    # # # #
    " # # # <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %> # # => "
    # # # # # #
    " # # # <%= button_to "Delete Image", { action: "delete", id: @image.id }, # method: :delete, data: { confirm: "Are you sure?" } %> # # => "
    # # # # # # # #
    " # # # <%= button_to('Destroy', 'http://www.example.com', # method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %> # # => "
    # # # # # # # #
    " # # def button_to(name = nil, options = nil, html_options = nil, &block) html_options, options = options, name if block_given? options ||= {} html_options ||= {} html_options = html_options.stringify_keys convert_boolean_attributes!(html_options, %w(disabled)) url = options.is_a?(String) ? options : url_for(options) remote = html_options.delete('remote') params = html_options.delete('params') method = html_options.delete('method').to_s method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe form_method = method == 'get' ? 'get' : 'post' form_options = html_options.delete('form') || {} form_options[:class] ||= html_options.delete('form_class') || 'button_to' form_options.merge!(method: form_method, action: url) form_options.merge!("data-remote" => "true") if remote request_token_tag = form_method == 'post' ? token_tag : '' html_options = convert_options_to_data_attributes(options, html_options) html_options['type'] = 'submit' button = if block_given? content_tag('button', html_options, &block) else html_options['value'] = name || url tag('input', html_options) end inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag) if params params.each do |param_name, value| inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param) end end content_tag('form', inner_tags, form_options) end # Creates a link tag of the given +name+ using a URL created by the set of # +options+ unless the current request URI is the same as the links, in # which case only the name is returned (or the given block is yielded, if # one exists). You can give +link_to_unless_current+ a block which will # specialize the default behavior (e.g., show a "Start Here" link rather # than the link's text). # # ==== Examples # Let's say you have a navigation menu... # # # # If in the "about" action, it will render... # # # # ...but if in the "index" action, it will render: # # # # The implicit block given to +link_to_unless_current+ is evaluated if the current # action is the action given. So, if we had a comments page and wanted to render a # "Go Back" link instead of a link to the comments page, we could do something like this... # # <%= # link_to_unless_current("Comment", { controller: "comments", action: "new" }) do # link_to("Go back", { controller: "posts", action: "index" }) # end # %> def link_to_unless_current(name, options = {}, html_options = {}, &block) link_to_unless current_page?(options), name, options, html_options, &block end # Creates a link tag of the given +name+ using a URL created by the set of # +options+ unless +condition+ is true, in which case only the name is # returned. To specialize the default behavior (i.e., show a login link rather # than just the plaintext link text), you can pass a block that # accepts the name or the full argument list for +link_to_unless+. # # ==== Examples # <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %> # # If the user is logged in... # # => Reply # # <%= # link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name| # link_to(name, { controller: "accounts", action: "signup" }) # end # %> # # If the user is logged in... # # => Reply # # If not... # # => Reply def link_to_unless(condition, name, options = {}, html_options = {}, &block) link_to_if !condition, name, options, html_options, &block end # Creates a link tag of the given +name+ using a URL created by the set of # +options+ if +condition+ is true, otherwise only the name is # returned. To specialize the default behavior, you can pass a block that # accepts the name or the full argument list for +link_to_unless+ (see the examples # in +link_to_unless+). # # ==== Examples # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %> # # If the user isn't logged in... # # => Login # # <%= # link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do # link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user }) # end # %> # # If the user isn't logged in... # # => Login # # If they are logged in... # # => my_username def link_to_if(condition, name, options = {}, html_options = {}, &block) if condition link_to(name, options, html_options) else if block_given? block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) else ERB::Util.html_escape(name) end end end # Creates a mailto link tag to the specified +email_address+, which is # also used as the name of the link unless +name+ is specified. Additional # HTML attributes for the link can be passed in +html_options+. # # +mail_to+ has several methods for customizing the email itself by # passing special keys to +html_options+. # # ==== Options # * :subject - Preset the subject line of the email. # * :body - Preset the body of the email. # * :cc - Carbon Copy additional recipients on the email. # * :bcc - Blind Carbon Copy additional recipients on the email. # # ==== Obfuscation # Prior to Rails 4.0, +mail_to+ provided options for encoding the address # in order to hinder email harvesters. To take advantage of these options, # install the +actionview-encoded_mail_to+ gem. # # ==== Examples # mail_to "me@domain.com" # # => me@domain.com # # mail_to "me@domain.com", "My email" # # => My email # # mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com", # subject: "This is an example email" # # => My email # # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: # # <%= mail_to "me@domain.com" do %> # Email me: me@domain.com # <% end %> # # => # Email me: me@domain.com # def mail_to(email_address, name = nil, html_options = {}, &block) html_options, name = name, nil if block_given? html_options = (html_options || {}).stringify_keys extras = %w{ cc bcc body subject }.map! { |item| option = html_options.delete(item) || next "#{item}=#{Rack::Utils.escape_path(option)}" }.compact extras = extras.empty? ? '' : '?' + extras.join('&') encoded_email_address = ERB::Util.url_encode(email_address ? email_address.to_str : '').gsub("%40", "@") html_options["href"] = "mailto:#{encoded_email_address}#{extras}" content_tag(:a, name || email_address, html_options, &block) end # True if the current request URI was generated by the given +options+. # # ==== Examples # Let's say we're in the http://www.example.com/shop/checkout?order=desc action. # # current_page?(action: 'process') # # => false # # current_page?(controller: 'shop', action: 'checkout') # # => true # # current_page?(controller: 'shop', action: 'checkout', order: 'asc') # # => false # # current_page?(action: 'checkout') # # => true # # current_page?(controller: 'library', action: 'checkout') # # => false # # current_page?('http://www.example.com/shop/checkout') # # => true # # current_page?('/shop/checkout') # # => true # # Let's say we're in the http://www.example.com/shop/checkout?order=desc&page=1 action. # # current_page?(action: 'process') # # => false # # current_page?(controller: 'shop', action: 'checkout') # # => true # # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1') # # => true # # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2') # # => false # # current_page?(controller: 'shop', action: 'checkout', order: 'desc') # # => false # # current_page?(action: 'checkout') # # => true # # current_page?(controller: 'library', action: 'checkout') # # => false # # Let's say we're in the http://www.example.com/products action with method POST in case of invalid product. # # current_page?(controller: 'product', action: 'index') # # => false # def current_page?(options) unless request raise "You cannot use helpers that need to determine the current " \ "page unless your view context provides a Request object " \ "in a #request method" end return false unless request.get? || request.head? url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY) # We ignore any extra parameters in the request_uri if the # submitted url doesn't have any either. This lets the function # work with things like ?order=asc request_uri = url_string.index("?") ? request.fullpath : request.path request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY) if url_string =~ /^\w+:\/\// url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" else url_string == request_uri end end private def convert_options_to_data_attributes(options, html_options) if html_options html_options = html_options.stringify_keys html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options) method = html_options.delete('method') add_method_to_attributes!(html_options, method) if method html_options else link_to_remote_options?(options) ? {'data-remote' => 'true'} : {} end end def link_to_remote_options?(options) if options.is_a?(Hash) options.delete('remote') || options.delete(:remote) end end def add_method_to_attributes!(html_options, method) if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/ html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip end html_options["data-method"] = method end # Processes the +html_options+ hash, converting the boolean # attributes from true/false form into the form required by # HTML/XHTML. (An attribute is considered to be boolean if # its name is listed in the given +bool_attrs+ array.) # # More specifically, for each boolean attribute in +html_options+ # given as: # # "attr" => bool_value # # if the associated +bool_value+ evaluates to true, it is # replaced with the attribute's name; otherwise the attribute is # removed from the +html_options+ hash. (See the XHTML 1.0 spec, # section 4.5 "Attribute Minimization" for more: # http://www.w3.org/TR/xhtml1/#h-4.5) # # Returns the updated +html_options+ hash, which is also modified # in place. # # Example: # # convert_boolean_attributes!( html_options, # %w( checked disabled readonly ) ) def convert_boolean_attributes!(html_options, bool_attrs) bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) } html_options end def token_tag(token=nil) if token != false && protect_against_forgery? token ||= form_authenticity_token tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token) else '' end end def method_tag(method) tag('input', type: 'hidden', name: '_method', value: method.to_s) end end end end rails-4.2.6/actionview/lib/action_view/layouts.rb000066400000000000000000000361321266740050600221220ustar00rootroot00000000000000require "action_view/rendering" require "active_support/core_ext/module/remove_method" module ActionView # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in # repeated setups. The inclusion pattern has pages that look like this: # # <%= render "shared/header" %> # Hello World # <%= render "shared/footer" %> # # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose # and if you ever want to change the structure of these two includes, you'll have to change all the templates. # # With layouts, you can flip it around and have the common structure know where to insert changing content. This means # that the header and footer are only mentioned in one place, like this: # # // The header part of this layout # <%= yield %> # // The footer part of this layout # # And then you have content pages that look like this: # # hello world # # At rendering time, the content page is computed and then inserted in the layout, like this: # # // The header part of this layout # hello world # // The footer part of this layout # # == Accessing shared variables # # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with # references that won't materialize before rendering time: # #

    <%= @page_title %>

    # <%= yield %> # # ...and content pages that fulfill these references _at_ rendering time: # # <% @page_title = "Welcome" %> # Off-world colonies offers you a chance to start a new life # # The result after rendering is: # #

    Welcome

    # Off-world colonies offers you a chance to start a new life # # == Layout assignment # # You can either specify a layout declaratively (using the #layout class method) or give # it the same name as your controller, and place it in app/views/layouts. # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. # # For instance, if you have PostsController and a template named app/views/layouts/posts.html.erb, # that template will be used for all actions in PostsController and controllers inheriting # from PostsController. # # If you use a module, for instance Weblog::PostsController, you will need a template named # app/views/layouts/weblog/posts.html.erb. # # Since all your controllers inherit from ApplicationController, they will use # app/views/layouts/application.html.erb if no other layout is specified # or provided. # # == Inheritance Examples # # class BankController < ActionController::Base # # bank.html.erb exists # # class ExchangeController < BankController # # exchange.html.erb exists # # class CurrencyController < BankController # # class InformationController < BankController # layout "information" # # class TellerController < InformationController # # teller.html.erb exists # # class EmployeeController < InformationController # # employee.html.erb exists # layout nil # # class VaultController < BankController # layout :access_level_layout # # class TillController < BankController # layout false # # In these examples, we have three implicit lookup scenarios: # * The BankController uses the "bank" layout. # * The ExchangeController uses the "exchange" layout. # * The CurrencyController inherits the layout from BankController. # # However, when a layout is explicitly set, the explicitly set layout wins: # * The InformationController uses the "information" layout, explicitly set. # * The TellerController also uses the "information" layout, because the parent explicitly set it. # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration. # * The VaultController chooses a layout dynamically by calling the access_level_layout method. # * The TillController does not use a layout at all. # # == Types of layouts # # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can # be done either by specifying a method reference as a symbol or using an inline method (as a proc). # # The method reference is the preferred approach to variable layouts and is used like this: # # class WeblogController < ActionController::Base # layout :writers_and_readers # # def index # # fetching posts # end # # private # def writers_and_readers # logged_in? ? "writer_layout" : "reader_layout" # end # end # # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing # is logged in or not. # # If you want to use an inline method, such as a proc, do something like this: # # class WeblogController < ActionController::Base # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } # end # # If an argument isn't given to the proc, it's evaluated in the context of # the current controller anyway. # # class WeblogController < ActionController::Base # layout proc { logged_in? ? "writer_layout" : "reader_layout" } # end # # Of course, the most common way of specifying a layout is still just as a plain template name: # # class WeblogController < ActionController::Base # layout "weblog_standard" # end # # The template will be looked always in app/views/layouts/ folder. But you can point # layouts folder direct also. layout "layouts/demo" is the same as layout "demo". # # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists. # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent: # # class ApplicationController < ActionController::Base # layout "application" # end # # class PostsController < ApplicationController # # Will use "application" layout # end # # class CommentsController < ApplicationController # # Will search for "comments" layout and fallback "application" layout # layout nil # end # # == Conditional layouts # # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The # :only and :except options can be passed to the layout call. For example: # # class WeblogController < ActionController::Base # layout "weblog_standard", except: :rss # # # ... # # end # # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will # be rendered directly, without wrapping a layout around the rendered view. # # Both the :only and :except condition can accept an arbitrary number of method references, so # #except: [ :rss, :text_only ] is valid, as is except: :rss. # # == Using a different layout in the action render call # # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. # You can do this by passing a :layout option to the render call. For example: # # class WeblogController < ActionController::Base # layout "weblog_standard" # # def help # render action: "help", layout: "help" # end # end # # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead. module Layouts extend ActiveSupport::Concern include ActionView::Rendering included do class_attribute :_layout, :_layout_conditions, :instance_accessor => false self._layout = nil self._layout_conditions = {} _write_layout_method end delegate :_layout_conditions, to: :class module ClassMethods def inherited(klass) # :nodoc: super klass._write_layout_method end # This module is mixed in if layout conditions are provided. This means # that if no layout conditions are used, this method is not used module LayoutConditions # :nodoc: private # Determines whether the current action has a layout definition by # checking the action name against the :only and :except conditions # set by the layout method. # # ==== Returns # * Boolean - True if the action has a layout definition, false otherwise. def _conditional_layout? return unless super conditions = _layout_conditions if only = conditions[:only] only.include?(action_name) elsif except = conditions[:except] !except.include?(action_name) else true end end end # Specify the layout to use for this class. # # If the specified layout is a: # String:: the String is the template name # Symbol:: call the method specified by the symbol, which will return the template name # false:: There is no layout # true:: raise an ArgumentError # nil:: Force default layout behavior with inheritance # # ==== Parameters # * layout - The layout to use. # # ==== Options (conditions) # * :only - A list of actions to apply this layout to. # * :except - Apply this layout to all actions but this one. def layout(layout, conditions = {}) include LayoutConditions unless conditions.empty? conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } self._layout_conditions = conditions self._layout = layout _write_layout_method end # Creates a _layout method to be called by _default_layout . # # If a layout is not explicitly mentioned then look for a layout with the controller's name. # if nothing is found then try same procedure to find super class's layout. def _write_layout_method # :nodoc: remove_possible_method(:_layout) prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"] default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super" name_clause = if name default_behavior else <<-RUBY super RUBY end layout_definition = case _layout when String _layout.inspect when Symbol <<-RUBY #{_layout}.tap do |layout| return #{default_behavior} if layout.nil? unless layout.is_a?(String) || !layout raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \ "should have returned a String, false, or nil" end end RUBY when Proc define_method :_layout_from_proc, &_layout protected :_layout_from_proc <<-RUBY result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'}) return #{default_behavior} if result.nil? result RUBY when false nil when true raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil" when nil name_clause end self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def _layout if _conditional_layout? #{layout_definition} else #{name_clause} end end private :_layout RUBY end private # If no layout is supplied, look for a template named the return # value of this method. # # ==== Returns # * String - A template name def _implied_layout_name # :nodoc: controller_path end end def _normalize_options(options) # :nodoc: super if _include_layout?(options) layout = options.delete(:layout) { :default } options[:layout] = _layout_for_option(layout) end end attr_internal_writer :action_has_layout def initialize(*) # :nodoc: @_action_has_layout = true super end # Controls whether an action should be rendered using a layout. # If you want to disable any layout settings for the # current action so that it is rendered without a layout then # either override this method in your controller to return false # for that action or set the action_has_layout attribute # to false before rendering. def action_has_layout? @_action_has_layout end private def _conditional_layout? true end # This will be overwritten by _write_layout_method def _layout; end # Determine the layout for a given name, taking into account the name type. # # ==== Parameters # * name - The name of the template def _layout_for_option(name) case name when String then _normalize_layout(name) when Proc then name when true then Proc.new { _default_layout(true) } when :default then Proc.new { _default_layout(false) } when false, nil then nil else raise ArgumentError, "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}" end end def _normalize_layout(value) value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value end # Returns the default layout for this controller. # Optionally raises an exception if the layout could not be found. # # ==== Parameters # * require_layout - If set to true and layout is not found, # an ArgumentError exception is raised (defaults to false) # # ==== Returns # * template - The template object for the default layout (or nil) def _default_layout(require_layout = false) begin value = _layout if action_has_layout? rescue NameError => e raise e, "Could not render layout: #{e.message}" end if require_layout && action_has_layout? && !value raise ArgumentError, "There was no default layout for #{self.class} in #{view_paths.inspect}" end _normalize_layout(value) end def _include_layout?(options) (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout) end end end rails-4.2.6/actionview/lib/action_view/locale/000077500000000000000000000000001266740050600213275ustar00rootroot00000000000000rails-4.2.6/actionview/lib/action_view/locale/en.yml000066400000000000000000000030351266740050600224550ustar00rootroot00000000000000"en": # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() datetime: distance_in_words: half_a_minute: "half a minute" less_than_x_seconds: one: "less than 1 second" other: "less than %{count} seconds" x_seconds: one: "1 second" other: "%{count} seconds" less_than_x_minutes: one: "less than a minute" other: "less than %{count} minutes" x_minutes: one: "1 minute" other: "%{count} minutes" about_x_hours: one: "about 1 hour" other: "about %{count} hours" x_days: one: "1 day" other: "%{count} days" about_x_months: one: "about 1 month" other: "about %{count} months" x_months: one: "1 month" other: "%{count} months" about_x_years: one: "about 1 year" other: "about %{count} years" over_x_years: one: "over 1 year" other: "over %{count} years" almost_x_years: one: "almost 1 year" other: "almost %{count} years" prompts: year: "Year" month: "Month" day: "Day" hour: "Hour" minute: "Minute" second: "Seconds" helpers: select: # Default value for :prompt => true in FormOptionsHelper prompt: "Please select" # Default translation keys for submit and button FormHelper submit: create: 'Create %{model}' update: 'Update %{model}' submit: 'Save %{model}' rails-4.2.6/actionview/lib/action_view/log_subscriber.rb000066400000000000000000000020221266740050600234150ustar00rootroot00000000000000require 'active_support/log_subscriber' module ActionView # = Action View Log Subscriber # # Provides functionality so that Rails can output logs from Action View. class LogSubscriber < ActiveSupport::LogSubscriber VIEWS_PATTERN = /^app\/views\// def initialize @root = nil super end def render_template(event) info do message = " Rendered #{from_rails_root(event.payload[:identifier])}" message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] message << " (#{event.duration.round(1)}ms)" end end alias :render_partial :render_template alias :render_collection :render_template def logger ActionView::Base.logger end protected EMPTY = '' def from_rails_root(string) string = string.sub(rails_root, EMPTY) string.sub!(VIEWS_PATTERN, EMPTY) string end def rails_root @root ||= "#{Rails.root}/" end end end ActionView::LogSubscriber.attach_to :action_view rails-4.2.6/actionview/lib/action_view/lookup_context.rb000066400000000000000000000174371266740050600235060ustar00rootroot00000000000000require 'thread_safe' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/module/attribute_accessors' require 'action_view/template/resolver' module ActionView # = Action View Lookup Context # # LookupContext is the object responsible to hold all information required to lookup # templates, i.e. view paths and details. The LookupContext is also responsible to # generate a key, given to view paths, used in the resolver cache lookup. Since # this key is generated just once during the request, it speeds up all cache accesses. class LookupContext #:nodoc: attr_accessor :prefixes, :rendered_format mattr_accessor :fallbacks @@fallbacks = FallbackFileSystemResolver.instances mattr_accessor :registered_details self.registered_details = [] def self.register_detail(name, options = {}, &block) self.registered_details << name initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" } Accessors.send :define_method, :"default_#{name}", &block Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{name} @details.fetch(:#{name}, []) end def #{name}=(value) value = value.present? ? Array(value) : default_#{name} _set_detail(:#{name}, value) if value != @details[:#{name}] end remove_possible_method :initialize_details def initialize_details(details) #{initialize.join("\n")} end METHOD end # Holds accessors for the registered details. module Accessors #:nodoc: end register_detail(:locale) do locales = [I18n.locale] locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks locales << I18n.default_locale locales.uniq! locales end register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } register_detail(:variants) { [] } register_detail(:handlers){ Template::Handlers.extensions } class DetailsKey #:nodoc: alias :eql? :equal? alias :object_hash :hash attr_reader :hash @details_keys = ThreadSafe::Cache.new def self.get(details) if details[:formats] details = details.dup details[:formats] &= Mime::SET.symbols end @details_keys[details] ||= new end def self.clear @details_keys.clear end def initialize @hash = object_hash end end # Add caching behavior on top of Details. module DetailsCache attr_accessor :cache # Calculate the details key. Remove the handlers from calculation to improve performance # since the user cannot modify it explicitly. def details_key #:nodoc: @details_key ||= DetailsKey.get(@details) if @cache end # Temporary skip passing the details_key forward. def disable_cache old_value, @cache = @cache, false yield ensure @cache = old_value end protected def _set_detail(key, value) @details = @details.dup if @details_key @details_key = nil @details[key] = value end end # Helpers related to template lookup using the lookup context information. module ViewPaths attr_reader :view_paths, :html_fallback_for_js # Whenever setting view paths, makes a copy so that we can manipulate them in # instance objects as we wish. def view_paths=(paths) @view_paths = ActionView::PathSet.new(Array(paths)) end def find(name, prefixes = [], partial = false, keys = [], options = {}) @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options)) end alias :find_template :find def find_file(name, prefixes = [], partial = false, keys = [], options = {}) @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options)) end def find_all(name, prefixes = [], partial = false, keys = [], options = {}) @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options)) end def exists?(name, prefixes = [], partial = false, keys = [], options = {}) @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options)) end alias :template_exists? :exists? # Adds fallbacks to the view paths. Useful in cases when you are rendering # a :file. def with_fallbacks added_resolvers = 0 self.class.fallbacks.each do |resolver| next if view_paths.include?(resolver) view_paths.push(resolver) added_resolvers += 1 end yield ensure added_resolvers.times { view_paths.pop } end protected def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc: name, prefixes = normalize_name(name, prefixes) details, details_key = detail_args_for(details_options) [name, prefixes, partial || false, details, details_key, keys] end # Compute details hash and key according to user options (e.g. passed from #render). def detail_args_for(options) return @details, details_key if options.empty? # most common path. user_details = @details.merge(options) if @cache details_key = DetailsKey.get(user_details) else details_key = nil end [user_details, details_key] end # Support legacy foo.erb names even though we now ignore .erb # as well as incorrectly putting part of the path in the template # name instead of the prefix. def normalize_name(name, prefixes) #:nodoc: prefixes = prefixes.presence parts = name.to_s.split('/') parts.shift if parts.first.empty? name = parts.pop return name, prefixes || [""] if parts.empty? parts = parts.join('/') prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts] return name, prefixes end end include Accessors include DetailsCache include ViewPaths def initialize(view_paths, details = {}, prefixes = []) @details, @details_key = {}, nil @skip_default_locale = false @cache = true @prefixes = prefixes @rendered_format = nil self.view_paths = view_paths initialize_details(details) end # Override formats= to expand ["*/*"] values and automatically # add :html as fallback to :js. def formats=(values) if values values.concat(default_formats) if values.delete "*/*" if values == [:js] values << :html @html_fallback_for_js = true end end super(values) end # Do not use the default locale on template lookup. def skip_default_locale! @skip_default_locale = true self.locale = nil end # Override locale to return a symbol instead of array. def locale @details[:locale].first end # Overload locale= to also set the I18n.locale. If the current I18n.config object responds # to original_config, it means that it has a copy of the original I18n configuration and it's # acting as proxy, which we need to skip. def locale=(value) if value config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config config.locale = value end super(@skip_default_locale ? I18n.locale : default_locale) end # Uses the first format in the formats array for layout lookup. def with_layout_format if formats.size == 1 yield else old_formats = formats _set_detail(:formats, formats[0,1]) begin yield ensure _set_detail(:formats, old_formats) end end end end end rails-4.2.6/actionview/lib/action_view/model_naming.rb000066400000000000000000000005171266740050600230510ustar00rootroot00000000000000module ActionView module ModelNaming # Converts the given object to an ActiveModel compliant one. def convert_to_model(object) object.respond_to?(:to_model) ? object.to_model : object end def model_name_from_record_or_class(record_or_class) convert_to_model(record_or_class).model_name end end end rails-4.2.6/actionview/lib/action_view/path_set.rb000066400000000000000000000041351266740050600222270ustar00rootroot00000000000000module ActionView #:nodoc: # = Action View PathSet # # This class is used to store and access paths in Action View. A number of # operations are defined so that you can search among the paths in this # set and also perform operations on other +PathSet+ objects. # # A +LookupContext+ will use a +PathSet+ to store the paths in its context. class PathSet #:nodoc: include Enumerable attr_reader :paths delegate :[], :include?, :pop, :size, :each, to: :paths def initialize(paths = []) @paths = typecast paths end def initialize_copy(other) @paths = other.paths.dup self end def to_ary paths.dup end def compact PathSet.new paths.compact end def +(array) PathSet.new(paths + array) end %w(<< concat push insert unshift).each do |method| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(*args) paths.#{method}(*typecast(args)) end METHOD end def find(*args) find_all(*args).first || raise(MissingTemplate.new(self, *args)) end def find_file(path, prefixes = [], *args) _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args)) end def find_all(path, prefixes = [], *args) _find_all path, prefixes, args, false end def exists?(path, prefixes, *args) find_all(path, prefixes, *args).any? end private def _find_all(path, prefixes, args, outside_app) prefixes = [prefixes] if String === prefixes prefixes.each do |prefix| paths.each do |resolver| if outside_app templates = resolver.find_all_anywhere(path, prefix, *args) else templates = resolver.find_all(path, prefix, *args) end return templates unless templates.empty? end end [] end def typecast(paths) paths.map do |path| case path when Pathname, String OptimizedFileSystemResolver.new path.to_s else path end end end end end rails-4.2.6/actionview/lib/action_view/railtie.rb000066400000000000000000000027161266740050600220540ustar00rootroot00000000000000require "action_view" require "rails" module ActionView # = Action View Railtie class Railtie < Rails::Railtie # :nodoc: config.action_view = ActiveSupport::OrderedOptions.new config.action_view.embed_authenticity_token_in_remote_forms = false config.eager_load_namespaces << ActionView initializer "action_view.embed_authenticity_token_in_remote_forms" do |app| ActiveSupport.on_load(:action_view) do ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = app.config.action_view.delete(:embed_authenticity_token_in_remote_forms) end end initializer "action_view.logger" do ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger } end initializer "action_view.set_configs" do |app| ActiveSupport.on_load(:action_view) do app.config.action_view.each do |k,v| send "#{k}=", v end end end initializer "action_view.caching" do |app| ActiveSupport.on_load(:action_view) do if app.config.action_view.cache_template_loading.nil? ActionView::Resolver.caching = app.config.cache_classes end end end initializer "action_view.setup_action_pack" do |app| ActiveSupport.on_load(:action_controller) do ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) end end rake_tasks do load "action_view/tasks/dependencies.rake" end end end rails-4.2.6/actionview/lib/action_view/record_identifier.rb000066400000000000000000000066501266740050600241040ustar00rootroot00000000000000require 'active_support/core_ext/module' require 'action_view/model_naming' module ActionView # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to # a higher logical level. # # # routes # resources :posts # # # view # <%= div_for(post) do %>
    # <%= post.body %> What a wonderful world! # <% end %>
    # # # controller # def update # post = Post.find(params[:id]) # post.update(params[:post]) # # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) # end # # As the example above shows, you can stop caring to a large extent what the actual id of the post is. # You just know that one is being assigned and that the subsequent calls in redirect_to expect that # same naming convention and allows you to write less code if you follow it. module RecordIdentifier extend self extend ModelNaming include ModelNaming JOIN = '_'.freeze NEW = 'new'.freeze # The DOM class convention is to use the singular form of an object or class. # # dom_class(post) # => "post" # dom_class(Person) # => "person" # # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class: # # dom_class(post, :edit) # => "edit_post" # dom_class(Person, :edit) # => "edit_person" def dom_class(record_or_class, prefix = nil) singular = model_name_from_record_or_class(record_or_class).param_key prefix ? "#{prefix}#{JOIN}#{singular}" : singular end # The DOM id convention is to use the singular form of an object or class with the id following an underscore. # If no id is found, prefix with "new_" instead. # # dom_id(Post.find(45)) # => "post_45" # dom_id(Post.new) # => "new_post" # # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: # # dom_id(Post.find(45), :edit) # => "edit_post_45" # dom_id(Post.new, :custom) # => "custom_post" def dom_id(record, prefix = nil) if record_id = record_key_for_dom_id(record) "#{dom_class(record, prefix)}#{JOIN}#{record_id}" else dom_class(record, prefix || NEW) end end protected # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. # This can be overwritten to customize the default generated string representation if desired. # If you need to read back a key from a dom_id in order to query for the underlying database record, # you should write a helper like 'person_record_from_dom_id' that will extract the key either based # on the default implementation (which just joins all key attributes with '_') or on your own # overwritten version of the method. By default, this implementation passes the key string through a # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to # make sure yourself that your dom ids are valid, in case you overwrite this method. def record_key_for_dom_id(record) key = convert_to_model(record).to_key key ? key.join('_') : key end end end rails-4.2.6/actionview/lib/action_view/renderer/000077500000000000000000000000001266740050600216765ustar00rootroot00000000000000rails-4.2.6/actionview/lib/action_view/renderer/abstract_renderer.rb000066400000000000000000000032211266740050600257120ustar00rootroot00000000000000module ActionView # This class defines the interface for a renderer. Each class that # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to # render a specific type of object. # # The base +Renderer+ class uses its +render+ method to delegate to the # renderers. These currently consist of # # PartialRenderer - Used for rendering partials # TemplateRenderer - Used for rendering other types of templates # StreamingTemplateRenderer - Used for streaming # # Whenever the +render+ method is called on the base +Renderer+ class, a new # renderer object of the correct type is created, and the +render+ method on # that new object is called in turn. This abstracts the setup and rendering # into a separate classes for partials and templates. class AbstractRenderer #:nodoc: delegate :find_template, :find_file, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context def initialize(lookup_context) @lookup_context = lookup_context end def render raise NotImplementedError end protected def extract_details(options) @lookup_context.registered_details.each_with_object({}) do |key, details| value = options[key] details[key] = Array(value) if value end end def instrument(name, options={}) ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield } end def prepend_formats(formats) formats = Array(formats) return if formats.empty? || @lookup_context.html_fallback_for_js @lookup_context.formats = formats | @lookup_context.formats end end end rails-4.2.6/actionview/lib/action_view/renderer/partial_renderer.rb000066400000000000000000000445451266740050600255610ustar00rootroot00000000000000require 'thread_safe' module ActionView class PartialIteration # The number of iterations that will be done by the partial. attr_reader :size # The current iteration of the partial. attr_reader :index def initialize(size) @size = size @index = 0 end # Check if this is the first iteration of the partial. def first? index == 0 end # Check if this is the last iteration of the partial. def last? index == size - 1 end def iterate! # :nodoc: @index += 1 end end # = Action View Partials # # There's also a convenience method for rendering sub templates within the current controller that depends on a # single object (we call this kind of sub templates for partials). It relies on the fact that partials should # follow the naming convention of being prefixed with an underscore -- as to separate them from regular # templates that could be rendered on their own. # # In a template for Advertiser#account: # # <%= render partial: "account" %> # # This would render "advertiser/_account.html.erb". # # In another template for Advertiser#buy, we could have: # # <%= render partial: "account", locals: { account: @buyer } %> # # <% @advertisements.each do |ad| %> # <%= render partial: "ad", locals: { ad: ad } %> # <% end %> # # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. # # == The :as and :object options # # By default ActionView::PartialRenderer doesn't have any local variables. # The :object option can be used to pass an object to the partial. For instance: # # <%= render partial: "account", object: @buyer %> # # would provide the @buyer object to the partial, available under the local variable +account+ and is # equivalent to: # # <%= render partial: "account", locals: { account: @buyer } %> # # With the :as option we can specify a different name for said local variable. For example, if we # wanted it to be +user+ instead of +account+ we'd do: # # <%= render partial: "account", object: @buyer, as: 'user' %> # # This is equivalent to # # <%= render partial: "account", locals: { user: @buyer } %> # # == Rendering a collection of partials # # The example of partial use describes a familiar pattern where a template needs to iterate over an array and # render a sub template for each of the elements. This pattern has been implemented as a single method that # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined # example in "Using partials" can be rewritten with a single line: # # <%= render partial: "ad", collection: @advertisements %> # # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An # iteration object will automatically be made available to the template with a name of the form # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in # the collection and the total size of the collection. The iteration object also has two convenience methods, # +first?+ and +last?+. In the case of the example above, the template would be fed +ad_iteration+. # For backwards compatibility the +partial_name_counter+ is still present and is mapped to the iteration's # +index+ method. # # The :as option may be used when rendering partials. # # You can specify a partial to be rendered between elements via the :spacer_template option. # The following example will render advertiser/_ad_divider.html.erb between each ad partial: # # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %> # # If the given :collection is nil or empty, render will return nil. This will allow you # to specify a text which will displayed instead by using this form: # # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %> # # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also # just keep domain objects, like Active Records, in there. # # == Rendering shared partials # # Two controllers can share a set of partials and render them like this: # # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %> # # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from. # # == Rendering objects that respond to `to_partial_path` # # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work # and pick the proper path by checking `to_partial_path` method. # # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: # # <%= render partial: "accounts/account", locals: { account: @account} %> # <%= render partial: @account %> # # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`, # # that's why we can replace: # # <%= render partial: "posts/post", collection: @posts %> # <%= render partial: @posts %> # # == Rendering the default case # # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand # defaults of render to render partials. Examples: # # # Instead of <%= render partial: "account" %> # <%= render "account" %> # # # Instead of <%= render partial: "account", locals: { account: @buyer } %> # <%= render "account", account: @buyer %> # # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: # # <%= render partial: "accounts/account", locals: { account: @account} %> # <%= render @account %> # # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`, # # that's why we can replace: # # <%= render partial: "posts/post", collection: @posts %> # <%= render @posts %> # # == Rendering partials with layouts # # Partials can have their own layouts applied to them. These layouts are different than the ones that are # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types # of users: # # <%# app/views/users/index.html.erb &> # Here's the administrator: # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %> # # Here's the editor: # <%= render partial: "user", layout: "editor", locals: { user: editor } %> # # <%# app/views/users/_user.html.erb &> # Name: <%= user.name %> # # <%# app/views/users/_administrator.html.erb &> #
    # Budget: $<%= user.budget %> # <%= yield %> #
    # # <%# app/views/users/_editor.html.erb &> #
    # Deadline: <%= user.deadline %> # <%= yield %> #
    # # ...this will return: # # Here's the administrator: #
    # Budget: $<%= user.budget %> # Name: <%= user.name %> #
    # # Here's the editor: #
    # Deadline: <%= user.deadline %> # Name: <%= user.name %> #
    # # If a collection is given, the layout will be rendered once for each item in # the collection. For example, these two snippets have the same output: # # <%# app/views/users/_user.html.erb %> # Name: <%= user.name %> # # <%# app/views/users/index.html.erb %> # <%# This does not use layouts %> #
      # <% users.each do |user| -%> #
    • # <%= render partial: "user", locals: { user: user } %> #
    • # <% end -%> #
    # # <%# app/views/users/_li_layout.html.erb %> #
  • # <%= yield %> #
  • # # <%# app/views/users/index.html.erb %> #
      # <%= render partial: "user", layout: "li_layout", collection: users %> #
    # # Given two users whose names are Alice and Bob, these snippets return: # #
      #
    • # Name: Alice #
    • #
    • # Name: Bob #
    • #
    # # The current object being rendered, as well as the object_counter, will be # available as local variables inside the layout template under the same names # as available in the partial. # # You can also apply a layout to a block within any template: # # <%# app/views/users/_chief.html.erb &> # <%= render(layout: "administrator", locals: { user: chief }) do %> # Title: <%= chief.title %> # <% end %> # # ...this will return: # #
    # Budget: $<%= user.budget %> # Title: <%= chief.name %> #
    # # As you can see, the :locals hash is shared between both the partial and its layout. # # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass # an array to layout and treat it as an enumerable. # # <%# app/views/users/_user.html.erb &> #
    # Budget: $<%= user.budget %> # <%= yield user %> #
    # # <%# app/views/users/index.html.erb &> # <%= render layout: @users do |user| %> # Title: <%= user.title %> # <% end %> # # This will render the layout for each user and yield to the block, passing the user, each time. # # You can also yield multiple times in one layout and use block arguments to differentiate the sections. # # <%# app/views/users/_user.html.erb &> #
    # <%= yield user, :header %> # Budget: $<%= user.budget %> # <%= yield user, :footer %> #
    # # <%# app/views/users/index.html.erb &> # <%= render layout: @users do |user, section| %> # <%- case section when :header -%> # Title: <%= user.title %> # <%- when :footer -%> # Deadline: <%= user.deadline %> # <%- end -%> # <% end %> class PartialRenderer < AbstractRenderer PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k| h[k] = ThreadSafe::Cache.new end def initialize(*) super @context_prefix = @lookup_context.prefixes.first end def render(context, options, block) setup(context, options, block) identifier = (@template = find_partial) ? @template.identifier : @path @lookup_context.rendered_format ||= begin if @template && @template.formats.present? @template.formats.first else formats.first end end if @collection instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do render_collection end else instrument(:partial, :identifier => identifier) do render_partial end end end private def render_collection return nil if @collection.blank? if @options.key?(:spacer_template) spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals) end result = @template ? collection_with_template : collection_without_template result.join(spacer).html_safe end def render_partial view, locals, block = @view, @locals, @block object, as = @object, @variable if !block && (layout = @options[:layout]) layout = find_template(layout.to_s, @template_keys) end object = locals[as] if object.nil? # Respect object when object is false locals[as] = object content = @template.render(view, locals) do |*name| view._layout_for(*name, &block) end content = layout.render(view, locals){ content } if layout content end private # Sets up instance variables needed for rendering a partial. This method # finds the options and details and extracts them. The method also contains # logic that handles the type of object passed in as the partial. # # If +options[:partial]+ is a string, then the +@path+ instance variable is # set to that string. Otherwise, the +options[:partial]+ object must # respond to +to_partial_path+ in order to setup the path. def setup(context, options, block) @view = context @options = options @block = block @locals = options[:locals] || {} @details = extract_details(options) prepend_formats(options[:formats]) partial = options[:partial] if String === partial @has_object = options.key?(:object) @object = options[:object] @collection = collection_from_options @path = partial else @has_object = true @object = partial @collection = collection_from_object || collection_from_options if @collection paths = @collection_data = @collection.map { |o| partial_path(o) } @path = paths.uniq.one? ? paths.first : nil else @path = partial_path end end if as = options[:as] raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/ as = as.to_sym end if @path @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as) @template_keys = retrieve_template_keys else paths.map! { |path| retrieve_variable(path, as).unshift(path) } end self end def collection_from_options if @options.key?(:collection) collection = @options[:collection] collection.respond_to?(:to_ary) ? collection.to_ary : [] end end def collection_from_object @object.to_ary if @object.respond_to?(:to_ary) end def find_partial find_template(@path, @template_keys) if @path end def find_template(path, locals) prefixes = path.include?(?/) ? [] : @lookup_context.prefixes @lookup_context.find_template(path, prefixes, true, locals, @details) end def collection_with_template view, locals, template = @view, @locals, @template as, counter, iteration = @variable, @variable_counter, @variable_iteration if layout = @options[:layout] layout = find_template(layout, @template_keys) end partial_iteration = PartialIteration.new(@collection.size) locals[iteration] = partial_iteration @collection.map do |object| locals[as] = object locals[counter] = partial_iteration.index content = template.render(view, locals) content = layout.render(view, locals) { content } if layout partial_iteration.iterate! content end end def collection_without_template view, locals, collection_data = @view, @locals, @collection_data cache = {} keys = @locals.keys partial_iteration = PartialIteration.new(@collection.size) @collection.map do |object| index = partial_iteration.index path, as, counter, iteration = collection_data[index] locals[as] = object locals[counter] = index locals[iteration] = partial_iteration template = (cache[path] ||= find_template(path, keys + [as, counter])) content = template.render(view, locals) partial_iteration.iterate! content end end # Obtains the path to where the object's partial is located. If the object # responds to +to_partial_path+, then +to_partial_path+ will be called and # will provide the path. If the object does not respond to +to_partial_path+, # then an +ArgumentError+ is raised. # # If +prefix_partial_path_with_controller_namespace+ is true, then this # method will prefix the partial paths with a namespace. def partial_path(object = @object) object = object.to_model if object.respond_to?(:to_model) path = if object.respond_to?(:to_partial_path) object.to_partial_path else raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") end if @view.prefix_partial_path_with_controller_namespace prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) else path end end def prefixed_partial_names @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix] end def merge_prefix_into_object_path(prefix, object_path) if prefix.include?(?/) && object_path.include?(?/) prefixes = [] prefix_array = File.dirname(prefix).split('/') object_path_array = object_path.split('/')[0..-3] # skip model dir & partial prefix_array.each_with_index do |dir, index| break if dir == object_path_array[index] prefixes << dir end (prefixes << object_path).join("/") else object_path end end def retrieve_template_keys keys = @locals.keys keys << @variable if @has_object || @collection if @collection keys << @variable_counter keys << @variable_iteration end keys end def retrieve_variable(path, as) variable = as || begin base = path[-1] == "/" ? "" : File.basename(path) raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/ $1.to_sym end if @collection variable_counter = :"#{variable}_counter" variable_iteration = :"#{variable}_iteration" end [variable, variable_counter, variable_iteration] end IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " + "make sure your partial name starts with underscore, " + "and is followed by any combination of letters, numbers and underscores." OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " + "make sure it starts with lowercase letter, " + "and is followed by any combination of letters, numbers and underscores." def raise_invalid_identifier(path) raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path)) end def raise_invalid_option_as(as) raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as)) end end end rails-4.2.6/actionview/lib/action_view/renderer/renderer.rb000066400000000000000000000035661266740050600240430ustar00rootroot00000000000000module ActionView # This is the main entry point for rendering. It basically delegates # to other objects like TemplateRenderer and PartialRenderer which # actually renders the template. # # The Renderer will parse the options from the +render+ or +render_body+ # method and render a partial or a template based on the options. The # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all # the setup and logic necessary to render a view and a new object is created # each time +render+ is called. class Renderer attr_accessor :lookup_context def initialize(lookup_context) @lookup_context = lookup_context end # Main render entry point shared by AV and AC. def render(context, options) if options.respond_to?(:permitted?) && !options.permitted? raise ArgumentError, "render parameters are not permitted" end if options.key?(:partial) render_partial(context, options) else render_template(context, options) end end # Render but returns a valid Rack body. If fibers are defined, we return # a streaming body that renders the template piece by piece. # # Note that partials are not supported to be rendered with streaming, # so in such cases, we just wrap them in an array. def render_body(context, options) if options.key?(:partial) [render_partial(context, options)] else StreamingTemplateRenderer.new(@lookup_context).render(context, options) end end # Direct accessor to template rendering. def render_template(context, options) #:nodoc: TemplateRenderer.new(@lookup_context).render(context, options) end # Direct access to partial rendering. def render_partial(context, options, &block) #:nodoc: PartialRenderer.new(@lookup_context).render(context, options, block) end end end rails-4.2.6/actionview/lib/action_view/renderer/streaming_template_renderer.rb000066400000000000000000000072111266740050600277760ustar00rootroot00000000000000require 'fiber' module ActionView # == TODO # # * Support streaming from child templates, partials and so on. # * Integrate exceptions with exceptron # * Rack::Cache needs to support streaming bodies class StreamingTemplateRenderer < TemplateRenderer #:nodoc: # A valid Rack::Body (i.e. it responds to each). # It is initialized with a block that, when called, starts # rendering the template. class Body #:nodoc: def initialize(&start) @start = start end def each(&block) begin @start.call(block) rescue Exception => exception log_error(exception) block.call ActionView::Base.streaming_completion_on_exception end self end private # This is the same logging logic as in ShowExceptions middleware. # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron. def log_error(exception) #:nodoc: logger = ActionView::Base.logger return unless logger message = "\n#{exception.class} (#{exception.message}):\n" message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) message << " " << exception.backtrace.join("\n ") logger.fatal("#{message}\n\n") end end # For streaming, instead of rendering a given a template, we return a Body # object that responds to each. This object is initialized with a block # that knows how to render the template. def render_template(template, layout_name = nil, locals = {}) #:nodoc: return [super] unless layout_name && template.supports_streaming? locals ||= {} layout = layout_name && find_layout(layout_name, locals.keys) Body.new do |buffer| delayed_render(buffer, template, layout, @view, locals) end end private def delayed_render(buffer, template, layout, view, locals) # Wrap the given buffer in the StreamingBuffer and pass it to the # underlying template handler. Now, every time something is concatenated # to the buffer, it is not appended to an array, but streamed straight # to the client. output = ActionView::StreamingBuffer.new(buffer) yielder = lambda { |*name| view._layout_for(*name) } instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do fiber = Fiber.new do if layout layout.render(view, locals, output, &yielder) else # If you don't have a layout, just render the thing # and concatenate the final result. This is the same # as a layout with just <%= yield %> output.safe_concat view._layout_for end end # Set the view flow to support streaming. It will be aware # when to stop rendering the layout because it needs to search # something in the template and vice-versa. view.view_flow = StreamingFlow.new(view, fiber) # Yo! Start the fiber! fiber.resume # If the fiber is still alive, it means we need something # from the template, so start rendering it. If not, it means # the layout exited without requiring anything from the template. if fiber.alive? content = template.render(view, locals, &yielder) # Once rendering the template is done, sets its content in the :layout key. view.view_flow.set(:layout, content) # In case the layout continues yielding, we need to resume # the fiber until all yields are handled. fiber.resume while fiber.alive? end end end end end rails-4.2.6/actionview/lib/action_view/renderer/template_renderer.rb000066400000000000000000000066561266740050600257410ustar00rootroot00000000000000require 'active_support/core_ext/object/try' module ActionView class TemplateRenderer < AbstractRenderer #:nodoc: def render(context, options) @view = context @details = extract_details(options) template = determine_template(options) prepend_formats(template.formats) @lookup_context.rendered_format ||= (template.formats.first || formats.first) render_template(template, options[:layout], options[:locals]) end private # Determine the template to be rendered using the given options. def determine_template(options) keys = options.has_key?(:locals) ? options[:locals].keys : [] if options.key?(:body) Template::Text.new(options[:body]) elsif options.key?(:text) Template::Text.new(options[:text], formats.first) elsif options.key?(:plain) Template::Text.new(options[:plain]) elsif options.key?(:html) Template::HTML.new(options[:html], formats.first) elsif options.key?(:file) with_fallbacks { find_file(options[:file], nil, false, keys, @details) } elsif options.key?(:inline) handler = Template.handler_for_extension(options[:type] || "erb") Template.new(options[:inline], "inline template", handler, :locals => keys) elsif options.key?(:template) if options[:template].respond_to?(:render) options[:template] else find_template(options[:template], options[:prefixes], false, keys, @details) end else raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option." end end # Renders the given template. A string representing the layout can be # supplied as well. def render_template(template, layout_name = nil, locals = nil) #:nodoc: view, locals = @view, locals || {} render_with_layout(layout_name, locals) do |layout| instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do template.render(view, locals) { |*name| view._layout_for(*name) } end end end def render_with_layout(path, locals) #:nodoc: layout = path && find_layout(path, locals.keys) content = yield(layout) if layout view = @view view.view_flow.set(:layout, content) layout.render(view, locals){ |*name| view._layout_for(*name) } else content end end # This is the method which actually finds the layout using details in the lookup # context object. If no layout is found, it checks if at least a layout with # the given name exists across all details before raising the error. def find_layout(layout, keys) with_layout_format { resolve_layout(layout, keys) } end def resolve_layout(layout, keys) case layout when String begin if layout =~ /^\// with_fallbacks { find_template(layout, nil, false, keys, @details) } else find_template(layout, nil, false, keys, @details) end rescue ActionView::MissingTemplate all_details = @details.merge(:formats => @lookup_context.default_formats) raise unless template_exists?(layout, nil, false, keys, all_details) end when Proc resolve_layout(layout.call, keys) when FalseClass nil else layout end end end end rails-4.2.6/actionview/lib/action_view/rendering.rb000066400000000000000000000101571266740050600223760ustar00rootroot00000000000000require "action_view/view_paths" module ActionView # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, # it will trigger the lookup_context and consequently expire the cache. class I18nProxy < ::I18n::Config #:nodoc: attr_reader :original_config, :lookup_context def initialize(original_config, lookup_context) original_config = original_config.original_config if original_config.respond_to?(:original_config) @original_config, @lookup_context = original_config, lookup_context end def locale @original_config.locale end def locale=(value) @lookup_context.locale = value end end module Rendering extend ActiveSupport::Concern include ActionView::ViewPaths # Overwrite process to setup I18n proxy. def process(*) #:nodoc: old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) super ensure I18n.config = old_config end module ClassMethods def view_context_class @view_context_class ||= begin supports_path = supports_path? routes = respond_to?(:_routes) && _routes helpers = respond_to?(:_helpers) && _helpers Class.new(ActionView::Base) do if routes include routes.url_helpers(supports_path) include routes.mounted_helpers end if helpers include helpers end end end end end attr_internal_writer :view_context_class def view_context_class @_view_context_class ||= self.class.view_context_class end # An instance of a view class. The default view class is ActionView::Base # # The view class must have the following methods: # View.new[lookup_context, assigns, controller] # Create a new ActionView instance for a controller and we can also pass the arguments. # View#render(option) # Returns String with the rendered template # # Override this method in a module to change the default behavior. def view_context view_context_class.new(view_renderer, view_assigns, self) end # Returns an object that is able to render templates. # :api: private def view_renderer @_view_renderer ||= ActionView::Renderer.new(lookup_context) end def render_to_body(options = {}) _process_options(options) _render_template(options) end def rendered_format Mime[lookup_context.rendered_format] end private # Find and render a template based on the options given. # :api: private def _render_template(options) #:nodoc: variant = options[:variant] lookup_context.rendered_format = nil if options[:formats] lookup_context.variants = variant if variant view_renderer.render(view_context, options) end # Assign the rendered format to lookup context. def _process_format(format, options = {}) #:nodoc: super lookup_context.formats = [format.to_sym] lookup_context.rendered_format = lookup_context.formats.first end # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :template => "foo/bar". # :api: private def _normalize_args(action=nil, options={}) options = super(action, options) case action when NilClass when Hash options = action when String, Symbol action = action.to_s key = action.include?(?/) ? :template : :action options[key] = action else options[:partial] = action end options end # Normalize options. # :api: private def _normalize_options(options) options = super(options) if options[:partial] == true options[:partial] = action_name end if (options.keys & [:partial, :file, :template]).empty? options[:prefixes] ||= _prefixes end options[:template] ||= (options[:action] || action_name).to_s options end end end rails-4.2.6/actionview/lib/action_view/routing_url_for.rb000066400000000000000000000120641266740050600236370ustar00rootroot00000000000000require 'action_dispatch/routing/polymorphic_routes' module ActionView module RoutingUrlFor # Returns the URL for the set of +options+ provided. This takes the # same options as +url_for+ in Action Controller (see the # documentation for ActionController::Base#url_for). Note that by default # :only_path is true so you'll get the relative "/controller/action" # instead of the fully qualified URL like "http://example.com/controller/action". # # ==== Options # * :anchor - Specifies the anchor name to be appended to the path. # * :only_path - If true, returns the relative URL (omitting the protocol, host name, and port) (true by default unless :host is specified). # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2005/". Note that this # is currently not recommended since it breaks caching. # * :host - Overrides the default (current) host if provided. # * :protocol - Overrides the default (current) protocol if provided. # * :user - Inline HTTP authentication (only plucked out if :password is also present). # * :password - Inline HTTP authentication (only plucked out if :user is also present). # # ==== Relying on named routes # # Passing a record (like an Active Record) instead of a hash as the options parameter will # trigger the named route for that record. The lookup will happen on the name of the class. So passing a # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). # # ==== Implicit Controller Namespacing # # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one. # # ==== Examples # <%= url_for(action: 'index') %> # # => /blog/ # # <%= url_for(action: 'find', controller: 'books') %> # # => /books/find # # <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %> # # => https://www.example.com/members/login/ # # <%= url_for(action: 'play', anchor: 'player') %> # # => /messages/play/#player # # <%= url_for(action: 'jump', anchor: 'tax&ship') %> # # => /testing/jump/#tax&ship # # <%= url_for(Workshop.new) %> # # relies on Workshop answering a persisted? call (and in this case returning false) # # => /workshops # # <%= url_for(@workshop) %> # # calls @workshop.to_param which by default returns the id # # => /workshops/5 # # # to_param can be re-defined in a model to provide different URL names: # # => /workshops/1-workshop-name # # <%= url_for("http://www.example.com") %> # # => http://www.example.com # # <%= url_for(:back) %> # # if request.env["HTTP_REFERER"] is set to "http://www.example.com" # # => http://www.example.com # # <%= url_for(:back) %> # # if request.env["HTTP_REFERER"] is not set or is blank # # => javascript:history.back() # # <%= url_for(action: 'index', controller: 'users') %> # # Assuming an "admin" namespace # # => /admin/users # # <%= url_for(action: 'index', controller: '/users') %> # # Specify absolute path with beginning slash # # => /users def url_for(options = nil) case options when String options when nil super(only_path: _generate_paths_by_default) when Hash options = options.symbolize_keys unless options.key?(:only_path) if options[:host].nil? options[:only_path] = _generate_paths_by_default else options[:only_path] = false end end super(options) when :back _back_url when Array components = options.dup if _generate_paths_by_default polymorphic_path(components, components.extract_options!) else polymorphic_url(components, components.extract_options!) end else method = _generate_paths_by_default ? :path : :url builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method) case options when Symbol builder.handle_string_call(self, options) when Class builder.handle_class_call(self, options) else builder.handle_model_call(self, options) end end end def url_options #:nodoc: return super unless controller.respond_to?(:url_options) controller.url_options end def _routes_context #:nodoc: controller end protected :_routes_context def optimize_routes_generation? #:nodoc: controller.respond_to?(:optimize_routes_generation?, true) ? controller.optimize_routes_generation? : super end protected :optimize_routes_generation? end end rails-4.2.6/actionview/lib/action_view/tasks/000077500000000000000000000000001266740050600212155ustar00rootroot00000000000000rails-4.2.6/actionview/lib/action_view/tasks/dependencies.rake000066400000000000000000000016771266740050600245220ustar00rootroot00000000000000namespace :cache_digests do desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)' task :nested_dependencies => :environment do abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present? puts JSON.pretty_generate ActionView::Digestor.new(name: CacheDigests.template_name, finder: CacheDigests.finder).nested_dependencies end desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)' task :dependencies => :environment do abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present? puts JSON.pretty_generate ActionView::Digestor.new(name: CacheDigests.template_name, finder: CacheDigests.finder).dependencies end class CacheDigests def self.template_name ENV['TEMPLATE'].split('.', 2).first end def self.finder ApplicationController.new.lookup_context end end end rails-4.2.6/actionview/lib/action_view/template.rb000066400000000000000000000311411266740050600222300ustar00rootroot00000000000000require 'active_support/core_ext/object/try' require 'active_support/core_ext/kernel/singleton_class' require 'thread' module ActionView # = Action View Template class Template extend ActiveSupport::Autoload # === Encodings in ActionView::Template # # ActionView::Template is one of a few sources of potential # encoding issues in Rails. This is because the source for # templates are usually read from disk, and Ruby (like most # encoding-aware programming languages) assumes that the # String retrieved through File IO is encoded in the # default_external encoding. In Rails, the default # default_external encoding is UTF-8. # # As a result, if a user saves their template as ISO-8859-1 # (for instance, using a non-Unicode-aware text editor), # and uses characters outside of the ASCII range, their # users will see diamonds with question marks in them in # the browser. # # For the rest of this documentation, when we say "UTF-8", # we mean "UTF-8 or whatever the default_internal encoding # is set to". By default, it will be UTF-8. # # To mitigate this problem, we use a few strategies: # 1. If the source is not valid UTF-8, we raise an exception # when the template is compiled to alert the user # to the problem. # 2. The user can specify the encoding using Ruby-style # encoding comments in any template engine. If such # a comment is supplied, Rails will apply that encoding # to the resulting compiled source returned by the # template handler. # 3. In all cases, we transcode the resulting String to # the UTF-8. # # This means that other parts of Rails can always assume # that templates are encoded in UTF-8, even if the original # source of the template was not UTF-8. # # From a user's perspective, the easiest thing to do is # to save your templates as UTF-8. If you do this, you # do not need to do anything else for things to "just work". # # === Instructions for template handlers # # The easiest thing for you to do is to simply ignore # encodings. Rails will hand you the template source # as the default_internal (generally UTF-8), raising # an exception for the user before sending the template # to you if it could not determine the original encoding. # # For the greatest simplicity, you can support only # UTF-8 as the default_internal. This means # that from the perspective of your handler, the # entire pipeline is just UTF-8. # # === Advanced: Handlers with alternate metadata sources # # If you want to provide an alternate mechanism for # specifying encodings (like ERB does via <%# encoding: ... %>), # you may indicate that you will handle encodings yourself # by implementing self.handles_encoding? # on your handler. # # If you do, Rails will not try to encode the String # into the default_internal, passing you the unaltered # bytes tagged with the assumed encoding (from # default_external). # # In this case, make sure you return a String from # your handler encoded in the default_internal. Since # you are handling out-of-band metadata, you are # also responsible for alerting the user to any # problems with converting the user's data to # the default_internal. # # To do so, simply raise +WrongEncodingError+ as follows: # # raise WrongEncodingError.new( # problematic_string, # expected_encoding # ) eager_autoload do autoload :Error autoload :Handlers autoload :HTML autoload :Text autoload :Types end extend Template::Handlers attr_accessor :locals, :formats, :variants, :virtual_path attr_reader :source, :identifier, :handler, :original_encoding, :updated_at # This finalizer is needed (and exactly with a proc inside another proc) # otherwise templates leak in development. Finalizer = proc do |method_name, mod| proc do mod.module_eval do remove_possible_method method_name end end end def initialize(source, identifier, handler, details) format = details[:format] || (handler.default_format if handler.respond_to?(:default_format)) @source = source @identifier = identifier @handler = handler @compiled = false @original_encoding = nil @locals = details[:locals] || [] @virtual_path = details[:virtual_path] @updated_at = details[:updated_at] || Time.now @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } @variants = [details[:variant]] @compile_mutex = Mutex.new end # Returns if the underlying handler supports streaming. If so, # a streaming buffer *may* be passed when it start rendering. def supports_streaming? handler.respond_to?(:supports_streaming?) && handler.supports_streaming? end # Render a template. If the template was not compiled yet, it is done # exactly before rendering. # # This method is instrumented as "!render_template.action_view". Notice that # we use a bang in this instrumentation because you don't want to # consume this in production. This is only slow if it's being listened to. def render(view, locals, buffer=nil, &block) instrument("!render_template") do compile!(view) view.send(method_name, locals, buffer, &block) end rescue => e handle_render_error(view, e) end def type @type ||= Types[@formats.first] if @formats.first end # Receives a view object and return a template similar to self by using @virtual_path. # # This method is useful if you have a template object but it does not contain its source # anymore since it was already compiled. In such cases, all you need to do is to call # refresh passing in the view object. # # Notice this method raises an error if the template to be refreshed does not have a # virtual path set (true just for inline templates). def refresh(view) raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path lookup = view.lookup_context pieces = @virtual_path.split("/") name = pieces.pop partial = !!name.sub!(/^_/, "") lookup.disable_cache do lookup.find_template(name, [ pieces.join('/') ], partial, @locals) end end def inspect @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier end # This method is responsible for properly setting the encoding of the # source. Until this point, we assume that the source is BINARY data. # If no additional information is supplied, we assume the encoding is # the same as Encoding.default_external. # # The user can also specify the encoding via a comment on the first # line of the template (# encoding: NAME-OF-ENCODING). This will work # with any template engine, as we process out the encoding comment # before passing the source on to the template engine, leaving a # blank line in its stead. def encode! return unless source.encoding == Encoding::BINARY # Look for # encoding: *. If we find one, we'll encode the # String in that encoding, otherwise, we'll use the # default external encoding. if source.sub!(/\A#{ENCODING_FLAG}/, '') encoding = magic_encoding = $1 else encoding = Encoding.default_external end # Tag the source with the default external encoding # or the encoding specified in the file source.force_encoding(encoding) # If the user didn't specify an encoding, and the handler # handles encodings, we simply pass the String as is to # the handler (with the default_external tag) if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding? source # Otherwise, if the String is valid in the encoding, # encode immediately to default_internal. This means # that if a handler doesn't handle encodings, it will # always get Strings in the default_internal elsif source.valid_encoding? source.encode! # Otherwise, since the String is invalid in the encoding # specified, raise an exception else raise WrongEncodingError.new(source, encoding) end end protected # Compile a template. This method ensures a template is compiled # just once and removes the source after it is compiled. def compile!(view) #:nodoc: return if @compiled # Templates can be used concurrently in threaded environments # so compilation and any instance variable modification must # be synchronized @compile_mutex.synchronize do # Any thread holding this lock will be compiling the template needed # by the threads waiting. So re-check the @compiled flag to avoid # re-compilation return if @compiled if view.is_a?(ActionView::CompiledTemplates) mod = ActionView::CompiledTemplates else mod = view.singleton_class end instrument("!compile_template") do compile(mod) end # Just discard the source if we have a virtual path. This # means we can get the template back. @source = nil if @virtual_path @compiled = true end end # Among other things, this method is responsible for properly setting # the encoding of the compiled template. # # If the template engine handles encodings, we send the encoded # String to the engine without further processing. This allows # the template engine to support additional mechanisms for # specifying the encoding. For instance, ERB supports <%# encoding: %> # # Otherwise, after we figure out the correct encoding, we then # encode the source into Encoding.default_internal. # In general, this means that templates will be UTF-8 inside of Rails, # regardless of the original source encoding. def compile(mod) #:nodoc: encode! method_name = self.method_name code = @handler.call(self) # Make sure that the resulting String to be eval'd is in the # encoding of the code source = <<-end_src def #{method_name}(local_assigns, output_buffer) _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code} ensure @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer end end_src # Make sure the source is in the encoding of the returned code source.force_encoding(code.encoding) # In case we get back a String from a handler that is not in # BINARY or the default_internal, encode it to the default_internal source.encode! # Now, validate that the source we got back from the template # handler is valid in the default_internal. This is for handlers # that handle encoding but screw up unless source.valid_encoding? raise WrongEncodingError.new(@source, Encoding.default_internal) end mod.module_eval(source, identifier, 0) ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) end def handle_render_error(view, e) #:nodoc: if e.is_a?(Template::Error) e.sub_template_of(self) raise e else template = self unless template.source template = refresh(view) template.encode! end raise Template::Error.new(template, e) end end def locals_code #:nodoc: # Double assign to suppress the dreaded 'assigned but unused variable' warning @locals.each_with_object('') { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" } end def method_name #:nodoc: @method_name ||= begin m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}" m.tr!('-', '_') m end end def identifier_method_name #:nodoc: inspect.tr('^a-z_', '_') end def instrument(action, &block) payload = { virtual_path: @virtual_path, identifier: @identifier } ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block) end end end rails-4.2.6/actionview/lib/action_view/template/000077500000000000000000000000001266740050600217035ustar00rootroot00000000000000rails-4.2.6/actionview/lib/action_view/template/error.rb000066400000000000000000000100501266740050600233550ustar00rootroot00000000000000require "active_support/core_ext/enumerable" module ActionView # = Action View Errors class ActionViewError < StandardError #:nodoc: end class EncodingError < StandardError #:nodoc: end class MissingRequestError < StandardError #:nodoc: end class WrongEncodingError < EncodingError #:nodoc: def initialize(string, encoding) @string, @encoding = string, encoding end def message @string.force_encoding(Encoding::ASCII_8BIT) "Your template was not saved as valid #{@encoding}. Please " \ "either specify #{@encoding} as the encoding for your template " \ "in your text editor, or mark the template with its " \ "encoding by inserting the following as the first line " \ "of the template:\n\n# encoding: .\n\n" \ "The source of your template was:\n\n#{@string}" end end class MissingTemplate < ActionViewError #:nodoc: attr_reader :path def initialize(paths, path, prefixes, partial, details, *) @path = path prefixes = Array(prefixes) template_type = if partial "partial" elsif path =~ /layouts/i 'layout' else 'template' end if partial && path.present? path = path.sub(%r{([^/]+)$}, "_\\1") end searched_paths = prefixes.map { |prefix| [prefix, path].join("/") } out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n" out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join super out end end class Template # The Template::Error exception is raised when the compilation or rendering of the template # fails. This exception then gathers a bunch of intimate details and uses it to report a # precise exception message. class Error < ActionViewError #:nodoc: SOURCE_CODE_RADIUS = 3 attr_reader :original_exception def initialize(template, original_exception) super(original_exception.message) @template, @original_exception = template, original_exception @sub_templates = nil set_backtrace(original_exception.backtrace) end def file_name @template.identifier end def sub_template_message if @sub_templates "Trace of template inclusion: " + @sub_templates.collect { |template| template.inspect }.join(", ") else "" end end def source_extract(indentation = 0, output = :console) return unless num = line_number num = num.to_i source_code = @template.source.split("\n") start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min indent = end_on_line.to_s.size + indentation return unless source_code = source_code[start_on_line..end_on_line] formatted_code_for(source_code, start_on_line, indent, output) end def sub_template_of(template_path) @sub_templates ||= [] @sub_templates << template_path end def line_number @line_number ||= if file_name regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/ $1 if message =~ regexp || backtrace.find { |line| line =~ regexp } end end def annoted_source_code source_extract(4) end private def source_location if line_number "on line ##{line_number} of " else 'in ' end + file_name end def formatted_code_for(source_code, line_counter, indent, output) start_value = (output == :html) ? {} : "" source_code.inject(start_value) do |result, line| line_counter += 1 if output == :html result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line]) else result << "%#{indent}s: %s\n" % [line_counter, line] end end end end end TemplateError = Template::Error end rails-4.2.6/actionview/lib/action_view/template/handlers.rb000066400000000000000000000042401266740050600240300ustar00rootroot00000000000000module ActionView #:nodoc: # = Action View Template Handlers class Template module Handlers #:nodoc: autoload :ERB, 'action_view/template/handlers/erb' autoload :Builder, 'action_view/template/handlers/builder' autoload :Raw, 'action_view/template/handlers/raw' def self.extended(base) base.register_default_template_handler :erb, ERB.new base.register_template_handler :builder, Builder.new base.register_template_handler :raw, Raw.new base.register_template_handler :ruby, :source.to_proc end @@template_handlers = {} @@default_template_handlers = nil def self.extensions @@template_extensions ||= @@template_handlers.keys end # Register an object that knows how to handle template files with the given # extensions. This can be used to implement new template types. # The handler must respond to +:call+, which will be passed the template # and should return the rendered template as a String. def register_template_handler(*extensions, handler) raise(ArgumentError, "Extension is required") if extensions.empty? extensions.each do |extension| @@template_handlers[extension.to_sym] = handler end @@template_extensions = nil end # Opposite to register_template_handler. def unregister_template_handler(*extensions) extensions.each do |extension| handler = @@template_handlers.delete extension.to_sym @@default_template_handlers = nil if @@default_template_handlers == handler end @@template_extensions = nil end def template_handler_extensions @@template_handlers.keys.map {|key| key.to_s }.sort end def registered_template_handler(extension) extension && @@template_handlers[extension.to_sym] end def register_default_template_handler(extension, klass) register_template_handler(extension, klass) @@default_template_handlers = klass end def handler_for_extension(extension) registered_template_handler(extension) || @@default_template_handlers end end end end rails-4.2.6/actionview/lib/action_view/template/handlers/000077500000000000000000000000001266740050600235035ustar00rootroot00000000000000rails-4.2.6/actionview/lib/action_view/template/handlers/builder.rb000066400000000000000000000010441266740050600254550ustar00rootroot00000000000000module ActionView module Template::Handlers class Builder # Default format used by Builder. class_attribute :default_format self.default_format = :xml def call(template) require_engine "xml = ::Builder::XmlMarkup.new(:indent => 2);" + "self.output_buffer = xml.target!;" + template.source + ";xml.target!;" end protected def require_engine @required ||= begin require "builder" true end end end end end rails-4.2.6/actionview/lib/action_view/template/handlers/erb.rb000066400000000000000000000101751266740050600246040ustar00rootroot00000000000000require 'erubis' module ActionView class Template module Handlers class Erubis < ::Erubis::Eruby def add_preamble(src) @newline_pending = 0 src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" end def add_text(src, text) return if text.empty? if text == "\n" @newline_pending += 1 else src << "@output_buffer.safe_append='" src << "\n" * @newline_pending if @newline_pending > 0 src << escape_text(text) src << "'.freeze;" @newline_pending = 0 end end # Erubis toggles <%= and <%== behavior when escaping is enabled. # We override to always treat <%== as escaped. def add_expr(src, code, indicator) case indicator when '==' add_expr_escaped(src, code) else super end end BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ def add_expr_literal(src, code) flush_newline_if_pending(src) if code =~ BLOCK_EXPR src << '@output_buffer.append= ' << code else src << '@output_buffer.append=(' << code << ');' end end def add_expr_escaped(src, code) flush_newline_if_pending(src) if code =~ BLOCK_EXPR src << "@output_buffer.safe_expr_append= " << code else src << "@output_buffer.safe_expr_append=(" << code << ");" end end def add_stmt(src, code) flush_newline_if_pending(src) super end def add_postamble(src) flush_newline_if_pending(src) src << '@output_buffer.to_s' end def flush_newline_if_pending(src) if @newline_pending > 0 src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;" @newline_pending = 0 end end end class ERB # Specify trim mode for the ERB compiler. Defaults to '-'. # See ERB documentation for suitable values. class_attribute :erb_trim_mode self.erb_trim_mode = '-' # Default implementation used. class_attribute :erb_implementation self.erb_implementation = Erubis # Do not escape templates of these mime types. class_attribute :escape_whitelist self.escape_whitelist = ["text/plain"] ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") def self.call(template) new.call(template) end def supports_streaming? true end def handles_encoding? true end def call(template) # First, convert to BINARY, so in case the encoding is # wrong, we can still find an encoding tag # (<%# encoding %>) inside the String using a regular # expression template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT) erb = template_source.gsub(ENCODING_TAG, '') encoding = $2 erb.force_encoding valid_encoding(template.source.dup, encoding) # Always make sure we return a String in the default_internal erb.encode! self.class.erb_implementation.new( erb, :escape => (self.class.escape_whitelist.include? template.type), :trim => (self.class.erb_trim_mode == "-") ).src end private def valid_encoding(string, encoding) # If a magic encoding comment was found, tag the # String with this encoding. This is for a case # where the original String was assumed to be, # for instance, UTF-8, but a magic comment # proved otherwise string.force_encoding(encoding) if encoding # If the String is valid, return the encoding we found return string.encoding if string.valid_encoding? # Otherwise, raise an exception raise WrongEncodingError.new(string, string.encoding) end end end end end rails-4.2.6/actionview/lib/action_view/template/handlers/raw.rb000066400000000000000000000003031266740050600246150ustar00rootroot00000000000000module ActionView module Template::Handlers class Raw def call(template) escaped = template.source.gsub(/:/, '\:') '%q:' + escaped + ':;' end end end end rails-4.2.6/actionview/lib/action_view/template/html.rb000066400000000000000000000011371266740050600231760ustar00rootroot00000000000000module ActionView #:nodoc: # = Action View HTML Template class Template class HTML #:nodoc: attr_accessor :type def initialize(string, type = nil) @string = string.to_s @type = Types[type] || type if type @type ||= Types[:html] end def identifier 'html template' end def inspect 'html template' end def to_str ERB::Util.h(@string) end def render(*args) to_str end def formats [@type.respond_to?(:ref) ? @type.ref : @type.to_s] end end end end rails-4.2.6/actionview/lib/action_view/template/resolver.rb000066400000000000000000000302041266740050600240700ustar00rootroot00000000000000require "pathname" require "active_support/core_ext/class" require "active_support/core_ext/module/attribute_accessors" require 'active_support/core_ext/string/filters' require "action_view/template" require "thread" require "thread_safe" module ActionView # = Action View Resolver class Resolver # Keeps all information about view path and builds virtual path. class Path attr_reader :name, :prefix, :partial, :virtual alias_method :partial?, :partial def self.build(name, prefix, partial) virtual = "" virtual << "#{prefix}/" unless prefix.empty? virtual << (partial ? "_#{name}" : name) new name, prefix, partial, virtual end def initialize(name, prefix, partial, virtual) @name = name @prefix = prefix @partial = partial @virtual = virtual end def to_str @virtual end alias :to_s :to_str end # Threadsafe template cache class Cache #:nodoc: class SmallCache < ThreadSafe::Cache def initialize(options = {}) super(options.merge(:initial_capacity => 2)) end end # preallocate all the default blocks for performance/memory consumption reasons PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new} PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)} NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)} KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)} # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory NO_TEMPLATES = [].freeze def initialize @data = SmallCache.new(&KEY_BLOCK) end # Cache the templates returned by the block def cache(key, name, prefix, partial, locals) if Resolver.caching? @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) else fresh_templates = yield cached_templates = @data[key][name][prefix][partial][locals] if templates_have_changed?(cached_templates, fresh_templates) @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates) else cached_templates || NO_TEMPLATES end end end def clear @data.clear end private def canonical_no_templates(templates) templates.empty? ? NO_TEMPLATES : templates end def templates_have_changed?(cached_templates, fresh_templates) # if either the old or new template list is empty, we don't need to (and can't) # compare modification times, and instead just check whether the lists are different if cached_templates.blank? || fresh_templates.blank? return fresh_templates.blank? != cached_templates.blank? end cached_templates_max_updated_at = cached_templates.map(&:updated_at).max # if a template has changed, it will be now be newer than all the cached templates fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at } end end cattr_accessor :caching self.caching = true class << self alias :caching? :caching end def initialize @cache = Cache.new end def clear_cache @cache.clear end # Normalizes the arguments and passes it on to find_templates. def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[]) cached(key, [name, prefix, partial], details, locals) do find_templates(name, prefix, partial, details, false) end end def find_all_anywhere(name, prefix, partial=false, details={}, key=nil, locals=[]) cached(key, [name, prefix, partial], details, locals) do find_templates(name, prefix, partial, details, true) end end private delegate :caching?, to: :class # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and # normalized. def find_templates(name, prefix, partial, details, outside_app_allowed) raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed) method" end # Helpers that builds a path. Useful for building virtual paths. def build_path(name, prefix, partial) Path.build(name, prefix, partial) end # Handles templates caching. If a key is given and caching is on # always check the cache before hitting the resolver. Otherwise, # it always hits the resolver but if the key is present, check if the # resolver is fresher before returning it. def cached(key, path_info, details, locals) #:nodoc: name, prefix, partial = path_info locals = locals.map { |x| x.to_s }.sort! if key @cache.cache(key, name, prefix, partial, locals) do decorate(yield, path_info, details, locals) end else decorate(yield, path_info, details, locals) end end # Ensures all the resolver information is set in the template. def decorate(templates, path_info, details, locals) #:nodoc: cached = nil templates.each do |t| t.locals = locals t.formats = details[:formats] || [:html] if t.formats.empty? t.variants = details[:variants] || [] if t.variants.empty? t.virtual_path ||= (cached ||= build_path(*path_info)) end end end # An abstract class that implements a Resolver with path semantics. class PathResolver < Resolver #:nodoc: EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." } DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}" def initialize(pattern=nil) @pattern = pattern || DEFAULT_PATTERN super() end private def find_templates(name, prefix, partial, details, outside_app_allowed = false) path = Path.build(name, prefix, partial) query(path, details, details[:formats], outside_app_allowed) end def query(path, details, formats, outside_app_allowed) query = build_query(path, details) template_paths = find_template_paths query template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed template_paths.map { |template| handler, format, variant = extract_handler_and_format_and_variant(template, formats) contents = File.binread(template) Template.new(contents, File.expand_path(template), handler, :virtual_path => path.virtual, :format => format, :variant => variant, :updated_at => mtime(template) ) } end def reject_files_external_to_app(files) files.reject { |filename| !inside_path?(@path, filename) } end if RUBY_VERSION >= '2.2.0' def find_template_paths(query) Dir[query].reject { |filename| File.directory?(filename) || # deals with case-insensitive file systems. !File.fnmatch(query, filename, File::FNM_EXTGLOB) } end else def find_template_paths(query) # deals with case-insensitive file systems. sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] } Dir[query].reject { |filename| File.directory?(filename) || !sanitizer[File.dirname(filename)].include?(filename) } end end def inside_path?(path, filename) filename = File.expand_path(filename) path = File.join(path, '') filename.start_with?(path) end # Helper for building query glob string based on resolver's pattern. def build_query(path, details) query = @pattern.dup prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1" query.gsub!(/\:prefix(\/)?/, prefix) partial = escape_entry(path.partial? ? "_#{path.name}" : path.name) query.gsub!(/\:action/, partial) details.each do |ext, variants| query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}") end File.expand_path(query, @path) end def escape_entry(entry) entry.gsub(/[*?{}\[\]]/, '\\\\\\&') end # Returns the file mtime from the filesystem. def mtime(p) File.mtime(p) end # Extract handler, formats and variant from path. If a format cannot be found neither # from the path, or the handler, we should return the array of formats given # to the resolver. def extract_handler_and_format_and_variant(path, default_formats) pieces = File.basename(path).split(".") pieces.shift extension = pieces.pop unless extension ActiveSupport::Deprecation.warn(<<-MSG.squish) The file #{path} did not specify a template handler. The default is currently ERB, but will change to RAW in the future. MSG end handler = Template.handler_for_extension(extension) format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last format &&= Template::Types[format] [handler, format, variant] end end # A resolver that loads files from the filesystem. It allows setting your own # resolving pattern. Such pattern can be a glob string supported by some variables. # # ==== Examples # # Default pattern, loads views the same way as previous versions of rails, eg. when you're # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}` # # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") # # This one allows you to keep files with different formats in separate subdirectories, # eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`, # `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc. # # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") # # If you don't specify a pattern then the default will be used. # # In order to use any of the customized resolvers above in a Rails application, you just need # to configure ActionController::Base.view_paths in an initializer, for example: # # ActionController::Base.view_paths = FileSystemResolver.new( # Rails.root.join("app/views"), # ":prefix{/:locale}/:action{.:formats,}{+:variants,}{.:handlers,}" # ) # # ==== Pattern format and variables # # Pattern has to be a valid glob string, and it allows you to use the # following variables: # # * :prefix - usually the controller path # * :action - name of the action # * :locale - possible locale versions # * :formats - possible request formats (for example html, json, xml...) # * :variants - possible request variants (for example phone, tablet...) # * :handlers - possible handlers (for example erb, haml, builder...) # class FileSystemResolver < PathResolver def initialize(path, pattern=nil) raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) super(pattern) @path = File.expand_path(path) end def to_s @path.to_s end alias :to_path :to_s def eql?(resolver) self.class.equal?(resolver.class) && to_path == resolver.to_path end alias :== :eql? end # An Optimized resolver for Rails' most common case. class OptimizedFileSystemResolver < FileSystemResolver #:nodoc: def build_query(path, details) query = escape_entry(File.join(@path, path)) exts = EXTENSIONS.map do |ext, prefix| "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}" end.join query + exts end end # The same as FileSystemResolver but does not allow templates to store # a virtual path since it is invalid for such resolvers. class FallbackFileSystemResolver < FileSystemResolver #:nodoc: def self.instances [new(""), new("/")] end def decorate(*) super.each { |t| t.virtual_path = nil } end end end rails-4.2.6/actionview/lib/action_view/template/text.rb000066400000000000000000000011221266740050600232100ustar00rootroot00000000000000module ActionView #:nodoc: # = Action View Text Template class Template class Text #:nodoc: attr_accessor :type def initialize(string, type = nil) @string = string.to_s @type = Types[type] || type if type @type ||= Types[:text] end def identifier 'text template' end def inspect 'text template' end def to_str @string end def render(*args) to_str end def formats [@type.respond_to?(:ref) ? @type.ref : @type.to_s] end end end end rails-4.2.6/actionview/lib/action_view/template/types.rb000066400000000000000000000021141266740050600233720ustar00rootroot00000000000000require 'set' require 'active_support/core_ext/module/attribute_accessors' module ActionView class Template class Types class Type cattr_accessor :types self.types = Set.new def self.register(*t) types.merge(t.map { |type| type.to_s }) end register :html, :text, :js, :css, :xml, :json def self.[](type) return type if type.is_a?(self) if type.is_a?(Symbol) || types.member?(type.to_s) new(type) end end attr_reader :symbol def initialize(symbol) @symbol = symbol.to_sym end delegate :to_s, :to_sym, :to => :symbol alias to_str to_s def ref to_sym || to_s end def ==(type) return false if type.blank? symbol.to_sym == type.to_sym end end cattr_accessor :type_klass def self.delegate_to(klass) self.type_klass = klass end delegate_to Type def self.[](type) type_klass[type] end end end end rails-4.2.6/actionview/lib/action_view/test_case.rb000066400000000000000000000170721266740050600223760ustar00rootroot00000000000000require 'active_support/core_ext/module/remove_method' require 'action_controller' require 'action_controller/test_case' require 'action_view' require 'rails-dom-testing' module ActionView # = Action View Test Case class TestCase < ActiveSupport::TestCase class TestController < ActionController::Base include ActionDispatch::TestProcess attr_accessor :request, :response, :params class << self attr_writer :controller_path end def controller_path=(path) self.class.controller_path=(path) end def initialize super self.class.controller_path = "" @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @request.env.delete('PATH_INFO') @params = {} end end module Behavior extend ActiveSupport::Concern include ActionDispatch::Assertions, ActionDispatch::TestProcess include Rails::Dom::Testing::Assertions include ActionController::TemplateAssertions include ActionView::Context include ActionDispatch::Routing::PolymorphicRoutes include AbstractController::Helpers include ActionView::Helpers include ActionView::RecordIdentifier include ActionView::RoutingUrlFor include ActiveSupport::Testing::ConstantLookup delegate :lookup_context, :to => :controller attr_accessor :controller, :output_buffer, :rendered module ClassMethods def tests(helper_class) case helper_class when String, Symbol self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize when Module self.helper_class = helper_class end end def determine_default_helper_class(name) determine_constant_from_test_name(name) do |constant| Module === constant && !(Class === constant) end end def helper_method(*methods) # Almost a duplicate from ActionController::Helpers methods.flatten.each do |method| _helpers.module_eval <<-end_eval def #{method}(*args, &block) # def current_user(*args, &block) _test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block) end # end end_eval end end attr_writer :helper_class def helper_class @helper_class ||= determine_default_helper_class(name) end def new(*) include_helper_modules! super end private def include_helper_modules! helper(helper_class) if helper_class include _helpers end end def setup_with_controller @controller = ActionView::TestCase::TestController.new @request = @controller.request # empty string ensures buffer has UTF-8 encoding as # new without arguments returns ASCII-8BIT encoded buffer like String#new @output_buffer = ActiveSupport::SafeBuffer.new '' @rendered = '' make_test_case_available_to_view! say_no_to_protect_against_forgery! end def config @controller.config if @controller.respond_to?(:config) end def render(options = {}, local_assigns = {}, &block) view.assign(view_assigns) @rendered << output = view.render(options, local_assigns, &block) output end def rendered_views @_rendered_views ||= RenderedViewsCollection.new end # Need to experiment if this priority is the best one: rendered => output_buffer class RenderedViewsCollection def initialize @rendered_views ||= Hash.new { |hash, key| hash[key] = [] } end def add(view, locals) @rendered_views[view] ||= [] @rendered_views[view] << locals end def locals_for(view) @rendered_views[view] end def rendered_views @rendered_views.keys end def view_rendered?(view, expected_locals) locals_for(view).any? do |actual_locals| expected_locals.all? {|key, value| value == actual_locals[key] } end end end included do setup :setup_with_controller end private # Need to experiment if this priority is the best one: rendered => output_buffer def document_root_element Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root end def say_no_to_protect_against_forgery! _helpers.module_eval do remove_possible_method :protect_against_forgery? def protect_against_forgery? false end end end def make_test_case_available_to_view! test_case_instance = self _helpers.module_eval do unless private_method_defined?(:_test_case) define_method(:_test_case) { test_case_instance } private :_test_case end end end module Locals attr_accessor :rendered_views def render(options = {}, local_assigns = {}) case options when Hash if block_given? rendered_views.add options[:layout], options[:locals] elsif options.key?(:partial) rendered_views.add options[:partial], options[:locals] end else rendered_views.add options, local_assigns end super end end # The instance of ActionView::Base that is used by +render+. def view @view ||= begin view = @controller.view_context view.singleton_class.send :include, _helpers view.extend(Locals) view.rendered_views = self.rendered_views view.output_buffer = self.output_buffer view end end alias_method :_view, :view INTERNAL_IVARS = [ :@NAME, :@failures, :@assertions, :@__io__, :@_assertion_wrapped, :@_assertions, :@_result, :@_routes, :@controller, :@_layouts, :@_files, :@_rendered_views, :@method_name, :@output_buffer, :@_partials, :@passed, :@rendered, :@request, :@routes, :@tagged_logger, :@_templates, :@options, :@test_passed, :@view, :@view_context_class, :@_subscribers, :@html_document, :@html_scanner_document ] def _user_defined_ivars instance_variables - INTERNAL_IVARS end # Returns a Hash of instance variables and their values, as defined by # the user in the test case, which are then assigned to the view being # rendered. This is generally intended for internal use and extension # frameworks. def view_assigns Hash[_user_defined_ivars.map do |ivar| [ivar[1..-1].to_sym, instance_variable_get(ivar)] end] end def _routes @controller._routes if @controller.respond_to?(:_routes) end def method_missing(selector, *args) if @controller.respond_to?(:_routes) && ( @controller._routes.named_routes.route_defined?(selector) || @controller._routes.mounted_helpers.method_defined?(selector) ) @controller.__send__(selector, *args) else super end end end include Behavior end end rails-4.2.6/actionview/lib/action_view/testing/000077500000000000000000000000001266740050600215455ustar00rootroot00000000000000rails-4.2.6/actionview/lib/action_view/testing/resolvers.rb000066400000000000000000000032031266740050600241140ustar00rootroot00000000000000require 'action_view/template/resolver' module ActionView #:nodoc: # Use FixtureResolver in your tests to simulate the presence of files on the # file system. This is used internally by Rails' own test suite, and is # useful for testing extensions that have no way of knowing what the file # system will look like at runtime. class FixtureResolver < PathResolver attr_reader :hash def initialize(hash = {}, pattern=nil) super(pattern) @hash = hash end def to_s @hash.keys.join(', ') end private def query(path, exts, formats, _) query = "" EXTENSIONS.each_key do |ext| query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)' end query = /^(#{Regexp.escape(path)})#{query}$/ templates = [] @hash.each do |_path, array| source, updated_at = array next unless _path =~ query handler, format, variant = extract_handler_and_format_and_variant(_path, formats) templates << Template.new(source, _path, handler, :virtual_path => path.virtual, :format => format, :variant => variant, :updated_at => updated_at ) end templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } end end class NullResolver < PathResolver def query(path, exts, formats, _) handler, format, variant = extract_handler_and_format_and_variant(path, formats) [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format, :variant => variant)] end end end rails-4.2.6/actionview/lib/action_view/version.rb000066400000000000000000000002651266740050600221050ustar00rootroot00000000000000require_relative 'gem_version' module ActionView # Returns the version of the currently loaded ActionView as a Gem::Version def self.version gem_version end end rails-4.2.6/actionview/lib/action_view/view_paths.rb000066400000000000000000000064061266740050600225740ustar00rootroot00000000000000require 'action_view/base' module ActionView module ViewPaths extend ActiveSupport::Concern included do class_attribute :_view_paths self._view_paths = ActionView::PathSet.new self._view_paths.freeze end delegate :template_exists?, :view_paths, :formats, :formats=, :locale, :locale=, :to => :lookup_context module ClassMethods def _prefixes # :nodoc: @_prefixes ||= begin deprecated_prefixes = handle_deprecated_parent_prefixes if deprecated_prefixes deprecated_prefixes else return local_prefixes if superclass.abstract? local_prefixes + superclass._prefixes end end end private # Override this method in your controller if you want to change paths prefixes for finding views. # Prefixes defined here will still be added to parents' ._prefixes. def local_prefixes [controller_path] end def handle_deprecated_parent_prefixes # TODO: remove in 4.3/5.0. return unless respond_to?(:parent_prefixes) ActiveSupport::Deprecation.warn(<<-MSG.squish) Overriding `ActionController::Base::parent_prefixes` is deprecated, override `.local_prefixes` instead. MSG local_prefixes + parent_prefixes end end # The prefixes used in render "foo" shortcuts. def _prefixes # :nodoc: self.class._prefixes end # LookupContext is the object responsible to hold all information required to lookup # templates, i.e. view paths and details. Check ActionView::LookupContext for more # information. def lookup_context @_lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes) end def details_for_lookup { } end def append_view_path(path) lookup_context.view_paths.push(*path) end def prepend_view_path(path) lookup_context.view_paths.unshift(*path) end module ClassMethods # Append a path to the list of view paths for this controller. # # ==== Parameters # * path - If a String is provided, it gets converted into # the default view path. You may also provide a custom view path # (see ActionView::PathSet for more information) def append_view_path(path) self._view_paths = view_paths + Array(path) end # Prepend a path to the list of view paths for this controller. # # ==== Parameters # * path - If a String is provided, it gets converted into # the default view path. You may also provide a custom view path # (see ActionView::PathSet for more information) def prepend_view_path(path) self._view_paths = ActionView::PathSet.new(Array(path) + view_paths) end # A list of all of the default view paths for this controller. def view_paths _view_paths end # Set the view paths. # # ==== Parameters # * paths - If a PathSet is provided, use that; # otherwise, process the parameter into a PathSet. def view_paths=(paths) self._view_paths = ActionView::PathSet.new(Array(paths)) end end end end rails-4.2.6/actionview/test/000077500000000000000000000000001266740050600157725ustar00rootroot00000000000000rails-4.2.6/actionview/test/abstract_unit.rb000066400000000000000000000213051266740050600211620ustar00rootroot00000000000000require File.expand_path('../../../load_paths', __FILE__) $:.unshift(File.dirname(__FILE__) + '/lib') $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') $:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp') require 'active_support/core_ext/kernel/reporting' # These are the normal settings that will be set up by Railties # TODO: Have these tests support other combinations of these values silence_warnings do Encoding.default_internal = "UTF-8" Encoding.default_external = "UTF-8" end require 'active_support/testing/autorun' require 'action_controller' require 'action_view' require 'action_view/testing/resolvers' require 'active_support/dependencies' require 'active_model' require 'active_record' require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late module Rails class << self def env @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test") end end end ActiveSupport::Dependencies.hook! Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false # Register danish language for testing I18n.backend.store_translations 'da', {} I18n.backend.store_translations 'pt-BR', {} ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') FIXTURES = Pathname.new(FIXTURE_LOAD_PATH) module RackTestUtils def body_to_string(body) if body.respond_to?(:each) str = "" body.each {|s| str << s } str else body end end extend self end module RenderERBUtils def view @view ||= begin path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH) view_paths = ActionView::PathSet.new([path]) ActionView::Base.new(view_paths) end end def render_erb(string) @virtual_path = nil template = ActionView::Template.new( string.strip, "test template", ActionView::Template::Handlers::ERB, {}) template.render(self, {}).strip end end SharedTestRoutes = ActionDispatch::Routing::RouteSet.new module ActionDispatch module SharedRoutes def before_setup @routes = SharedTestRoutes super end end # Hold off drawing routes until all the possible controller classes # have been loaded. module DrawOnce class << self attr_accessor :drew end self.drew = false def before_setup super return if DrawOnce.drew SharedTestRoutes.draw do get ':controller(/:action)' end ActionDispatch::IntegrationTest.app.routes.draw do get ':controller(/:action)' end DrawOnce.drew = true end end end module ActiveSupport class TestCase include ActionDispatch::DrawOnce end end class RoutedRackApp attr_reader :routes def initialize(routes, &blk) @routes = routes @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes) end def call(env) @stack.call(env) end end class BasicController attr_accessor :request def config @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config| # VIEW TODO: View tests should not require a controller public_dir = File.expand_path("../fixtures/public", __FILE__) config.assets_dir = public_dir config.javascripts_dir = "#{public_dir}/javascripts" config.stylesheets_dir = "#{public_dir}/stylesheets" config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" }) config end end end class ActionDispatch::IntegrationTest < ActiveSupport::TestCase include ActionDispatch::SharedRoutes def self.build_app(routes = nil) RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware| middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") middleware.use "ActionDispatch::DebugExceptions" middleware.use "ActionDispatch::Callbacks" middleware.use "ActionDispatch::ParamsParser" middleware.use "ActionDispatch::Cookies" middleware.use "ActionDispatch::Flash" middleware.use "Rack::Head" yield(middleware) if block_given? end end self.app = build_app # Stub Rails dispatcher so it does not get controller references and # simply return the controller#action as Rack::Body. class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher protected def controller_reference(controller_param) controller_param end def dispatch(controller, action, env) [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] end end def self.stub_controllers old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher } yield ActionDispatch::Routing::RouteSet.new ensure ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher } end def with_routing(&block) temporary_routes = ActionDispatch::Routing::RouteSet.new old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes) old_routes = SharedTestRoutes silence_warnings { Object.const_set(:SharedTestRoutes, temporary_routes) } yield temporary_routes ensure self.class.app = old_app silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) } end def with_autoload_path(path) path = File.join(File.dirname(__FILE__), "fixtures", path) if ActiveSupport::Dependencies.autoload_paths.include?(path) yield else begin ActiveSupport::Dependencies.autoload_paths << path yield ensure ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path} ActiveSupport::Dependencies.clear end end end end # Temporary base class class Rack::TestCase < ActionDispatch::IntegrationTest def self.testing(klass = nil) if klass @testing = "/#{klass.name.underscore}".sub!(/_controller$/, '') else @testing end end def get(thing, *args) if thing.is_a?(Symbol) super("#{self.class.testing}/#{thing}", *args) else super end end def assert_body(body) assert_equal body, Array(response.body).join end def assert_status(code) assert_equal code, response.status end def assert_response(body, status = 200, headers = {}) assert_body body assert_status status headers.each do |header, value| assert_header header, value end end def assert_content_type(type) assert_equal type, response.headers["Content-Type"] end def assert_header(name, value) assert_equal value, response.headers[name] end end ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) module ActionController class Base # This stub emulates the Railtie including the URL helpers from a Rails application include SharedTestRoutes.url_helpers include SharedTestRoutes.mounted_helpers self.view_paths = FIXTURE_LOAD_PATH def self.test_routes(&block) routes = ActionDispatch::Routing::RouteSet.new routes.draw(&block) include routes.url_helpers end end class TestCase include ActionDispatch::TestProcess include ActionDispatch::SharedRoutes end end module ActionView class TestCase # Must repeat the setup because AV::TestCase is a duplication # of AC::TestCase include ActionDispatch::SharedRoutes end end class Workshop extend ActiveModel::Naming include ActiveModel::Conversion attr_accessor :id def initialize(id) @id = id end def persisted? id.present? end def to_s id.to_s end end module ActionDispatch class DebugExceptions private remove_method :stderr_logger # Silence logger def stderr_logger nil end end end # Skips the current run on Rubinius using Minitest::Assertions#skip def rubinius_skip(message = '') skip message if RUBY_ENGINE == 'rbx' end # Skips the current run on JRuby using Minitest::Assertions#skip def jruby_skip(message = '') skip message if defined?(JRUBY_VERSION) end require 'mocha/setup' # FIXME: stop using mocha # FIXME: we have tests that depend on run order, we should fix that and # remove this method call. require 'active_support/test_case' ActiveSupport::TestCase.test_order = :sorted rails-4.2.6/actionview/test/actionpack/000077500000000000000000000000001266740050600201065ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/000077500000000000000000000000001266740050600217115ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/abstract_controller_test.rb000066400000000000000000000210051266740050600273410ustar00rootroot00000000000000require 'abstract_unit' require 'set' module AbstractController module Testing # Test basic dispatching. # ==== # * Call process # * Test that the response_body is set correctly class SimpleController < AbstractController::Base end class Me < SimpleController def index self.response_body = "Hello world" "Something else" end end class TestBasic < ActiveSupport::TestCase test "dispatching works" do controller = Me.new controller.process(:index) assert_equal "Hello world", controller.response_body end end # Test Render mixin # ==== class RenderingController < AbstractController::Base include AbstractController::Rendering include ActionView::Rendering def _prefixes [] end def render(options = {}) if options.is_a?(String) options = {:_template_name => options} end super end append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views")) end class Me2 < RenderingController def index render "index.erb" end def index_to_string self.response_body = render_to_string "index" end def action_with_ivars @my_ivar = "Hello" render "action_with_ivars.erb" end def naked_render render end def rendering_to_body self.response_body = render_to_body :template => "naked_render" end def rendering_to_string self.response_body = render_to_string :template => "naked_render" end end class TestRenderingController < ActiveSupport::TestCase def setup @controller = Me2.new end test "rendering templates works" do @controller.process(:index) assert_equal "Hello from index.erb", @controller.response_body end test "render_to_string works with a String as an argument" do @controller.process(:index_to_string) assert_equal "Hello from index.erb", @controller.response_body end test "rendering passes ivars to the view" do @controller.process(:action_with_ivars) assert_equal "Hello from index_with_ivars.erb", @controller.response_body end test "rendering with no template name" do @controller.process(:naked_render) assert_equal "Hello from naked_render.erb", @controller.response_body end test "rendering to a rack body" do @controller.process(:rendering_to_body) assert_equal "Hello from naked_render.erb", @controller.response_body end test "rendering to a string" do @controller.process(:rendering_to_string) assert_equal "Hello from naked_render.erb", @controller.response_body end end # Test rendering with prefixes # ==== # * self._prefix is used when defined class PrefixedViews < RenderingController private def self.prefix name.underscore end def _prefixes [self.class.prefix] end end class Me3 < PrefixedViews def index render end def formatted self.formats = [:html] render end end class TestPrefixedViews < ActiveSupport::TestCase def setup @controller = Me3.new end test "templates are located inside their 'prefix' folder" do @controller.process(:index) assert_equal "Hello from me3/index.erb", @controller.response_body end test "templates included their format" do @controller.process(:formatted) assert_equal "Hello from me3/formatted.html.erb", @controller.response_body end end class OverridingLocalPrefixes < AbstractController::Base include AbstractController::Rendering include ActionView::Rendering append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views")) def index render end def self.local_prefixes # this would usually return "abstract_controller/testing/overriding_local_prefixes" super + ["abstract_controller/testing/me3"] end class Inheriting < self end end class OverridingLocalPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3. test "overriding .local_prefixes adds prefix" do @controller = OverridingLocalPrefixes.new @controller.process(:index) assert_equal "Hello from me3/index.erb", @controller.response_body end test ".local_prefixes is inherited" do @controller = OverridingLocalPrefixes::Inheriting.new @controller.process(:index) assert_equal "Hello from me3/index.erb", @controller.response_body end end class DeprecatedParentPrefixes < OverridingLocalPrefixes def self.parent_prefixes ["abstract_controller/testing/me3"] end end class DeprecatedParentPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3. test "overriding .parent_prefixes is deprecated" do @controller = DeprecatedParentPrefixes.new assert_deprecated do @controller.process(:index) end assert_equal "Hello from me3/index.erb", @controller.response_body end end # Test rendering with layouts # ==== # self._layout is used when defined class WithLayouts < PrefixedViews include ActionView::Layouts private def self.layout(formats) find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"]) rescue ActionView::MissingTemplate begin find_template("application", {:formats => formats}, :_prefixes => ["layouts"]) rescue ActionView::MissingTemplate end end def render_to_body(options = {}) options[:_layout] = options[:layout] || _default_layout({}) super end end class Me4 < WithLayouts def index render end end class TestLayouts < ActiveSupport::TestCase test "layouts are included" do controller = Me4.new controller.process(:index) assert_equal "Me4 Enter : Hello from me4/index.erb : Exit", controller.response_body end end # respond_to_action?(action_name) # ==== # * A method can be used as an action only if this method # returns true when passed the method name as an argument # * Defaults to true in AbstractController class DefaultRespondToActionController < AbstractController::Base def index() self.response_body = "success" end end class ActionMissingRespondToActionController < AbstractController::Base # No actions private def action_missing(action_name) self.response_body = "success" end end class RespondToActionController < AbstractController::Base; def index() self.response_body = "success" end def fail() self.response_body = "fail" end private def method_for_action(action_name) action_name.to_s != "fail" && action_name end end class TestRespondToAction < ActiveSupport::TestCase def assert_dispatch(klass, body = "success", action = :index) controller = klass.new controller.process(action) assert_equal body, controller.response_body end test "an arbitrary method is available as an action by default" do assert_dispatch DefaultRespondToActionController, "success", :index end test "raises ActionNotFound when method does not exist and action_missing is not defined" do assert_raise(ActionNotFound) { DefaultRespondToActionController.new.process(:fail) } end test "dispatches to action_missing when method does not exist and action_missing is defined" do assert_dispatch ActionMissingRespondToActionController, "success", :ohai end test "a method is available as an action if method_for_action returns true" do assert_dispatch RespondToActionController, "success", :index end test "raises ActionNotFound if method is defined but method_for_action returns false" do assert_raise(ActionNotFound) { RespondToActionController.new.process(:fail) } end end class Me6 < AbstractController::Base self.action_methods def index end end class TestActionMethodsReloading < ActiveSupport::TestCase test "action_methods should be reloaded after defining a new method" do assert_equal Set.new(["index"]), Me6.action_methods end end end end rails-4.2.6/actionview/test/actionpack/abstract/helper_test.rb000066400000000000000000000071661266740050600245660ustar00rootroot00000000000000require 'abstract_unit' ActionController::Base.helpers_path = File.expand_path('../../../fixtures/helpers', __FILE__) module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base include AbstractController::Helpers include AbstractController::Rendering include ActionView::Rendering def with_module render :inline => "Module <%= included_method %>" end end module HelperyTest def included_method "Included" end end class AbstractHelpers < ControllerWithHelpers helper(HelperyTest) do def helpery_test "World" end end helper :abc def with_block render :inline => "Hello <%= helpery_test %>" end def with_symbol render :inline => "I respond to bare_a: <%= respond_to?(:bare_a) %>" end end class ::HelperyTestController < AbstractHelpers clear_helpers end class AbstractHelpersBlock < ControllerWithHelpers helper do include AbstractController::Testing::HelperyTest end end class AbstractInvalidHelpers < AbstractHelpers include ActionController::Helpers path = File.expand_path('../../../fixtures/helpers_missing', __FILE__) $:.unshift(path) self.helpers_path = path end class TestHelpers < ActiveSupport::TestCase def setup @controller = AbstractHelpers.new end def test_helpers_with_block @controller.process(:with_block) assert_equal "Hello World", @controller.response_body end def test_helpers_with_module @controller.process(:with_module) assert_equal "Module Included", @controller.response_body end def test_helpers_with_symbol @controller.process(:with_symbol) assert_equal "I respond to bare_a: true", @controller.response_body end def test_declare_missing_helper e = assert_raise AbstractController::Helpers::MissingHelperError do AbstractHelpers.helper :missing end assert_equal "helpers/missing_helper.rb", e.path end def test_helpers_with_module_through_block @controller = AbstractHelpersBlock.new @controller.process(:with_module) assert_equal "Module Included", @controller.response_body end end class ClearHelpersTest < ActiveSupport::TestCase def setup @controller = HelperyTestController.new end def test_clears_up_previous_helpers @controller.process(:with_symbol) assert_equal "I respond to bare_a: false", @controller.response_body end def test_includes_controller_default_helper @controller.process(:with_block) assert_equal "Hello Default", @controller.response_body end end class InvalidHelpersTest < ActiveSupport::TestCase def test_controller_raise_error_about_real_require_problem e = assert_raise(LoadError) { AbstractInvalidHelpers.helper(:invalid_require) } assert_equal "No such file to load -- very_invalid_file_name", e.message end def test_controller_raise_error_about_missing_helper e = assert_raise(AbstractController::Helpers::MissingHelperError) { AbstractInvalidHelpers.helper(:missing) } assert_equal "Missing helper file helpers/missing_helper.rb", e.message end def test_missing_helper_error_has_the_right_path e = assert_raise(AbstractController::Helpers::MissingHelperError) { AbstractInvalidHelpers.helper(:missing) } assert_equal "helpers/missing_helper.rb", e.path end end end end rails-4.2.6/actionview/test/actionpack/abstract/layouts_test.rb000066400000000000000000000300361266740050600247770ustar00rootroot00000000000000require 'abstract_unit' module AbstractControllerTests module Layouts # Base controller for these tests class Base < AbstractController::Base include AbstractController::Rendering include ActionView::Rendering include ActionView::Layouts abstract! self.view_paths = [ActionView::FixtureResolver.new( "layouts/hello.erb" => "With String <%= yield %>", "layouts/hello_override.erb" => "With Override <%= yield %>", "layouts/overwrite.erb" => "Overwrite <%= yield %>", "layouts/with_false_layout.erb" => "False Layout <%= yield %>", "abstract_controller_tests/layouts/with_string_implied_child.erb" => "With Implied <%= yield %>", "abstract_controller_tests/layouts/with_grand_child_of_implied.erb" => "With Grand Child <%= yield %>" )] end class Blank < Base self.view_paths = [] def index render :template => ActionView::Template::Text.new("Hello blank!") end end class WithString < Base layout "hello" def index render :template => ActionView::Template::Text.new("Hello string!") end def overwrite_default render :template => ActionView::Template::Text.new("Hello string!"), :layout => :default end def overwrite_false render :template => ActionView::Template::Text.new("Hello string!"), :layout => false end def overwrite_string render :template => ActionView::Template::Text.new("Hello string!"), :layout => "overwrite" end def overwrite_skip render :text => "Hello text!" end end class WithStringChild < WithString end class WithStringOverriddenChild < WithString layout "hello_override" end class WithStringImpliedChild < WithString layout nil end class WithChildOfImplied < WithStringImpliedChild end class WithGrandChildOfImplied < WithStringImpliedChild layout nil end class WithProc < Base layout proc { "overwrite" } def index render :template => ActionView::Template::Text.new("Hello proc!") end end class WithProcReturningNil < Base layout proc { nil } def index render template: ActionView::Template::Text.new("Hello nil!") end end class WithZeroArityProc < Base layout proc { "overwrite" } def index render :template => ActionView::Template::Text.new("Hello zero arity proc!") end end class WithProcInContextOfInstance < Base def an_instance_method; end layout proc { break unless respond_to? :an_instance_method "overwrite" } def index render :template => ActionView::Template::Text.new("Hello again zero arity proc!") end end class WithSymbol < Base layout :hello def index render :template => ActionView::Template::Text.new("Hello symbol!") end private def hello "overwrite" end end class WithSymbolReturningNil < Base layout :nilz def index render :template => ActionView::Template::Text.new("Hello nilz!") end def nilz() end end class WithSymbolReturningObj < Base layout :objekt def index render :template => ActionView::Template::Text.new("Hello nilz!") end def objekt Object.new end end class WithSymbolAndNoMethod < Base layout :no_method def index render :template => ActionView::Template::Text.new("Hello boom!") end end class WithMissingLayout < Base layout "missing" def index render :template => ActionView::Template::Text.new("Hello missing!") end end class WithFalseLayout < Base layout false def index render :template => ActionView::Template::Text.new("Hello false!") end end class WithNilLayout < Base layout nil def index render :template => ActionView::Template::Text.new("Hello nil!") end end class WithOnlyConditional < WithStringImpliedChild layout "overwrite", :only => :show def index render :template => ActionView::Template::Text.new("Hello index!") end def show render :template => ActionView::Template::Text.new("Hello show!") end end class WithExceptConditional < WithStringImpliedChild layout "overwrite", :except => :show def index render :template => ActionView::Template::Text.new("Hello index!") end def show render :template => ActionView::Template::Text.new("Hello show!") end end class TestBase < ActiveSupport::TestCase test "when no layout is specified, and no default is available, render without a layout" do controller = Blank.new controller.process(:index) assert_equal "Hello blank!", controller.response_body end test "when layout is specified as a string, render with that layout" do controller = WithString.new controller.process(:index) assert_equal "With String Hello string!", controller.response_body end test "when layout is overwritten by :default in render, render default layout" do controller = WithString.new controller.process(:overwrite_default) assert_equal "With String Hello string!", controller.response_body end test "when layout is overwritten by string in render, render new layout" do controller = WithString.new controller.process(:overwrite_string) assert_equal "Overwrite Hello string!", controller.response_body end test "when layout is overwritten by false in render, render no layout" do controller = WithString.new controller.process(:overwrite_false) assert_equal "Hello string!", controller.response_body end test "when text is rendered, render no layout" do controller = WithString.new controller.process(:overwrite_skip) assert_equal "Hello text!", controller.response_body end test "when layout is specified as a string, but the layout is missing, raise an exception" do assert_raises(ActionView::MissingTemplate) { WithMissingLayout.new.process(:index) } end test "when layout is specified as false, do not use a layout" do controller = WithFalseLayout.new controller.process(:index) assert_equal "Hello false!", controller.response_body end test "when layout is specified as nil, do not use a layout" do controller = WithNilLayout.new controller.process(:index) assert_equal "Hello nil!", controller.response_body end test "when layout is specified as a proc, do not leak any methods into controller's action_methods" do assert_equal Set.new(['index']), WithProc.action_methods end test "when layout is specified as a proc, call it and use the layout returned" do controller = WithProc.new controller.process(:index) assert_equal "Overwrite Hello proc!", controller.response_body end test "when layout is specified as a proc and the proc returns nil, don't use a layout" do controller = WithProcReturningNil.new controller.process(:index) assert_equal "Hello nil!", controller.response_body end test "when layout is specified as a proc without parameters it works just the same" do controller = WithZeroArityProc.new controller.process(:index) assert_equal "Overwrite Hello zero arity proc!", controller.response_body end test "when layout is specified as a proc without parameters the block is evaluated in the context of an instance" do controller = WithProcInContextOfInstance.new controller.process(:index) assert_equal "Overwrite Hello again zero arity proc!", controller.response_body end test "when layout is specified as a symbol, call the requested method and use the layout returned" do controller = WithSymbol.new controller.process(:index) assert_equal "Overwrite Hello symbol!", controller.response_body end test "when layout is specified as a symbol and the method returns nil, don't use a layout" do controller = WithSymbolReturningNil.new controller.process(:index) assert_equal "Hello nilz!", controller.response_body end test "when the layout is specified as a symbol and the method doesn't exist, raise an exception" do assert_raises(NameError) { WithSymbolAndNoMethod.new.process(:index) } end test "when the layout is specified as a symbol and the method returns something besides a string/false/nil, raise an exception" do assert_raises(ArgumentError) { WithSymbolReturningObj.new.process(:index) } end test "when a child controller does not have a layout, use the parent controller layout" do controller = WithStringChild.new controller.process(:index) assert_equal "With String Hello string!", controller.response_body end test "when a child controller has specified a layout, use that layout and not the parent controller layout" do controller = WithStringOverriddenChild.new controller.process(:index) assert_equal "With Override Hello string!", controller.response_body end test "when a child controller has an implied layout, use that layout and not the parent controller layout" do controller = WithStringImpliedChild.new controller.process(:index) assert_equal "With Implied Hello string!", controller.response_body end test "when a grandchild has no layout specified, the child has an implied layout, and the " \ "parent has specified a layout, use the child controller layout" do controller = WithChildOfImplied.new controller.process(:index) assert_equal "With Implied Hello string!", controller.response_body end test "when a grandchild has nil layout specified, the child has an implied layout, and the " \ "parent has specified a layout, use the child controller layout" do controller = WithGrandChildOfImplied.new controller.process(:index) assert_equal "With Grand Child Hello string!", controller.response_body end test "raises an exception when specifying layout true" do assert_raises ArgumentError do Object.class_eval do class ::BadFailLayout < AbstractControllerTests::Layouts::Base layout true end end end end test "when specify an :only option which match current action name" do controller = WithOnlyConditional.new controller.process(:show) assert_equal "Overwrite Hello show!", controller.response_body end test "when specify an :only option which does not match current action name" do controller = WithOnlyConditional.new controller.process(:index) assert_equal "With Implied Hello index!", controller.response_body end test "when specify an :except option which match current action name" do controller = WithExceptConditional.new controller.process(:show) assert_equal "With Implied Hello show!", controller.response_body end test "when specify an :except option which does not match current action name" do controller = WithExceptConditional.new controller.process(:index) assert_equal "Overwrite Hello index!", controller.response_body end test "layout for anonymous controller" do klass = Class.new(WithString) do def index render :text => 'index', :layout => true end end controller = klass.new controller.process(:index) assert_equal "With String index", controller.response_body end end end end rails-4.2.6/actionview/test/actionpack/abstract/render_test.rb000066400000000000000000000050741266740050600245620ustar00rootroot00000000000000require 'abstract_unit' module AbstractController module Testing class ControllerRenderer < AbstractController::Base include AbstractController::Rendering include ActionView::Rendering def _prefixes %w[renderer] end self.view_paths = [ActionView::FixtureResolver.new( "template.erb" => "With Template", "renderer/default.erb" => "With Default", "renderer/string.erb" => "With String", "renderer/symbol.erb" => "With Symbol", "string/with_path.erb" => "With String With Path", "some/file.erb" => "With File" )] def template render :template => "template" end def file render :file => "some/file" end def inline render :inline => "With <%= :Inline %>" end def text render :text => "With Text" end def default render end def string render "string" end def string_with_path render "string/with_path" end def symbol render :symbol end end class TestRenderer < ActiveSupport::TestCase def setup @controller = ControllerRenderer.new end def test_render_template assert_equal "With Template", @controller.process(:template) assert_equal "With Template", @controller.response_body end def test_render_file assert_equal "With File", @controller.process(:file) assert_equal "With File", @controller.response_body end def test_render_inline assert_equal "With Inline", @controller.process(:inline) assert_equal "With Inline", @controller.response_body end def test_render_text assert_equal "With Text", @controller.process(:text) assert_equal "With Text", @controller.response_body end def test_render_default assert_equal "With Default", @controller.process(:default) assert_equal "With Default", @controller.response_body end def test_render_string assert_equal "With String", @controller.process(:string) assert_equal "With String", @controller.response_body end def test_render_symbol assert_equal "With Symbol", @controller.process(:symbol) assert_equal "With Symbol", @controller.response_body end def test_render_string_with_path assert_equal "With String With Path", @controller.process(:string_with_path) assert_equal "With String With Path", @controller.response_body end end end end rails-4.2.6/actionview/test/actionpack/abstract/views/000077500000000000000000000000001266740050600230465ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/000077500000000000000000000000001266740050600271145ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/testing/000077500000000000000000000000001266740050600305715ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/testing/me3/000077500000000000000000000000001266740050600312555ustar00rootroot00000000000000formatted.html.erb000066400000000000000000000000411266740050600346130ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/testing/me3Hello from me3/formatted.html.erbrails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/testing/me3/index.erb000066400000000000000000000000301266740050600330470ustar00rootroot00000000000000Hello from me3/index.erbrails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/testing/me4/000077500000000000000000000000001266740050600312565ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/testing/me4/index.erb000066400000000000000000000000301266740050600330500ustar00rootroot00000000000000Hello from me4/index.erbrails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/testing/me5/000077500000000000000000000000001266740050600312575ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/abstract_controller/testing/me5/index.erb000066400000000000000000000000301266740050600330510ustar00rootroot00000000000000Hello from me5/index.erbrails-4.2.6/actionview/test/actionpack/abstract/views/action_with_ivars.erb000066400000000000000000000000511266740050600272500ustar00rootroot00000000000000<%= @my_ivar %> from index_with_ivars.erbrails-4.2.6/actionview/test/actionpack/abstract/views/helper_test.erb000066400000000000000000000000621266740050600260540ustar00rootroot00000000000000Hello <%= helpery_test %> : <%= included_method %>rails-4.2.6/actionview/test/actionpack/abstract/views/index.erb000066400000000000000000000000241266740050600246430ustar00rootroot00000000000000Hello from index.erbrails-4.2.6/actionview/test/actionpack/abstract/views/layouts/000077500000000000000000000000001266740050600245465ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/layouts/abstract_controller/000077500000000000000000000000001266740050600306145ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/layouts/abstract_controller/testing/000077500000000000000000000000001266740050600322715ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/abstract/views/layouts/abstract_controller/testing/me4.erb000066400000000000000000000000371266740050600334500ustar00rootroot00000000000000Me4 Enter : <%= yield %> : Exitrails-4.2.6/actionview/test/actionpack/abstract/views/layouts/application.erb000066400000000000000000000000471266740050600275440ustar00rootroot00000000000000Application Enter : <%= yield %> : Exitrails-4.2.6/actionview/test/actionpack/abstract/views/naked_render.erb000066400000000000000000000000331266740050600261550ustar00rootroot00000000000000Hello from naked_render.erbrails-4.2.6/actionview/test/actionpack/controller/000077500000000000000000000000001266740050600222715ustar00rootroot00000000000000rails-4.2.6/actionview/test/actionpack/controller/capture_test.rb000066400000000000000000000036311266740050600253230ustar00rootroot00000000000000require 'abstract_unit' require 'active_support/logger' class CaptureController < ActionController::Base self.view_paths = [ File.dirname(__FILE__) + '/../../fixtures/actionpack' ] def self.controller_name; "test"; end def self.controller_path; "test"; end def content_for @title = nil render :layout => "talk_from_action" end def content_for_with_parameter @title = nil render :layout => "talk_from_action" end def content_for_concatenated @title = nil render :layout => "talk_from_action" end def non_erb_block_content_for @title = nil render :layout => "talk_from_action" end def proper_block_detection @todo = "some todo" end end class CaptureTest < ActionController::TestCase tests CaptureController def setup super # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get # a more accurate simulation of what happens in "real life". @controller.logger = ActiveSupport::Logger.new(nil) @request.host = "www.nextangle.com" end def test_simple_capture get :capturing assert_equal "Dreamy days", @response.body.strip end def test_content_for get :content_for assert_equal expected_content_for_output, @response.body end def test_should_concatentate_content_for get :content_for_concatenated assert_equal expected_content_for_output, @response.body end def test_should_set_content_for_with_parameter get :content_for_with_parameter assert_equal expected_content_for_output, @response.body end def test_non_erb_block_content_for get :non_erb_block_content_for assert_equal expected_content_for_output, @response.body end def test_proper_block_detection get :proper_block_detection assert_equal "some todo", @response.body end private def expected_content_for_output "Putting stuff in the title!\nGreat stuff!" end end rails-4.2.6/actionview/test/actionpack/controller/layout_test.rb000066400000000000000000000161441266740050600252000ustar00rootroot00000000000000require 'abstract_unit' require 'rbconfig' require 'active_support/core_ext/array/extract_options' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. old_load_paths = ActionController::Base.view_paths ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../../fixtures/actionpack/layout_tests/' ] class LayoutTest < ActionController::Base def self.controller_path; 'views' end def self._implied_layout_name; to_s.underscore.gsub(/_controller$/, '') ; end self.view_paths = ActionController::Base.view_paths.dup end module TemplateHandlerHelper def with_template_handler(*extensions, handler) ActionView::Template.register_template_handler(*extensions, handler) yield ensure ActionView::Template.unregister_template_handler(*extensions) end end # Restore view_paths to previous value ActionController::Base.view_paths = old_load_paths class ProductController < LayoutTest end class ItemController < LayoutTest end class ThirdPartyTemplateLibraryController < LayoutTest end module ControllerNameSpace end class ControllerNameSpace::NestedController < LayoutTest end class MultipleExtensions < LayoutTest end class LayoutAutoDiscoveryTest < ActionController::TestCase include TemplateHandlerHelper def setup super @request.host = "www.nextangle.com" end def test_application_layout_is_default_when_no_controller_match @controller = ProductController.new get :hello assert_equal 'layout_test.erb hello.erb', @response.body end def test_controller_name_layout_name_match @controller = ItemController.new get :hello assert_equal 'item.erb hello.erb', @response.body end def test_third_party_template_library_auto_discovers_layout with_template_handler :mab, lambda { |template| template.source.inspect } do @controller = ThirdPartyTemplateLibraryController.new get :hello assert_response :success assert_equal 'layouts/third_party_template_library.mab', @response.body end end def test_namespaced_controllers_auto_detect_layouts1 @controller = ControllerNameSpace::NestedController.new get :hello assert_equal 'controller_name_space/nested.erb hello.erb', @response.body end def test_namespaced_controllers_auto_detect_layouts2 @controller = MultipleExtensions.new get :hello assert_equal 'multiple_extensions.html.erb hello.erb', @response.body.strip end end class DefaultLayoutController < LayoutTest end class StreamingLayoutController < LayoutTest def render(*args) options = args.extract_options! super(*args, options.merge(:stream => true)) end end class AbsolutePathLayoutController < LayoutTest layout File.expand_path(File.expand_path(__FILE__) + '/../../../fixtures/actionpack/layout_tests/layouts/layout_test') end class HasOwnLayoutController < LayoutTest layout 'item' end class HasNilLayoutSymbol < LayoutTest layout :nilz def nilz nil end end class HasNilLayoutProc < LayoutTest layout proc { nil } end class PrependsViewPathController < LayoutTest def hello prepend_view_path File.dirname(__FILE__) + '/../../fixtures/actionpack/layout_tests/alt/' render :layout => 'alt' end end class OnlyLayoutController < LayoutTest layout 'item', :only => "hello" end class ExceptLayoutController < LayoutTest layout 'item', :except => "goodbye" end class SetsLayoutInRenderController < LayoutTest def hello render :layout => 'third_party_template_library' end end class RendersNoLayoutController < LayoutTest def hello render :layout => false end end class LayoutSetInResponseTest < ActionController::TestCase include ActionView::Template::Handlers include TemplateHandlerHelper def test_layout_set_when_using_default_layout @controller = DefaultLayoutController.new get :hello assert_template :layout => "layouts/layout_test" end def test_layout_set_when_using_streaming_layout @controller = StreamingLayoutController.new get :hello assert_template :hello end def test_layout_set_when_set_in_controller @controller = HasOwnLayoutController.new get :hello assert_template :layout => "layouts/item" end def test_layout_symbol_set_in_controller_returning_nil_falls_back_to_default @controller = HasNilLayoutSymbol.new get :hello assert_template layout: "layouts/layout_test" end def test_layout_proc_set_in_controller_returning_nil_falls_back_to_default @controller = HasNilLayoutProc.new get :hello assert_template layout: "layouts/layout_test" end def test_layout_only_exception_when_included @controller = OnlyLayoutController.new get :hello assert_template :layout => "layouts/item" end def test_layout_only_exception_when_excepted @controller = OnlyLayoutController.new get :goodbye assert !@response.body.include?("item.erb"), "#{@response.body.inspect} included 'item.erb'" end def test_layout_except_exception_when_included @controller = ExceptLayoutController.new get :hello assert_template :layout => "layouts/item" end def test_layout_except_exception_when_excepted @controller = ExceptLayoutController.new get :goodbye assert !@response.body.include?("item.erb"), "#{@response.body.inspect} included 'item.erb'" end def test_layout_set_when_using_render with_template_handler :mab, lambda { |template| template.source.inspect } do @controller = SetsLayoutInRenderController.new get :hello assert_template :layout => "layouts/third_party_template_library" end end def test_layout_is_not_set_when_none_rendered @controller = RendersNoLayoutController.new get :hello assert_template :layout => nil end def test_layout_is_picked_from_the_controller_instances_view_path @controller = PrependsViewPathController.new get :hello assert_template :layout => /layouts\/alt/ end def test_absolute_pathed_layout @controller = AbsolutePathLayoutController.new get :hello assert_equal "layout_test.erb hello.erb", @response.body.strip end end class SetsNonExistentLayoutFile < LayoutTest layout "nofile" end class LayoutExceptionRaisedTest < ActionController::TestCase def test_exception_raised_when_layout_file_not_found @controller = SetsNonExistentLayoutFile.new assert_raise(ActionView::MissingTemplate) { get :hello } end end class LayoutStatusIsRendered < LayoutTest def hello render :status => 401 end end class LayoutStatusIsRenderedTest < ActionController::TestCase def test_layout_status_is_rendered @controller = LayoutStatusIsRendered.new get :hello assert_response 401 end end unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ class LayoutSymlinkedTest < LayoutTest layout "symlinked/symlinked_layout" end class LayoutSymlinkedIsRenderedTest < ActionController::TestCase def test_symlinked_layout_is_rendered @controller = LayoutSymlinkedTest.new get :hello assert_response 200 assert_template :layout => "layouts/symlinked/symlinked_layout" end end end rails-4.2.6/actionview/test/actionpack/controller/render_test.rb000066400000000000000000001132131266740050600251350ustar00rootroot00000000000000require 'abstract_unit' require 'active_model' class ApplicationController < ActionController::Base self.view_paths = File.join(FIXTURE_LOAD_PATH, "actionpack") end class Customer < Struct.new(:name, :id) extend ActiveModel::Naming include ActiveModel::Conversion undef_method :to_json def to_xml(options={}) if options[:builder] options[:builder].name name else "#{name}" end end def to_js(options={}) "name: #{name.inspect}" end alias :to_text :to_js def errors [] end def persisted? id.present? end end module Quiz #Models class Question < Struct.new(:name, :id) extend ActiveModel::Naming include ActiveModel::Conversion def persisted? id.present? end end # Controller class QuestionsController < ApplicationController def new render :partial => Quiz::Question.new("Namespaced Partial") end end end class BadCustomer < Customer; end class GoodCustomer < Customer; end module Fun class GamesController < ApplicationController def hello_world; end def nested_partial_with_form_builder render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) end end end class TestController < ApplicationController protect_from_forgery before_action :set_variable_for_layout class LabellingFormBuilder < ActionView::Helpers::FormBuilder end layout :determine_layout def name nil end private :name helper_method :name def hello_world end def hello_world_file render :file => File.expand_path("../../../fixtures/actionpack/hello", __FILE__), :formats => [:html] end # :ported: def render_hello_world render "test/hello_world" end def render_hello_world_with_last_modified_set response.last_modified = Date.new(2008, 10, 10).to_time render "test/hello_world" end # :ported: compatibility def render_hello_world_with_forward_slash render "/test/hello_world" end # :ported: def render_template_in_top_directory render :template => 'shared' end # :deprecated: def render_template_in_top_directory_with_slash render '/shared' end # :ported: def render_hello_world_from_variable @person = "david" render :text => "hello #{@person}" end # :ported: def render_action_hello_world render :action => "hello_world" end def render_action_upcased_hello_world render :action => "Hello_world" end def render_action_hello_world_as_string render "hello_world" end def render_action_hello_world_with_symbol render :action => :hello_world end # :ported: def render_text_hello_world render :text => "hello world" end # :ported: def render_text_hello_world_with_layout @variable_for_layout = ", I am here!" render :text => "hello world", :layout => true end def hello_world_with_layout_false render :layout => false end # :ported: def render_file_with_instance_variables @secret = 'in the sauce' path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar') render :file => path end # :ported: def render_file_not_using_full_path @secret = 'in the sauce' render :file => 'test/render_file_with_ivar' end def render_file_not_using_full_path_with_dot_in_path @secret = 'in the sauce' render :file => 'test/dot.directory/render_file_with_ivar' end def render_file_using_pathname @secret = 'in the sauce' render :file => Pathname.new(File.dirname(__FILE__)).join('..', '..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar') end def render_file_from_template @secret = 'in the sauce' @path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')) end def render_file_with_locals path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals') render :file => path, :locals => {:secret => 'in the sauce'} end def render_file_as_string_with_locals path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals')) render file: path, :locals => {:secret => 'in the sauce'} end def accessing_request_in_template render :inline => "Hello: <%= request.host %>" end def accessing_logger_in_template render :inline => "<%= logger.class %>" end def accessing_action_name_in_template render :inline => "<%= action_name %>" end def accessing_controller_name_in_template render :inline => "<%= controller_name %>" end # :ported: def render_custom_code render :text => "hello world", :status => 404 end # :ported: def render_text_with_nil render :text => nil end # :ported: def render_text_with_false render :text => false end def render_text_with_resource render :text => Customer.new("David") end # :ported: def render_nothing_with_appendix render :text => "appended" end # This test is testing 3 things: # render :file in AV :ported: # render :template in AC :ported: # setting content type def render_xml_hello @name = "David" render :template => "test/hello" end def render_xml_hello_as_string_template @name = "David" render "test/hello" end def render_line_offset render :inline => '<% raise %>', :locals => {:foo => 'bar'} end def heading head :ok end def greeting # let's just rely on the template end # :ported: def blank_response render :text => ' ' end # :ported: def layout_test render :action => "hello_world" end # :ported: def builder_layout_test @name = nil render :action => "hello", :layout => "layouts/builder" end # :move: test this in Action View def builder_partial_test render :action => "hello_world_container" end # :ported: def partials_list @test_unchanged = 'hello' @customers = [ Customer.new("david"), Customer.new("mary") ] render :action => "list" end def partial_only render :partial => true end def hello_in_a_string @customers = [ Customer.new("david"), Customer.new("mary") ] render :text => "How's there? " + render_to_string(:template => "test/list") end def accessing_params_in_template render :inline => "Hello: <%= params[:name] %>" end def accessing_local_assigns_in_inline_template name = params[:local_name] render :inline => "<%= 'Goodbye, ' + local_name %>", :locals => { :local_name => name } end def render_implicit_html_template_from_xhr_request end def render_implicit_js_template_without_layout end def formatted_html_erb end def formatted_xml_erb end def render_to_string_test @foo = render_to_string :inline => "this is a test" end def default_render @alternate_default_render ||= nil if @alternate_default_render @alternate_default_render.call else super end end def render_action_hello_world_as_symbol render :action => :hello_world end def layout_test_with_different_layout render :action => "hello_world", :layout => "standard" end def layout_test_with_different_layout_and_string_action render "hello_world", :layout => "standard" end def layout_test_with_different_layout_and_symbol_action render :hello_world, :layout => "standard" end def rendering_without_layout render :action => "hello_world", :layout => false end def layout_overriding_layout render :action => "hello_world", :layout => "standard" end def rendering_nothing_on_layout render :nothing => true end def render_to_string_with_assigns @before = "i'm before the render" render_to_string :text => "foo" @after = "i'm after the render" render :template => "test/hello_world" end def render_to_string_with_exception render_to_string :file => "exception that will not be caught - this will certainly not work" end def render_to_string_with_caught_exception @before = "i'm before the render" begin render_to_string :file => "exception that will be caught- hope my future instance vars still work!" rescue end @after = "i'm after the render" render :template => "test/hello_world" end def accessing_params_in_template_with_layout render :layout => true, :inline => "Hello: <%= params[:name] %>" end # :ported: def render_with_explicit_template render :template => "test/hello_world" end def render_with_explicit_unescaped_template render :template => "test/h*llo_world" end def render_with_explicit_escaped_template render :template => "test/hello,world" end def render_with_explicit_string_template render "test/hello_world" end # :ported: def render_with_explicit_template_with_locals render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' } end # :ported: def double_render render :text => "hello" render :text => "world" end def double_redirect redirect_to :action => "double_render" redirect_to :action => "double_render" end def render_and_redirect render :text => "hello" redirect_to :action => "double_render" end def render_to_string_and_render @stuff = render_to_string :text => "here is some cached stuff" render :text => "Hi web users! #{@stuff}" end def render_to_string_with_inline_and_render render_to_string :inline => "<%= 'dlrow olleh'.reverse %>" render :template => "test/hello_world" end def rendering_with_conflicting_local_vars @name = "David" render :action => "potential_conflicts" end def hello_world_from_rxml_using_action render :action => "hello_world_from_rxml", :handlers => [:builder] end # :deprecated: def hello_world_from_rxml_using_template render :template => "test/hello_world_from_rxml", :handlers => [:builder] end def action_talk_to_layout # Action template sets variable that's picked up by layout end # :addressed: def render_text_with_assigns @hello = "world" render :text => "foo" end def yield_content_for render :action => "content_for", :layout => "yield" end def render_content_type_from_body response.content_type = Mime::RSS render :text => "hello world!" end def render_using_layout_around_block render :action => "using_layout_around_block" end def render_using_layout_around_block_in_main_layout_and_within_content_for_layout render :action => "using_layout_around_block", :layout => "layouts/block_with_layout" end def partial_formats_html render :partial => 'partial', :formats => [:html] end def partial render :partial => 'partial' end def partial_html_erb render :partial => 'partial_html_erb' end def render_to_string_with_partial @partial_only = render_to_string :partial => "partial_only" @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") } render :template => "test/hello_world" end def render_to_string_with_template_and_html_partial @text = render_to_string :template => "test/with_partial", :formats => [:text] @html = render_to_string :template => "test/with_partial", :formats => [:html] render :template => "test/with_html_partial" end def render_to_string_and_render_with_different_formats @html = render_to_string :template => "test/with_partial", :formats => [:html] render :template => "test/with_partial", :formats => [:text] end def render_template_within_a_template_with_other_format render :template => "test/with_xml_template", :formats => [:html], :layout => "with_html_partial" end def partial_with_counter render :partial => "counter", :locals => { :counter_counter => 5 } end def partial_with_locals render :partial => "customer", :locals => { :customer => Customer.new("david") } end def partial_with_form_builder render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) end def partial_with_form_builder_subclass render :partial => LabellingFormBuilder.new(:post, nil, view_context, {}) end def partial_collection render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ] end def partial_collection_with_as render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer end def partial_collection_with_iteration render partial: "customer_iteration", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new('christine') ] end def partial_collection_with_as_and_iteration render partial: "customer_iteration_with_as", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new('christine') ], as: :client end def partial_collection_with_counter render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ] end def partial_collection_with_as_and_counter render :partial => "customer_counter_with_as", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :client end def partial_collection_with_locals render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } end def partial_collection_with_spacer render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ] end def partial_collection_with_spacer_which_uses_render render :partial => "customer", :spacer_template => "partial_with_partial", :collection => [ Customer.new("david"), Customer.new("mary") ] end def partial_collection_shorthand_with_locals render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } end def partial_collection_shorthand_with_different_types_of_records render :partial => [ BadCustomer.new("mark"), GoodCustomer.new("craig"), BadCustomer.new("john"), GoodCustomer.new("zach"), GoodCustomer.new("brandon"), BadCustomer.new("dan") ], :locals => { :greeting => "Bonjour" } end def empty_partial_collection render :partial => "customer", :collection => [] end def partial_collection_shorthand_with_different_types_of_records_with_counter partial_collection_shorthand_with_different_types_of_records end def missing_partial render :partial => 'thisFileIsntHere' end def partial_with_hash_object render :partial => "hash_object", :object => {:first_name => "Sam"} end def partial_with_nested_object render :partial => "quiz/questions/question", :object => Quiz::Question.new("first") end def partial_with_nested_object_shorthand render Quiz::Question.new("first") end def partial_hash_collection render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ] end def partial_hash_collection_with_locals render :partial => "hash_greeting", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ], :locals => { :greeting => "Hola" } end def partial_with_implicit_local_assignment @customer = Customer.new("Marcel") render :partial => "customer" end def render_call_to_partial_with_layout render :action => "calling_partial_with_layout" end def render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout" end before_action only: :render_with_filters do request.format = :xml end # Ensure that the before filter is executed *before* self.formats is set. def render_with_filters render :action => :formatted_xml_erb end private def set_variable_for_layout @variable_for_layout = nil end def determine_layout case action_name when "hello_world", "layout_test", "rendering_without_layout", "rendering_nothing_on_layout", "render_text_hello_world", "render_text_hello_world_with_layout", "hello_world_with_layout_false", "partial_only", "accessing_params_in_template", "accessing_params_in_template_with_layout", "render_with_explicit_template", "render_with_explicit_string_template", "update_page", "update_page_with_instance_variables" "layouts/standard" when "action_talk_to_layout", "layout_overriding_layout" "layouts/talk_from_action" when "render_implicit_html_template_from_xhr_request" (request.xhr? ? 'layouts/xhr' : 'layouts/standard') end end end class RenderTest < ActionController::TestCase tests TestController def setup # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get # a more accurate simulation of what happens in "real life". super @controller.logger = ActiveSupport::Logger.new(nil) ActionView::Base.logger = ActiveSupport::Logger.new(nil) @request.host = "www.nextangle.com" end def teardown ActionView::Base.logger = nil end # :ported: def test_simple_show get :hello_world assert_response 200 assert_response :success assert_template "test/hello_world" assert_equal "Hello world!", @response.body end # :ported: def test_renders_default_template_for_missing_action get :'hyphen-ated' assert_template 'test/hyphen-ated' end # :ported: def test_render get :render_hello_world assert_template "test/hello_world" end def test_line_offset exc = assert_raises ActionView::Template::Error do get :render_line_offset end line = exc.backtrace.first assert(line =~ %r{:(\d+):}) assert_equal "1", $1, "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}" end # :ported: compatibility def test_render_with_forward_slash get :render_hello_world_with_forward_slash assert_template "test/hello_world" end # :ported: def test_render_in_top_directory get :render_template_in_top_directory assert_template "shared" assert_equal "Elastica", @response.body end # :ported: def test_render_in_top_directory_with_slash get :render_template_in_top_directory_with_slash assert_template "shared" assert_equal "Elastica", @response.body end def test_render_process get :render_action_hello_world_as_string assert_equal ["Hello world!"], @controller.process(:render_action_hello_world_as_string) end # :ported: def test_render_from_variable get :render_hello_world_from_variable assert_equal "hello david", @response.body end # :ported: def test_render_action get :render_action_hello_world assert_template "test/hello_world" end def test_render_action_upcased assert_raise ActionView::MissingTemplate do get :render_action_upcased_hello_world end end # :ported: def test_render_action_hello_world_as_string get :render_action_hello_world_as_string assert_equal "Hello world!", @response.body assert_template "test/hello_world" end # :ported: def test_render_action_with_symbol get :render_action_hello_world_with_symbol assert_template "test/hello_world" end # :ported: def test_render_text get :render_text_hello_world assert_equal "hello world", @response.body end # :ported: def test_do_with_render_text_and_layout get :render_text_hello_world_with_layout assert_equal "hello world, I am here!", @response.body end # :ported: def test_do_with_render_action_and_layout_false get :hello_world_with_layout_false assert_equal 'Hello world!', @response.body end # :ported: def test_render_file_with_instance_variables get :render_file_with_instance_variables assert_equal "The secret is in the sauce\n", @response.body end def test_render_file get :hello_world_file assert_equal "Hello world!", @response.body end # :ported: def test_render_file_not_using_full_path get :render_file_not_using_full_path assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_file_not_using_full_path_with_dot_in_path get :render_file_not_using_full_path_with_dot_in_path assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_file_using_pathname get :render_file_using_pathname assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_file_with_locals get :render_file_with_locals assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_file_as_string_with_locals get :render_file_as_string_with_locals assert_equal "The secret is in the sauce\n", @response.body end # :assessed: def test_render_file_from_template get :render_file_from_template assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_custom_code get :render_custom_code assert_response 404 assert_response :missing assert_equal 'hello world', @response.body end # :ported: def test_render_text_with_nil get :render_text_with_nil assert_response 200 assert_equal '', @response.body end # :ported: def test_render_text_with_false get :render_text_with_false assert_equal 'false', @response.body end # :ported: def test_render_nothing_with_appendix get :render_nothing_with_appendix assert_response 200 assert_equal 'appended', @response.body end def test_render_text_with_resource get :render_text_with_resource assert_equal 'name: "David"', @response.body end # :ported: def test_attempt_to_access_object_method assert_raise(AbstractController::ActionNotFound, "No action responded to [clone]") { get :clone } end # :ported: def test_private_methods assert_raise(AbstractController::ActionNotFound, "No action responded to [determine_layout]") { get :determine_layout } end # :ported: def test_access_to_request_in_view get :accessing_request_in_template assert_equal "Hello: www.nextangle.com", @response.body end def test_access_to_logger_in_view get :accessing_logger_in_template assert_equal "ActiveSupport::Logger", @response.body end # :ported: def test_access_to_action_name_in_view get :accessing_action_name_in_template assert_equal "accessing_action_name_in_template", @response.body end # :ported: def test_access_to_controller_name_in_view get :accessing_controller_name_in_template assert_equal "test", @response.body # name is explicitly set in the controller. end # :ported: def test_render_xml get :render_xml_hello assert_equal "\n

    Hello David

    \n

    This is grand!

    \n\n", @response.body assert_equal "application/xml", @response.content_type end # :ported: def test_render_xml_as_string_template get :render_xml_hello_as_string_template assert_equal "\n

    Hello David

    \n

    This is grand!

    \n\n", @response.body assert_equal "application/xml", @response.content_type end # :ported: def test_render_xml_with_default get :greeting assert_equal "

    This is grand!

    \n", @response.body end # :move: test in AV def test_render_xml_with_partial get :builder_partial_test assert_equal "\n \n\n", @response.body end # :ported: def test_layout_rendering get :layout_test assert_equal "Hello world!", @response.body end def test_render_xml_with_layouts get :builder_layout_test assert_equal "\n\n

    Hello

    \n

    This is grand!

    \n\n
    \n", @response.body end def test_partials_list get :partials_list assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body end def test_render_to_string get :hello_in_a_string assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body end def test_render_to_string_resets_assigns get :render_to_string_test assert_equal "The value of foo is: ::this is a test::\n", @response.body end def test_render_to_string_inline get :render_to_string_with_inline_and_render assert_template "test/hello_world" end # :ported: def test_nested_rendering @controller = Fun::GamesController.new get :hello_world assert_equal "Living in a nested world", @response.body end def test_accessing_params_in_template get :accessing_params_in_template, :name => "David" assert_equal "Hello: David", @response.body end def test_accessing_local_assigns_in_inline_template get :accessing_local_assigns_in_inline_template, :local_name => "Local David" assert_equal "Goodbye, Local David", @response.body assert_equal "text/html", @response.content_type end def test_should_implicitly_render_html_template_from_xhr_request xhr :get, :render_implicit_html_template_from_xhr_request assert_equal "XHR!\nHello HTML!", @response.body end def test_should_implicitly_render_js_template_without_layout xhr :get, :render_implicit_js_template_without_layout, :format => :js assert_no_match %r{}, @response.body end def test_should_render_formatted_template get :formatted_html_erb assert_equal 'formatted html erb', @response.body end def test_should_render_formatted_html_erb_template get :formatted_xml_erb assert_equal 'passed formatted html erb', @response.body end def test_should_render_formatted_html_erb_template_with_bad_accepts_header @request.env["HTTP_ACCEPT"] = "; a=dsf" get :formatted_xml_erb assert_equal 'passed formatted html erb', @response.body end def test_should_render_formatted_html_erb_template_with_faulty_accepts_header @request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*" get :formatted_xml_erb assert_equal 'passed formatted html erb', @response.body end def test_layout_test_with_different_layout get :layout_test_with_different_layout assert_equal "Hello world!", @response.body end def test_layout_test_with_different_layout_and_string_action get :layout_test_with_different_layout_and_string_action assert_equal "Hello world!", @response.body end def test_layout_test_with_different_layout_and_symbol_action get :layout_test_with_different_layout_and_symbol_action assert_equal "Hello world!", @response.body end def test_rendering_without_layout get :rendering_without_layout assert_equal "Hello world!", @response.body end def test_layout_overriding_layout get :layout_overriding_layout assert_no_match %r{}, @response.body end def test_rendering_nothing_on_layout get :rendering_nothing_on_layout assert_equal '', @response.body end def test_render_to_string_doesnt_break_assigns get :render_to_string_with_assigns assert_equal "i'm before the render", assigns(:before) assert_equal "i'm after the render", assigns(:after) end def test_bad_render_to_string_still_throws_exception assert_raise(ActionView::MissingTemplate) { get :render_to_string_with_exception } end def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns assert_nothing_raised { get :render_to_string_with_caught_exception } assert_equal "i'm before the render", assigns(:before) assert_equal "i'm after the render", assigns(:after) end def test_accessing_params_in_template_with_layout get :accessing_params_in_template_with_layout, :name => "David" assert_equal "<html>Hello: David</html>", @response.body end def test_render_with_explicit_template get :render_with_explicit_template assert_response :success end def test_render_with_explicit_unescaped_template assert_raise(ActionView::MissingTemplate) { get :render_with_explicit_unescaped_template } get :render_with_explicit_escaped_template assert_equal "Hello w*rld!", @response.body end def test_render_with_explicit_string_template get :render_with_explicit_string_template assert_equal "<html>Hello world!</html>", @response.body end def test_render_with_filters get :render_with_filters assert_equal "<test>passed formatted xml erb</test>", @response.body end # :ported: def test_double_render assert_raise(AbstractController::DoubleRenderError) { get :double_render } end def test_double_redirect assert_raise(AbstractController::DoubleRenderError) { get :double_redirect } end def test_render_and_redirect assert_raise(AbstractController::DoubleRenderError) { get :render_and_redirect } end # specify the one exception to double render rule - render_to_string followed by render def test_render_to_string_and_render get :render_to_string_and_render assert_equal("Hi web users! here is some cached stuff", @response.body) end def test_rendering_with_conflicting_local_vars get :rendering_with_conflicting_local_vars assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body) end def test_action_talk_to_layout get :action_talk_to_layout assert_equal "<title>Talking to the layout\nAction was here!", @response.body end # :addressed: def test_render_text_with_assigns get :render_text_with_assigns assert_equal "world", assigns["hello"] end # :ported: def test_template_with_locals get :render_with_explicit_template_with_locals assert_equal "The secret is area51\n", @response.body end def test_yield_content_for get :yield_content_for assert_equal "Putting stuff in the title!\nGreat stuff!\n", @response.body end def test_overwritting_rendering_relative_file_with_extension get :hello_world_from_rxml_using_template assert_equal "\n

    Hello

    \n\n", @response.body get :hello_world_from_rxml_using_action assert_equal "\n

    Hello

    \n\n", @response.body end def test_using_layout_around_block get :render_using_layout_around_block assert_equal "Before (David)\nInside from block\nAfter", @response.body end def test_using_layout_around_block_in_main_layout_and_within_content_for_layout get :render_using_layout_around_block_in_main_layout_and_within_content_for_layout assert_equal "Before (Anthony)\nInside from first block in layout\nAfter\nBefore (David)\nInside from block\nAfter\nBefore (Ramm)\nInside from second block in layout\nAfter\n", @response.body end def test_partial_only get :partial_only assert_equal "only partial", @response.body assert_equal "text/html", @response.content_type end def test_should_render_html_formatted_partial get :partial assert_equal "partial html", @response.body assert_equal "text/html", @response.content_type end def test_render_html_formatted_partial_even_with_other_mime_time_in_accept @request.accept = "text/javascript, text/html" get :partial_html_erb assert_equal "partial.html.erb", @response.body.strip assert_equal "text/html", @response.content_type end def test_should_render_html_partial_with_formats get :partial_formats_html assert_equal "partial html", @response.body assert_equal "text/html", @response.content_type end def test_render_to_string_partial get :render_to_string_with_partial assert_equal "only partial", assigns(:partial_only) assert_equal "Hello: david", assigns(:partial_with_locals) assert_equal "text/html", @response.content_type end def test_render_to_string_with_template_and_html_partial get :render_to_string_with_template_and_html_partial assert_equal "**only partial**\n", assigns(:text) assert_equal "only partial\n", assigns(:html) assert_equal "only html partial\n", @response.body assert_equal "text/html", @response.content_type end def test_render_to_string_and_render_with_different_formats get :render_to_string_and_render_with_different_formats assert_equal "only partial\n", assigns(:html) assert_equal "**only partial**\n", @response.body assert_equal "text/plain", @response.content_type end def test_render_template_within_a_template_with_other_format get :render_template_within_a_template_with_other_format expected = "only html partial

    This is grand!

    " assert_equal expected, @response.body.strip assert_equal "text/html", @response.content_type end def test_partial_with_counter get :partial_with_counter assert_equal "5", @response.body end def test_partial_with_locals get :partial_with_locals assert_equal "Hello: david", @response.body end def test_partial_with_form_builder get :partial_with_form_builder assert_match(/
    , :except, :methods and # :include. The following are all valid examples: # # person.serializable_hash(only: 'name') # person.serializable_hash(include: :address) # person.serializable_hash(include: { address: { only: 'city' }}) module Serialization # Returns a serialized hash of your object. # # class Person # include ActiveModel::Serialization # # attr_accessor :name, :age # # def attributes # {'name' => nil, 'age' => nil} # end # # def capitalized_name # name.capitalize # end # end # # person = Person.new # person.name = 'bob' # person.age = 22 # person.serializable_hash # => {"name"=>"bob", "age"=>22} # person.serializable_hash(only: :name) # => {"name"=>"bob"} # person.serializable_hash(except: :name) # => {"age"=>22} # person.serializable_hash(methods: :capitalized_name) # # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"} def serializable_hash(options = nil) options ||= {} attribute_names = attributes.keys if only = options[:only] attribute_names &= Array(only).map(&:to_s) elsif except = options[:except] attribute_names -= Array(except).map(&:to_s) end hash = {} attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) } Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) } serializable_add_includes(options) do |association, records, opts| hash[association.to_s] = if records.respond_to?(:to_ary) records.to_ary.map { |a| a.serializable_hash(opts) } else records.serializable_hash(opts) end end hash end private # Hook method defining how an attribute value should be retrieved for # serialization. By default this is assumed to be an instance named after # the attribute. Override this method in subclasses should you need to # retrieve the value for a given attribute differently: # # class MyClass # include ActiveModel::Serialization # # def initialize(data = {}) # @data = data # end # # def read_attribute_for_serialization(key) # @data[key] # end # end alias :read_attribute_for_serialization :send # Add associations specified via the :include option. # # Expects a block that takes as arguments: # +association+ - name of the association # +records+ - the association record(s) to be serialized # +opts+ - options for the association records def serializable_add_includes(options = {}) #:nodoc: return unless includes = options[:include] unless includes.is_a?(Hash) includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }] end includes.each do |association, opts| if records = send(association) yield association, records, opts end end end end end rails-4.2.6/activemodel/lib/active_model/serializers/000077500000000000000000000000001266740050600226745ustar00rootroot00000000000000rails-4.2.6/activemodel/lib/active_model/serializers/json.rb000066400000000000000000000131041266740050600241710ustar00rootroot00000000000000require 'active_support/json' module ActiveModel module Serializers # == Active \Model \JSON \Serializer module JSON extend ActiveSupport::Concern include ActiveModel::Serialization included do extend ActiveModel::Naming class_attribute :include_root_in_json, instance_writer: false self.include_root_in_json = false end # Returns a hash representing the model. Some configuration can be # passed through +options+. # # The option include_root_in_json controls the top-level behavior # of +as_json+. If +true+, +as_json+ will emit a single root node named # after the object's type. The default value for include_root_in_json # option is +false+. # # user = User.find(1) # user.as_json # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, # # "created_at" => "2006/08/01", "awesome" => true} # # ActiveRecord::Base.include_root_in_json = true # # user.as_json # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16, # # "created_at" => "2006/08/01", "awesome" => true } } # # This behavior can also be achieved by setting the :root option # to +true+ as in: # # user = User.find(1) # user.as_json(root: true) # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16, # # "created_at" => "2006/08/01", "awesome" => true } } # # Without any +options+, the returned Hash will include all the model's # attributes. # # user = User.find(1) # user.as_json # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, # # "created_at" => "2006/08/01", "awesome" => true} # # The :only and :except options can be used to limit # the attributes included, and work similar to the +attributes+ method. # # user.as_json(only: [:id, :name]) # # => { "id" => 1, "name" => "Konata Izumi" } # # user.as_json(except: [:id, :created_at, :age]) # # => { "name" => "Konata Izumi", "awesome" => true } # # To include the result of some method calls on the model use :methods: # # user.as_json(methods: :permalink) # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, # # "created_at" => "2006/08/01", "awesome" => true, # # "permalink" => "1-konata-izumi" } # # To include associations use :include: # # user.as_json(include: :posts) # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, # # "created_at" => "2006/08/01", "awesome" => true, # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" }, # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] } # # Second level and higher order associations work as well: # # user.as_json(include: { posts: { # include: { comments: { # only: :body } }, # only: :title } }) # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, # # "created_at" => "2006/08/01", "awesome" => true, # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ], # # "title" => "Welcome to the weblog" }, # # { "comments" => [ { "body" => "Don't think too hard" } ], # # "title" => "So I was thinking" } ] } def as_json(options = nil) root = if options && options.key?(:root) options[:root] else include_root_in_json end if root root = model_name.element if root == true { root => serializable_hash(options) } else serializable_hash(options) end end # Sets the model +attributes+ from a JSON string. Returns +self+. # # class Person # include ActiveModel::Serializers::JSON # # attr_accessor :name, :age, :awesome # # def attributes=(hash) # hash.each do |key, value| # send("#{key}=", value) # end # end # # def attributes # instance_values # end # end # # json = { name: 'bob', age: 22, awesome:true }.to_json # person = Person.new # person.from_json(json) # => # # person.name # => "bob" # person.age # => 22 # person.awesome # => true # # The default value for +include_root+ is +false+. You can change it to # +true+ if the given JSON string includes a single root node. # # json = { person: { name: 'bob', age: 22, awesome:true } }.to_json # person = Person.new # person.from_json(json, true) # => # # person.name # => "bob" # person.age # => 22 # person.awesome # => true def from_json(json, include_root=include_root_in_json) hash = ActiveSupport::JSON.decode(json) hash = hash.values.first if include_root self.attributes = hash self end end end end rails-4.2.6/activemodel/lib/active_model/serializers/xml.rb000066400000000000000000000167001266740050600240250ustar00rootroot00000000000000require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/time/acts_like' module ActiveModel module Serializers # == Active Model XML Serializer module Xml extend ActiveSupport::Concern include ActiveModel::Serialization included do extend ActiveModel::Naming end class Serializer #:nodoc: class Attribute #:nodoc: attr_reader :name, :value, :type def initialize(name, serializable, value) @name, @serializable = name, serializable if value.acts_like?(:time) && value.respond_to?(:in_time_zone) value = value.in_time_zone end @value = value @type = compute_type end def decorations decorations = {} decorations[:encoding] = 'base64' if type == :binary decorations[:type] = (type == :string) ? nil : type decorations[:nil] = true if value.nil? decorations end protected def compute_type return if value.nil? type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] type ||= :string if value.respond_to?(:to_str) type ||= :yaml type end end class MethodAttribute < Attribute #:nodoc: end attr_reader :options def initialize(serializable, options = nil) @serializable = serializable @options = options ? options.dup : {} end def serializable_hash @serializable.serializable_hash(@options.except(:include)) end def serializable_collection methods = Array(options[:methods]).map(&:to_s) serializable_hash.map do |name, value| name = name.to_s if methods.include?(name) self.class::MethodAttribute.new(name, @serializable, value) else self.class::Attribute.new(name, @serializable, value) end end end def serialize require 'builder' unless defined? ::Builder options[:indent] ||= 2 options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent]) @builder = options[:builder] @builder.instruct! unless options[:skip_instruct] root = (options[:root] || @serializable.model_name.element).to_s root = ActiveSupport::XmlMini.rename_key(root, options) args = [root] args << { xmlns: options[:namespace] } if options[:namespace] args << { type: options[:type] } if options[:type] && !options[:skip_types] @builder.tag!(*args) do add_attributes_and_methods add_includes add_extra_behavior add_procs yield @builder if block_given? end end private def add_extra_behavior end def add_attributes_and_methods serializable_collection.each do |attribute| key = ActiveSupport::XmlMini.rename_key(attribute.name, options) ActiveSupport::XmlMini.to_tag(key, attribute.value, options.merge(attribute.decorations)) end end def add_includes @serializable.send(:serializable_add_includes, options) do |association, records, opts| add_associations(association, records, opts) end end # TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. def add_associations(association, records, opts) merged_options = opts.merge(options.slice(:builder, :indent)) merged_options[:skip_instruct] = true [:skip_types, :dasherize, :camelize].each do |key| merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil? end if records.respond_to?(:to_ary) records = records.to_ary tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) type = options[:skip_types] ? { } : { type: "array" } association_name = association.to_s.singularize merged_options[:root] = association_name if records.empty? @builder.tag!(tag, type) else @builder.tag!(tag, type) do records.each do |record| if options[:skip_types] record_type = {} else record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name record_type = { type: record_class } end record.to_xml merged_options.merge(record_type) end end end else merged_options[:root] = association.to_s unless records.class.to_s.underscore == association.to_s merged_options[:type] = records.class.name end records.to_xml merged_options end end def add_procs if procs = options.delete(:procs) Array(procs).each do |proc| if proc.arity == 1 proc.call(options) else proc.call(options, @serializable) end end end end end # Returns XML representing the model. Configuration can be # passed through +options+. # # Without any +options+, the returned XML string will include all the # model's attributes. # # user = User.find(1) # user.to_xml # # # # 1 # David # 16 # 2011-01-30T22:29:23Z # # # The :only and :except options can be used to limit the # attributes included, and work similar to the +attributes+ method. # # To include the result of some method calls on the model use :methods. # # To include associations use :include. # # For further documentation, see ActiveRecord::Serialization#to_xml def to_xml(options = {}, &block) Serializer.new(self, options).serialize(&block) end # Sets the model +attributes+ from an XML string. Returns +self+. # # class Person # include ActiveModel::Serializers::Xml # # attr_accessor :name, :age, :awesome # # def attributes=(hash) # hash.each do |key, value| # instance_variable_set("@#{key}", value) # end # end # # def attributes # instance_values # end # end # # xml = { name: 'bob', age: 22, awesome:true }.to_xml # person = Person.new # person.from_xml(xml) # => # # person.name # => "bob" # person.age # => 22 # person.awesome # => true def from_xml(xml) self.attributes = Hash.from_xml(xml).values.first self end end end end rails-4.2.6/activemodel/lib/active_model/test_case.rb000066400000000000000000000001321266740050600226330ustar00rootroot00000000000000module ActiveModel #:nodoc: class TestCase < ActiveSupport::TestCase #:nodoc: end end rails-4.2.6/activemodel/lib/active_model/translation.rb000066400000000000000000000044041266740050600232250ustar00rootroot00000000000000module ActiveModel # == Active \Model \Translation # # Provides integration between your object and the Rails internationalization # (i18n) framework. # # A minimal implementation could be: # # class TranslatedPerson # extend ActiveModel::Translation # end # # TranslatedPerson.human_attribute_name('my_attribute') # # => "My attribute" # # This also provides the required class methods for hooking into the # Rails internationalization API, including being able to define a # class based +i18n_scope+ and +lookup_ancestors+ to find translations in # parent classes. module Translation include ActiveModel::Naming # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup. def i18n_scope :activemodel end # When localizing a string, it goes through the lookup returned by this # method, which is used in ActiveModel::Name#human, # ActiveModel::Errors#full_messages and # ActiveModel::Translation#human_attribute_name. def lookup_ancestors self.ancestors.select { |x| x.respond_to?(:model_name) } end # Transforms attribute names into a more human format, such as "First name" # instead of "first_name". # # Person.human_attribute_name("first_name") # => "First name" # # Specify +options+ with additional translating options. def human_attribute_name(attribute, options = {}) options = { count: 1 }.merge!(options) parts = attribute.to_s.split(".") attribute = parts.pop namespace = parts.join("/") unless parts.empty? attributes_scope = "#{self.i18n_scope}.attributes" if namespace defaults = lookup_ancestors.map do |klass| :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}" end defaults << :"#{attributes_scope}.#{namespace}.#{attribute}" else defaults = lookup_ancestors.map do |klass| :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}" end end defaults << :"attributes.#{attribute}" defaults << options.delete(:default) if options[:default] defaults << attribute.humanize options[:default] = defaults I18n.translate(defaults.shift, options) end end end rails-4.2.6/activemodel/lib/active_model/validations.rb000066400000000000000000000330571266740050600232120ustar00rootroot00000000000000require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/except' module ActiveModel # == Active \Model \Validations # # Provides a full validation framework to your objects. # # A minimal implementation could be: # # class Person # include ActiveModel::Validations # # attr_accessor :first_name, :last_name # # validates_each :first_name, :last_name do |record, attr, value| # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z # end # end # # Which provides you with the full standard validation stack that you # know from Active Record: # # person = Person.new # person.valid? # => true # person.invalid? # => false # # person.first_name = 'zoolander' # person.valid? # => false # person.invalid? # => true # person.errors.messages # => {first_name:["starts with z."]} # # Note that ActiveModel::Validations automatically adds an +errors+ # method to your instances initialized with a new ActiveModel::Errors # object, so there is no need for you to do this manually. module Validations extend ActiveSupport::Concern included do extend ActiveModel::Naming extend ActiveModel::Callbacks extend ActiveModel::Translation extend HelperMethods include HelperMethods attr_accessor :validation_context private :validation_context= define_callbacks :validate, scope: :name class_attribute :_validators, instance_writer: false self._validators = Hash.new { |h,k| h[k] = [] } end module ClassMethods # Validates each attribute against a block. # # class Person # include ActiveModel::Validations # # attr_accessor :first_name, :last_name # # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value| # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z # end # end # # Options: # * :on - Specifies the contexts where this validation is active. # Runs in all validation contexts by default (nil). You can pass a symbol # or an array of symbols. (e.g. on: :create or # on: :custom_validation_context or # on: [:create, :custom_validation_context]) # * :allow_nil - Skip validation if attribute is +nil+. # * :allow_blank - Skip validation if attribute is blank. # * :if - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. if: :allow_validation, # or if: Proc.new { |user| user.signup_step > 2 }). The method, # proc or string should return or evaluate to a +true+ or +false+ value. # * :unless - Specifies a method, proc or string to call to # determine if the validation should not occur (e.g. unless: :skip_validation, # or unless: Proc.new { |user| user.signup_step <= 2 }). The # method, proc or string should return or evaluate to a +true+ or +false+ # value. def validates_each(*attr_names, &block) validates_with BlockValidator, _merge_attributes(attr_names), &block end VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc: # Adds a validation method or block to the class. This is useful when # overriding the +validate+ instance method becomes too unwieldy and # you're looking for more descriptive declaration of your validations. # # This can be done with a symbol pointing to a method: # # class Comment # include ActiveModel::Validations # # validate :must_be_friends # # def must_be_friends # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) # end # end # # With a block which is passed with the current record to be validated: # # class Comment # include ActiveModel::Validations # # validate do |comment| # comment.must_be_friends # end # # def must_be_friends # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) # end # end # # Or with a block where self points to the current record to be validated: # # class Comment # include ActiveModel::Validations # # validate do # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) # end # end # # Note that the return value of validation methods is not relevant. # It's not possible to halt the validate callback chain. # # Options: # * :on - Specifies the contexts where this validation is active. # Runs in all validation contexts by default (nil). You can pass a symbol # or an array of symbols. (e.g. on: :create or # on: :custom_validation_context or # on: [:create, :custom_validation_context]) # * :if - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. if: :allow_validation, # or if: Proc.new { |user| user.signup_step > 2 }). The method, # proc or string should return or evaluate to a +true+ or +false+ value. # * :unless - Specifies a method, proc or string to call to # determine if the validation should not occur (e.g. unless: :skip_validation, # or unless: Proc.new { |user| user.signup_step <= 2 }). The # method, proc or string should return or evaluate to a +true+ or +false+ # value. def validate(*args, &block) options = args.extract_options! if args.all? { |arg| arg.is_a?(Symbol) } options.each_key do |k| unless VALID_OPTIONS_FOR_VALIDATE.include?(k) raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?") end end end if options.key?(:on) options = options.dup options[:if] = Array(options[:if]) options[:if].unshift ->(o) { Array(options[:on]).include?(o.validation_context) } end args << options set_callback(:validate, *args, &block) end # List all validators that are being used to validate the model using # +validates_with+ method. # # class Person # include ActiveModel::Validations # # validates_with MyValidator # validates_with OtherValidator, on: :create # validates_with StrictValidator, strict: true # end # # Person.validators # # => [ # # #, # # #, # # # # # ] def validators _validators.values.flatten.uniq end # Clears all of the validators and validations. # # Note that this will clear anything that is being used to validate # the model for both the +validates_with+ and +validate+ methods. # It clears the validators that are created with an invocation of # +validates_with+ and the callbacks that are set by an invocation # of +validate+. # # class Person # include ActiveModel::Validations # # validates_with MyValidator # validates_with OtherValidator, on: :create # validates_with StrictValidator, strict: true # validate :cannot_be_robot # # def cannot_be_robot # errors.add(:base, 'A person cannot be a robot') if person_is_robot # end # end # # Person.validators # # => [ # # #, # # #, # # # # # ] # # If one runs Person.clear_validators! and then checks to see what # validators this class has, you would obtain: # # Person.validators # => [] # # Also, the callback set by validate :cannot_be_robot will be erased # so that: # # Person._validate_callbacks.empty? # => true # def clear_validators! reset_callbacks(:validate) _validators.clear end # List all validators that are being used to validate a specific attribute. # # class Person # include ActiveModel::Validations # # attr_accessor :name , :age # # validates_presence_of :name # validates_inclusion_of :age, in: 0..99 # end # # Person.validators_on(:name) # # => [ # # #, # # ] def validators_on(*attributes) attributes.flat_map do |attribute| _validators[attribute.to_sym] end end # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise. # # class Person # include ActiveModel::Validations # # attr_accessor :name # end # # User.attribute_method?(:name) # => true # User.attribute_method?(:age) # => false def attribute_method?(attribute) method_defined?(attribute) end # Copy validators on inheritance. def inherited(base) #:nodoc: dup = _validators.dup base._validators = dup.each { |k, v| dup[k] = v.dup } super end end # Clean the +Errors+ object if instance is duped. def initialize_dup(other) #:nodoc: @errors = nil super end # Returns the +Errors+ object that holds all information about attribute # error messages. # # class Person # include ActiveModel::Validations # # attr_accessor :name # validates_presence_of :name # end # # person = Person.new # person.valid? # => false # person.errors # => # def errors @errors ||= Errors.new(self) end # Runs all the specified validations and returns +true+ if no errors were # added otherwise +false+. # # Aliased as validate. # # class Person # include ActiveModel::Validations # # attr_accessor :name # validates_presence_of :name # end # # person = Person.new # person.name = '' # person.valid? # => false # person.name = 'david' # person.valid? # => true # # Context can optionally be supplied to define which callbacks to test # against (the context is defined on the validations using :on). # # class Person # include ActiveModel::Validations # # attr_accessor :name # validates_presence_of :name, on: :new # end # # person = Person.new # person.valid? # => true # person.valid?(:new) # => false def valid?(context = nil) current_context, self.validation_context = validation_context, context errors.clear run_validations! ensure self.validation_context = current_context end alias_method :validate, :valid? # Performs the opposite of valid?. Returns +true+ if errors were # added, +false+ otherwise. # # class Person # include ActiveModel::Validations # # attr_accessor :name # validates_presence_of :name # end # # person = Person.new # person.name = '' # person.invalid? # => true # person.name = 'david' # person.invalid? # => false # # Context can optionally be supplied to define which callbacks to test # against (the context is defined on the validations using :on). # # class Person # include ActiveModel::Validations # # attr_accessor :name # validates_presence_of :name, on: :new # end # # person = Person.new # person.invalid? # => false # person.invalid?(:new) # => true def invalid?(context = nil) !valid?(context) end # Hook method defining how an attribute value should be retrieved. By default # this is assumed to be an instance named after the attribute. Override this # method in subclasses should you need to retrieve the value for a given # attribute differently: # # class MyClass # include ActiveModel::Validations # # def initialize(data = {}) # @data = data # end # # def read_attribute_for_validation(key) # @data[key] # end # end alias :read_attribute_for_validation :send protected def run_validations! #:nodoc: _run_validate_callbacks errors.empty? end end end Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file } rails-4.2.6/activemodel/lib/active_model/validations/000077500000000000000000000000001266740050600226555ustar00rootroot00000000000000rails-4.2.6/activemodel/lib/active_model/validations/absence.rb000066400000000000000000000021471266740050600246060ustar00rootroot00000000000000module ActiveModel module Validations # == Active Model Absence Validator class AbsenceValidator < EachValidator #:nodoc: def validate_each(record, attr_name, value) record.errors.add(attr_name, :present, options) if value.present? end end module HelperMethods # Validates that the specified attributes are blank (as defined by # Object#blank?). Happens by default on save. # # class Person < ActiveRecord::Base # validates_absence_of :first_name # end # # The first_name attribute must be in the object and it must be blank. # # Configuration options: # * :message - A custom error message (default is: "must be blank"). # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_absence_of(*attr_names) validates_with AbsenceValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activemodel/lib/active_model/validations/acceptance.rb000066400000000000000000000042121266740050600252670ustar00rootroot00000000000000module ActiveModel module Validations class AcceptanceValidator < EachValidator # :nodoc: def initialize(options) super({ allow_nil: true, accept: "1" }.merge!(options)) setup!(options[:class]) end def validate_each(record, attribute, value) unless value == options[:accept] record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil)) end end private def setup!(klass) attr_readers = attributes.reject { |name| klass.attribute_method?(name) } attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") } klass.send(:attr_reader, *attr_readers) klass.send(:attr_writer, *attr_writers) end end module HelperMethods # Encapsulates the pattern of wanting to validate the acceptance of a # terms of service check box (or similar agreement). # # class Person < ActiveRecord::Base # validates_acceptance_of :terms_of_service # validates_acceptance_of :eula, message: 'must be abided' # end # # If the database column does not exist, the +terms_of_service+ attribute # is entirely virtual. This check is performed only if +terms_of_service+ # is not +nil+ and by default on save. # # Configuration options: # * :message - A custom error message (default is: "must be # accepted"). # * :accept - Specifies value that is considered accepted. # The default value is a string "1", which makes it easy to relate to # an HTML checkbox. This should be set to +true+ if you are validating # a database column, since the attribute is typecast from "1" to +true+ # before validation. # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information. def validates_acceptance_of(*attr_names) validates_with AcceptanceValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activemodel/lib/active_model/validations/callbacks.rb000066400000000000000000000067561266740050600251370ustar00rootroot00000000000000module ActiveModel module Validations # == Active \Model \Validation \Callbacks # # Provides an interface for any class to have +before_validation+ and # +after_validation+ callbacks. # # First, include ActiveModel::Validations::Callbacks from the class you are # creating: # # class MyModel # include ActiveModel::Validations::Callbacks # # before_validation :do_stuff_before_validation # after_validation :do_stuff_after_validation # end # # Like other before_* callbacks if +before_validation+ returns # +false+ then valid? will not be called. module Callbacks extend ActiveSupport::Concern included do include ActiveSupport::Callbacks define_callbacks :validation, terminator: ->(_,result) { result == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name] end module ClassMethods # Defines a callback that will get called right before validation # happens. # # class Person # include ActiveModel::Validations # include ActiveModel::Validations::Callbacks # # attr_accessor :name # # validates_length_of :name, maximum: 6 # # before_validation :remove_whitespaces # # private # # def remove_whitespaces # name.strip! # end # end # # person = Person.new # person.name = ' bob ' # person.valid? # => true # person.name # => "bob" def before_validation(*args, &block) options = args.last if options.is_a?(Hash) && options[:on] options[:if] = Array(options[:if]) options[:on] = Array(options[:on]) options[:if].unshift ->(o) { options[:on].include? o.validation_context } end set_callback(:validation, :before, *args, &block) end # Defines a callback that will get called right after validation # happens. # # class Person # include ActiveModel::Validations # include ActiveModel::Validations::Callbacks # # attr_accessor :name, :status # # validates_presence_of :name # # after_validation :set_status # # private # # def set_status # self.status = errors.empty? # end # end # # person = Person.new # person.name = '' # person.valid? # => false # person.status # => false # person.name = 'bob' # person.valid? # => true # person.status # => true def after_validation(*args, &block) options = args.extract_options! options[:prepend] = true options[:if] = Array(options[:if]) if options[:on] options[:on] = Array(options[:on]) options[:if].unshift ->(o) { options[:on].include? o.validation_context } end set_callback(:validation, :after, *(args << options), &block) end end protected # Overwrite run validations to include callbacks. def run_validations! #:nodoc: _run_validation_callbacks { super } end end end end rails-4.2.6/activemodel/lib/active_model/validations/clusivity.rb000066400000000000000000000031451266740050600252400ustar00rootroot00000000000000require 'active_support/core_ext/range' module ActiveModel module Validations module Clusivity #:nodoc: ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \ "and must be supplied as the :in (or :within) option of the configuration hash" def check_validity! unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym) raise ArgumentError, ERROR_MESSAGE end end private def include?(record, value) members = if delimiter.respond_to?(:call) delimiter.call(record) elsif delimiter.respond_to?(:to_sym) record.send(delimiter) else delimiter end members.send(inclusion_method(members), value) end def delimiter @delimiter ||= options[:in] || options[:within] end # In Ruby 1.9 Range#include? on non-number-or-time-ish ranges checks all # possible values in the range for equality, which is slower but more accurate. # Range#cover? uses the previous logic of comparing a value with the range # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges. def inclusion_method(enumerable) if enumerable.is_a? Range case enumerable.first when Numeric, Time, DateTime :cover? else :include? end else :include? end end end end end rails-4.2.6/activemodel/lib/active_model/validations/confirmation.rb000066400000000000000000000051361266740050600256770ustar00rootroot00000000000000module ActiveModel module Validations class ConfirmationValidator < EachValidator # :nodoc: def initialize(options) super setup!(options[:class]) end def validate_each(record, attribute, value) if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed) human_attribute_name = record.class.human_attribute_name(attribute) record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(attribute: human_attribute_name)) end end private def setup!(klass) klass.send(:attr_reader, *attributes.map do |attribute| :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") end.compact) klass.send(:attr_writer, *attributes.map do |attribute| :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") end.compact) end end module HelperMethods # Encapsulates the pattern of wanting to validate a password or email # address field with a confirmation. # # Model: # class Person < ActiveRecord::Base # validates_confirmation_of :user_name, :password # validates_confirmation_of :email_address, # message: 'should match confirmation' # end # # View: # <%= password_field "person", "password" %> # <%= password_field "person", "password_confirmation" %> # # The added +password_confirmation+ attribute is virtual; it exists only # as an in-memory attribute for validating the password. To achieve this, # the validation adds accessors to the model for the confirmation # attribute. # # NOTE: This check is performed only if +password_confirmation+ is not # +nil+. To require confirmation, make sure to add a presence check for # the confirmation attribute: # # validates_presence_of :password_confirmation, if: :password_changed? # # Configuration options: # * :message - A custom error message (default is: "doesn't match # %{translated_attribute_name}"). # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_confirmation_of(*attr_names) validates_with ConfirmationValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activemodel/lib/active_model/validations/exclusion.rb000066400000000000000000000040411266740050600252120ustar00rootroot00000000000000require "active_model/validations/clusivity" module ActiveModel module Validations class ExclusionValidator < EachValidator # :nodoc: include Clusivity def validate_each(record, attribute, value) if include?(record, value) record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value)) end end end module HelperMethods # Validates that the value of the specified attribute is not in a # particular enumerable object. # # class Person < ActiveRecord::Base # validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here" # validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60' # validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed" # validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] }, # message: 'should not be the same as your username or first name' # validates_exclusion_of :karma, in: :reserved_karmas # end # # Configuration options: # * :in - An enumerable object of items that the value shouldn't # be part of. This can be supplied as a proc, lambda or symbol which returns an # enumerable. If the enumerable is a range the test is performed with # * :within - A synonym(or alias) for :in # Range#cover?, otherwise with include?. # * :message - Specifies a custom error message (default is: "is # reserved"). # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_exclusion_of(*attr_names) validates_with ExclusionValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activemodel/lib/active_model/validations/format.rb000066400000000000000000000115531266740050600244770ustar00rootroot00000000000000module ActiveModel module Validations class FormatValidator < EachValidator # :nodoc: def validate_each(record, attribute, value) if options[:with] regexp = option_call(record, :with) record_error(record, attribute, :with, value) if value.to_s !~ regexp elsif options[:without] regexp = option_call(record, :without) record_error(record, attribute, :without, value) if value.to_s =~ regexp end end def check_validity! unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or" raise ArgumentError, "Either :with or :without must be supplied (but not both)" end check_options_validity :with check_options_validity :without end private def option_call(record, name) option = options[name] option.respond_to?(:call) ? option.call(record) : option end def record_error(record, attribute, name, value) record.errors.add(attribute, :invalid, options.except(name).merge!(value: value)) end def check_options_validity(name) if option = options[name] if option.is_a?(Regexp) if options[:multiline] != true && regexp_using_multiline_anchors?(option) raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \ "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \ ":multiline => true option?" end elsif !option.respond_to?(:call) raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}" end end end def regexp_using_multiline_anchors?(regexp) source = regexp.source source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$")) end end module HelperMethods # Validates whether the value of the specified attribute is of the correct # form, going by the regular expression provided. You can require that the # attribute matches the regular expression: # # class Person < ActiveRecord::Base # validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create # end # # Alternatively, you can require that the specified attribute does _not_ # match the regular expression: # # class Person < ActiveRecord::Base # validates_format_of :email, without: /NOSPAM/ # end # # You can also provide a proc or lambda which will determine the regular # expression that will be used to validate the attribute. # # class Person < ActiveRecord::Base # # Admin can have number as a first letter in their screen name # validates_format_of :screen_name, # with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i } # end # # Note: use \A and \Z to match the start and end of the # string, ^ and $ match the start/end of a line. # # Due to frequent misuse of ^ and $, you need to pass # the multiline: true option in case you use any of these two # anchors in the provided regular expression. In most cases, you should be # using \A and \z. # # You must pass either :with or :without as an option. # In addition, both must be a regular expression or a proc or lambda, or # else an exception will be raised. # # Configuration options: # * :message - A custom error message (default is: "is invalid"). # * :with - Regular expression that if the attribute matches will # result in a successful validation. This can be provided as a proc or # lambda returning regular expression which will be called at runtime. # * :without - Regular expression that if the attribute does not # match will result in a successful validation. This can be provided as # a proc or lambda returning regular expression which will be called at # runtime. # * :multiline - Set to true if your regular expression contains # anchors that match the beginning or end of lines as opposed to the # beginning or end of the string. These anchors are ^ and $. # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_format_of(*attr_names) validates_with FormatValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activemodel/lib/active_model/validations/inclusion.rb000066400000000000000000000037011266740050600252060ustar00rootroot00000000000000require "active_model/validations/clusivity" module ActiveModel module Validations class InclusionValidator < EachValidator # :nodoc: include Clusivity def validate_each(record, attribute, value) unless include?(record, value) record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value)) end end end module HelperMethods # Validates whether the value of the specified attribute is available in a # particular enumerable object. # # class Person < ActiveRecord::Base # validates_inclusion_of :gender, in: %w( m f ) # validates_inclusion_of :age, in: 0..99 # validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list" # validates_inclusion_of :states, in: ->(person) { STATES[person.country] } # validates_inclusion_of :karma, in: :available_karmas # end # # Configuration options: # * :in - An enumerable object of available items. This can be # supplied as a proc, lambda or symbol which returns an enumerable. If the # enumerable is a numerical range the test is performed with Range#cover?, # otherwise with include?. When using a proc or lambda the instance # under validation is passed as an argument. # * :within - A synonym(or alias) for :in # * :message - Specifies a custom error message (default is: "is # not included in the list"). # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_inclusion_of(*attr_names) validates_with InclusionValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activemodel/lib/active_model/validations/length.rb000066400000000000000000000124761266740050600244750ustar00rootroot00000000000000module ActiveModel # == Active \Model Length Validator module Validations class LengthValidator < EachValidator # :nodoc: MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long] def initialize(options) if range = (options.delete(:in) || options.delete(:within)) raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range) options[:minimum], options[:maximum] = range.min, range.max end if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil? options[:minimum] = 1 end super end def check_validity! keys = CHECKS.keys & options.keys if keys.empty? raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.' end keys.each do |key| value = options[key] unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity" end end end def validate_each(record, attribute, value) value = tokenize(value) value_length = value.respond_to?(:length) ? value.length : value.to_s.length errors_options = options.except(*RESERVED_OPTIONS) CHECKS.each do |key, validity_check| next unless check_value = options[key] if !value.nil? || skip_nil_check?(key) next if value_length.send(validity_check, check_value) end errors_options[:count] = check_value default_message = options[MESSAGES[key]] errors_options[:message] ||= default_message if default_message record.errors.add(attribute, MESSAGES[key], errors_options) end end private def tokenize(value) if options[:tokenizer] && value.kind_of?(String) options[:tokenizer].call(value) end || value end def skip_nil_check?(key) key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil? end end module HelperMethods # Validates that the specified attribute matches the length restrictions # supplied. Only one option can be used at a time: # # class Person < ActiveRecord::Base # validates_length_of :first_name, maximum: 30 # validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind" # validates_length_of :fax, in: 7..32, allow_nil: true # validates_length_of :phone, in: 7..32, allow_blank: true # validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name' # validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters' # validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me." # validates_length_of :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.', # tokenizer: ->(str) { str.scan(/\w+/) } # end # # Configuration options: # * :minimum - The minimum size of the attribute. # * :maximum - The maximum size of the attribute. Allows +nil+ by # default if not used with :minimum. # * :is - The exact size of the attribute. # * :within - A range specifying the minimum and maximum size of # the attribute. # * :in - A synonym (or alias) for :within. # * :allow_nil - Attribute may be +nil+; skip validation. # * :allow_blank - Attribute may be blank; skip validation. # * :too_long - The error message if the attribute goes over the # maximum (default is: "is too long (maximum is %{count} characters)"). # * :too_short - The error message if the attribute goes under the # minimum (default is: "is too short (min is %{count} characters)"). # * :wrong_length - The error message if using the :is # method and the attribute is the wrong size (default is: "is the wrong # length (should be %{count} characters)"). # * :message - The error message to use for a :minimum, # :maximum, or :is violation. An alias of the appropriate # too_long/too_short/wrong_length message. # * :tokenizer - Specifies how to split up the attribute string. # (e.g. tokenizer: ->(str) { str.scan(/\w+/) } to count words # as in above example). Defaults to ->(value) { value.split(//) } # which counts individual characters. # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+ and +:strict+. # See ActiveModel::Validation#validates for more information def validates_length_of(*attr_names) validates_with LengthValidator, _merge_attributes(attr_names) end alias_method :validates_size_of, :validates_length_of end end end rails-4.2.6/activemodel/lib/active_model/validations/numericality.rb000066400000000000000000000133011266740050600257050ustar00rootroot00000000000000module ActiveModel module Validations class NumericalityValidator < EachValidator # :nodoc: CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=, equal_to: :==, less_than: :<, less_than_or_equal_to: :<=, odd: :odd?, even: :even?, other_than: :!= }.freeze RESERVED_OPTIONS = CHECKS.keys + [:only_integer] def check_validity! keys = CHECKS.keys - [:odd, :even] options.slice(*keys).each do |option, value| unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol) raise ArgumentError, ":#{option} must be a number, a symbol or a proc" end end end def validate_each(record, attr_name, value) before_type_cast = :"#{attr_name}_before_type_cast" raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast) raw_value ||= value if record_attribute_changed_in_place?(record, attr_name) raw_value = value end return if options[:allow_nil] && raw_value.nil? unless value = parse_raw_value_as_a_number(raw_value) record.errors.add(attr_name, :not_a_number, filtered_options(raw_value)) return end if allow_only_integer?(record) unless value = parse_raw_value_as_an_integer(raw_value) record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value)) return end end options.slice(*CHECKS.keys).each do |option, option_value| case option when :odd, :even unless value.to_i.send(CHECKS[option]) record.errors.add(attr_name, option, filtered_options(value)) end else case option_value when Proc option_value = option_value.call(record) when Symbol option_value = record.send(option_value) end unless value.send(CHECKS[option], option_value) record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value)) end end end end protected def parse_raw_value_as_a_number(raw_value) Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ rescue ArgumentError, TypeError nil end def parse_raw_value_as_an_integer(raw_value) raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\z/ end def filtered_options(value) filtered = options.except(*RESERVED_OPTIONS) filtered[:value] = value filtered end def allow_only_integer?(record) case options[:only_integer] when Symbol record.send(options[:only_integer]) when Proc options[:only_integer].call(record) else options[:only_integer] end end private def record_attribute_changed_in_place?(record, attr_name) record.respond_to?(:attribute_changed_in_place?) && record.attribute_changed_in_place?(attr_name.to_s) end end module HelperMethods # Validates whether the value of the specified attribute is numeric by # trying to convert it to a float with Kernel.Float (if only_integer # is +false+) or applying it to the regular expression /\A[\+\-]?\d+\Z/ # (if only_integer is set to +true+). # # class Person < ActiveRecord::Base # validates_numericality_of :value, on: :create # end # # Configuration options: # * :message - A custom error message (default is: "is not a number"). # * :only_integer - Specifies whether the value has to be an # integer, e.g. an integral value (default is +false+). # * :allow_nil - Skip validation if attribute is +nil+ (default is # +false+). Notice that for fixnum and float columns empty strings are # converted to +nil+. # * :greater_than - Specifies the value must be greater than the # supplied value. # * :greater_than_or_equal_to - Specifies the value must be # greater than or equal the supplied value. # * :equal_to - Specifies the value must be equal to the supplied # value. # * :less_than - Specifies the value must be less than the # supplied value. # * :less_than_or_equal_to - Specifies the value must be less # than or equal the supplied value. # * :other_than - Specifies the value must be other than the # supplied value. # * :odd - Specifies the value must be an odd number. # * :even - Specifies the value must be an even number. # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . # See ActiveModel::Validation#validates for more information # # The following checks can also be supplied with a proc or a symbol which # corresponds to a method: # # * :greater_than # * :greater_than_or_equal_to # * :equal_to # * :less_than # * :less_than_or_equal_to # * :only_integer # # For example: # # class Person < ActiveRecord::Base # validates_numericality_of :width, less_than: ->(person) { person.height } # validates_numericality_of :width, greater_than: :minimum_weight # end def validates_numericality_of(*attr_names) validates_with NumericalityValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activemodel/lib/active_model/validations/presence.rb000066400000000000000000000026341266740050600250130ustar00rootroot00000000000000 module ActiveModel module Validations class PresenceValidator < EachValidator # :nodoc: def validate_each(record, attr_name, value) record.errors.add(attr_name, :blank, options) if value.blank? end end module HelperMethods # Validates that the specified attributes are not blank (as defined by # Object#blank?). Happens by default on save. # # class Person < ActiveRecord::Base # validates_presence_of :first_name # end # # The first_name attribute must be in the object and it cannot be blank. # # If you want to validate the presence of a boolean field (where the real # values are +true+ and +false+), you will want to use # validates_inclusion_of :field_name, in: [true, false]. # # This is due to the way Object#blank? handles boolean values: # false.blank? # => true. # # Configuration options: # * :message - A custom error message (default is: "can't be blank"). # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activemodel/lib/active_model/validations/validates.rb000066400000000000000000000162171266740050600251650ustar00rootroot00000000000000require 'active_support/core_ext/hash/slice' module ActiveModel module Validations module ClassMethods # This method is a shortcut to all default validators and any custom # validator classes ending in 'Validator'. Note that Rails default # validators can be overridden inside specific classes by creating # custom validator classes in their place such as PresenceValidator. # # Examples of using the default rails validators: # # validates :terms, acceptance: true # validates :password, confirmation: true # validates :username, exclusion: { in: %w(admin superuser) } # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create } # validates :age, inclusion: { in: 0..9 } # validates :first_name, length: { maximum: 30 } # validates :age, numericality: true # validates :username, presence: true # validates :username, uniqueness: true # # The power of the +validates+ method comes when using custom validators # and default validators in one call for a given attribute. # # class EmailValidator < ActiveModel::EachValidator # def validate_each(record, attribute, value) # record.errors.add attribute, (options[:message] || "is not an email") unless # value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i # end # end # # class Person # include ActiveModel::Validations # attr_accessor :name, :email # # validates :name, presence: true, uniqueness: true, length: { maximum: 100 } # validates :email, presence: true, email: true # end # # Validator classes may also exist within the class being validated # allowing custom modules of validators to be included as needed. # # class Film # include ActiveModel::Validations # # class TitleValidator < ActiveModel::EachValidator # def validate_each(record, attribute, value) # record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i # end # end # # validates :name, title: true # end # # Additionally validator classes may be in another namespace and still # used within any class. # # validates :name, :'film/title' => true # # The validators hash can also handle regular expressions, ranges, arrays # and strings in shortcut form. # # validates :email, format: /@/ # validates :gender, inclusion: %w(male female) # validates :password, length: 6..20 # # When using shortcut form, ranges and arrays are passed to your # validator's initializer as options[:in] while other types # including regular expressions and strings are passed as options[:with]. # # There is also a list of options that could be used along with validators: # # * :on - Specifies the contexts where this validation is active. # Runs in all validation contexts by default (nil). You can pass a symbol # or an array of symbols. (e.g. on: :create or # on: :custom_validation_context or # on: [:create, :custom_validation_context]) # * :if - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. if: :allow_validation, # or if: Proc.new { |user| user.signup_step > 2 }). The method, # proc or string should return or evaluate to a +true+ or +false+ value. # * :unless - Specifies a method, proc or string to call to determine # if the validation should not occur (e.g. unless: :skip_validation, # or unless: Proc.new { |user| user.signup_step <= 2 }). The # method, proc or string should return or evaluate to a +true+ or # +false+ value. # * :allow_nil - Skip validation if the attribute is +nil+. # * :allow_blank - Skip validation if the attribute is blank. # * :strict - If the :strict option is set to true # will raise ActiveModel::StrictValidationFailed instead of adding the error. # :strict option can also be set to any other exception. # # Example: # # validates :password, presence: true, confirmation: true, if: :password_required? # validates :token, uniqueness: true, strict: TokenGenerationException # # # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+ # and +:message+ can be given to one specific validator, as a hash: # # validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true def validates(*attributes) defaults = attributes.extract_options!.dup validations = defaults.slice!(*_validates_default_keys) raise ArgumentError, "You need to supply at least one attribute" if attributes.empty? raise ArgumentError, "You need to supply at least one validation" if validations.empty? defaults[:attributes] = attributes validations.each do |key, options| next unless options key = "#{key.to_s.camelize}Validator" begin validator = key.include?('::') ? key.constantize : const_get(key) rescue NameError raise ArgumentError, "Unknown validator: '#{key}'" end validates_with(validator, defaults.merge(_parse_validates_options(options))) end end # This method is used to define validations that cannot be corrected by end # users and are considered exceptional. So each validator defined with bang # or :strict option set to true will always raise # ActiveModel::StrictValidationFailed instead of adding error # when validation fails. See validates for more information about # the validation itself. # # class Person # include ActiveModel::Validations # # attr_accessor :name # validates! :name, presence: true # end # # person = Person.new # person.name = '' # person.valid? # # => ActiveModel::StrictValidationFailed: Name can't be blank def validates!(*attributes) options = attributes.extract_options! options[:strict] = true validates(*(attributes << options)) end protected # When creating custom validators, it might be useful to be able to specify # additional default keys. This can be done by overwriting this method. def _validates_default_keys # :nodoc: [:if, :unless, :on, :allow_blank, :allow_nil , :strict] end def _parse_validates_options(options) # :nodoc: case options when TrueClass {} when Hash options when Range, Array { in: options } else { with: options } end end end end end rails-4.2.6/activemodel/lib/active_model/validations/with.rb000066400000000000000000000121431266740050600241560ustar00rootroot00000000000000module ActiveModel module Validations module HelperMethods private def _merge_attributes(attr_names) options = attr_names.extract_options!.symbolize_keys attr_names.flatten! options[:attributes] = attr_names options end end class WithValidator < EachValidator # :nodoc: def validate_each(record, attr, val) method_name = options[:with] if record.method(method_name).arity == 0 record.send method_name else record.send method_name, attr end end end module ClassMethods # Passes the record off to the class or classes specified and allows them # to add errors based on more complex conditions. # # class Person # include ActiveModel::Validations # validates_with MyValidator # end # # class MyValidator < ActiveModel::Validator # def validate(record) # if some_complex_logic # record.errors.add :base, 'This record is invalid' # end # end # # private # def some_complex_logic # # ... # end # end # # You may also pass it multiple classes, like so: # # class Person # include ActiveModel::Validations # validates_with MyValidator, MyOtherValidator, on: :create # end # # Configuration options: # * :on - Specifies the contexts where this validation is active. # Runs in all validation contexts by default (nil). You can pass a symbol # or an array of symbols. (e.g. on: :create or # on: :custom_validation_context or # on: [:create, :custom_validation_context]) # * :if - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. if: :allow_validation, # or if: Proc.new { |user| user.signup_step > 2 }). # The method, proc or string should return or evaluate to a +true+ or # +false+ value. # * :unless - Specifies a method, proc or string to call to # determine if the validation should not occur # (e.g. unless: :skip_validation, or # unless: Proc.new { |user| user.signup_step <= 2 }). # The method, proc or string should return or evaluate to a +true+ or # +false+ value. # * :strict - Specifies whether validation should be strict. # See ActiveModel::Validation#validates! for more information. # # If you pass any additional configuration options, they will be passed # to the class and available as +options+: # # class Person # include ActiveModel::Validations # validates_with MyValidator, my_custom_key: 'my custom value' # end # # class MyValidator < ActiveModel::Validator # def validate(record) # options[:my_custom_key] # => "my custom value" # end # end def validates_with(*args, &block) options = args.extract_options! options[:class] = self args.each do |klass| validator = klass.new(options, &block) if validator.respond_to?(:attributes) && !validator.attributes.empty? validator.attributes.each do |attribute| _validators[attribute.to_sym] << validator end else _validators[nil] << validator end validate(validator, options) end end end # Passes the record off to the class or classes specified and allows them # to add errors based on more complex conditions. # # class Person # include ActiveModel::Validations # # validate :instance_validations # # def instance_validations # validates_with MyValidator # end # end # # Please consult the class method documentation for more information on # creating your own validator. # # You may also pass it multiple classes, like so: # # class Person # include ActiveModel::Validations # # validate :instance_validations, on: :create # # def instance_validations # validates_with MyValidator, MyOtherValidator # end # end # # Standard configuration options (:on, :if and # :unless), which are available on the class version of # +validates_with+, should instead be placed on the +validates+ method # as these are applied and tested in the callback. # # If you pass any additional configuration options, they will be passed # to the class and available as +options+, please refer to the # class version of this method for more information. def validates_with(*args, &block) options = args.extract_options! options[:class] = self.class args.each do |klass| validator = klass.new(options, &block) validator.validate(self) end end end end rails-4.2.6/activemodel/lib/active_model/validator.rb000066400000000000000000000141541266740050600226570ustar00rootroot00000000000000require "active_support/core_ext/module/anonymous" module ActiveModel # == Active \Model \Validator # # A simple base class that can be used along with # ActiveModel::Validations::ClassMethods.validates_with # # class Person # include ActiveModel::Validations # validates_with MyValidator # end # # class MyValidator < ActiveModel::Validator # def validate(record) # if some_complex_logic # record.errors[:base] = "This record is invalid" # end # end # # private # def some_complex_logic # # ... # end # end # # Any class that inherits from ActiveModel::Validator must implement a method # called +validate+ which accepts a +record+. # # class Person # include ActiveModel::Validations # validates_with MyValidator # end # # class MyValidator < ActiveModel::Validator # def validate(record) # record # => The person instance being validated # options # => Any non-standard options passed to validates_with # end # end # # To cause a validation error, you must add to the +record+'s errors directly # from within the validators message. # # class MyValidator < ActiveModel::Validator # def validate(record) # record.errors.add :base, "This is some custom error message" # record.errors.add :first_name, "This is some complex validation" # # etc... # end # end # # To add behavior to the initialize method, use the following signature: # # class MyValidator < ActiveModel::Validator # def initialize(options) # super # @my_custom_field = options[:field_name] || :first_name # end # end # # Note that the validator is initialized only once for the whole application # life cycle, and not on each validation run. # # The easiest way to add custom validators for validating individual attributes # is with the convenient ActiveModel::EachValidator. # # class TitleValidator < ActiveModel::EachValidator # def validate_each(record, attribute, value) # record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value) # end # end # # This can now be used in combination with the +validates+ method # (see ActiveModel::Validations::ClassMethods.validates for more on this). # # class Person # include ActiveModel::Validations # attr_accessor :title # # validates :title, presence: true, title: true # end # # It can be useful to access the class that is using that validator when there are prerequisites such # as an +attr_accessor+ being present. This class is accessible via +options[:class]+ in the constructor. # To setup your validator override the constructor. # # class MyValidator < ActiveModel::Validator # def initialize(options={}) # super # options[:class].send :attr_accessor, :custom_attribute # end # end class Validator attr_reader :options # Returns the kind of the validator. # # PresenceValidator.kind # => :presence # UniquenessValidator.kind # => :uniqueness def self.kind @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous? end # Accepts options that will be made available through the +options+ reader. def initialize(options = {}) @options = options.except(:class).freeze end # Returns the kind for this validator. # # PresenceValidator.new.kind # => :presence # UniquenessValidator.new.kind # => :uniqueness def kind self.class.kind end # Override this method in subclasses with validation logic, adding errors # to the records +errors+ array where necessary. def validate(record) raise NotImplementedError, "Subclasses must implement a validate(record) method." end end # +EachValidator+ is a validator which iterates through the attributes given # in the options hash invoking the validate_each method passing in the # record, attribute and value. # # All Active Model validations are built on top of this validator. class EachValidator < Validator #:nodoc: attr_reader :attributes # Returns a new validator instance. All options will be available via the # +options+ reader, however the :attributes option will be removed # and instead be made available through the +attributes+ reader. def initialize(options) @attributes = Array(options.delete(:attributes)) raise ArgumentError, ":attributes cannot be blank" if @attributes.empty? super check_validity! end # Performs validation on the supplied record. By default this will call # +validates_each+ to determine validity therefore subclasses should # override +validates_each+ with validation logic. def validate(record) attributes.each do |attribute| value = record.read_attribute_for_validation(attribute) next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) validate_each(record, attribute, value) end end # Override this method in subclasses with the validation logic, adding # errors to the records +errors+ array where necessary. def validate_each(record, attribute, value) raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method" end # Hook method that gets called by the initializer allowing verification # that the arguments supplied are valid. You could for example raise an # +ArgumentError+ when invalid options are supplied. def check_validity! end end # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization # and call this block for each attribute being validated. +validates_each+ uses this validator. class BlockValidator < EachValidator #:nodoc: def initialize(options, &block) @block = block super end private def validate_each(record, attribute, value) @block.call(record, attribute, value) end end end rails-4.2.6/activemodel/lib/active_model/version.rb000066400000000000000000000002671266740050600223570ustar00rootroot00000000000000require_relative 'gem_version' module ActiveModel # Returns the version of the currently loaded ActiveModel as a Gem::Version def self.version gem_version end end rails-4.2.6/activemodel/test/000077500000000000000000000000001266740050600161165ustar00rootroot00000000000000rails-4.2.6/activemodel/test/cases/000077500000000000000000000000001266740050600172145ustar00rootroot00000000000000rails-4.2.6/activemodel/test/cases/attribute_methods_test.rb000066400000000000000000000170171266740050600243340ustar00rootroot00000000000000require 'cases/helper' class ModelWithAttributes include ActiveModel::AttributeMethods class << self define_method(:bar) do 'original bar' end end def attributes { foo: 'value of foo', baz: 'value of baz' } end private def attribute(name) attributes[name.to_sym] end end class ModelWithAttributes2 include ActiveModel::AttributeMethods attr_accessor :attributes attribute_method_suffix '_test' private def attribute(name) attributes[name.to_s] end alias attribute_test attribute def private_method "<3 <3" end protected def protected_method "O_o O_o" end end class ModelWithAttributesWithSpaces include ActiveModel::AttributeMethods def attributes { :'foo bar' => 'value of foo bar'} end private def attribute(name) attributes[name.to_sym] end end class ModelWithWeirdNamesAttributes include ActiveModel::AttributeMethods class << self define_method(:'c?d') do 'original c?d' end end def attributes { :'a?b' => 'value of a?b' } end private def attribute(name) attributes[name.to_sym] end end class ModelWithRubyKeywordNamedAttributes include ActiveModel::AttributeMethods def attributes { begin: 'value of begin', end: 'value of end' } end private def attribute(name) attributes[name.to_sym] end end class ModelWithoutAttributesMethod include ActiveModel::AttributeMethods end class AttributeMethodsTest < ActiveModel::TestCase test 'method missing works correctly even if attributes method is not defined' do assert_raises(NoMethodError) { ModelWithoutAttributesMethod.new.foo } end test 'unrelated classes should not share attribute method matchers' do assert_not_equal ModelWithAttributes.send(:attribute_method_matchers), ModelWithAttributes2.send(:attribute_method_matchers) end test '#define_attribute_method generates attribute method' do begin ModelWithAttributes.define_attribute_method(:foo) assert_respond_to ModelWithAttributes.new, :foo assert_equal "value of foo", ModelWithAttributes.new.foo ensure ModelWithAttributes.undefine_attribute_methods end end test '#define_attribute_method does not generate attribute method if already defined in attribute module' do klass = Class.new(ModelWithAttributes) klass.generated_attribute_methods.module_eval do def foo '<3' end end klass.define_attribute_method(:foo) assert_equal '<3', klass.new.foo end test '#define_attribute_method generates a method that is already defined on the host' do klass = Class.new(ModelWithAttributes) do def foo super end end klass.define_attribute_method(:foo) assert_equal 'value of foo', klass.new.foo end test '#define_attribute_method generates attribute method with invalid identifier characters' do begin ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b') assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b' assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b') ensure ModelWithWeirdNamesAttributes.undefine_attribute_methods end end test '#define_attribute_methods works passing multiple arguments' do begin ModelWithAttributes.define_attribute_methods(:foo, :baz) assert_equal "value of foo", ModelWithAttributes.new.foo assert_equal "value of baz", ModelWithAttributes.new.baz ensure ModelWithAttributes.undefine_attribute_methods end end test '#define_attribute_methods generates attribute methods' do begin ModelWithAttributes.define_attribute_methods(:foo) assert_respond_to ModelWithAttributes.new, :foo assert_equal "value of foo", ModelWithAttributes.new.foo ensure ModelWithAttributes.undefine_attribute_methods end end test '#alias_attribute generates attribute_aliases lookup hash' do klass = Class.new(ModelWithAttributes) do define_attribute_methods :foo alias_attribute :bar, :foo end assert_equal({ "bar" => "foo" }, klass.attribute_aliases) end test '#define_attribute_methods generates attribute methods with spaces in their names' do begin ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar') assert_respond_to ModelWithAttributesWithSpaces.new, :'foo bar' assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar') ensure ModelWithAttributesWithSpaces.undefine_attribute_methods end end test '#alias_attribute works with attributes with spaces in their names' do begin ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar') ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar') assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar ensure ModelWithAttributesWithSpaces.undefine_attribute_methods end end test '#alias_attribute works with attributes named as a ruby keyword' do begin ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end]) ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin) ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end) assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to ensure ModelWithRubyKeywordNamedAttributes.undefine_attribute_methods end end test '#undefine_attribute_methods removes attribute methods' do ModelWithAttributes.define_attribute_methods(:foo) ModelWithAttributes.undefine_attribute_methods assert !ModelWithAttributes.new.respond_to?(:foo) assert_raises(NoMethodError) { ModelWithAttributes.new.foo } end test 'accessing a suffixed attribute' do m = ModelWithAttributes2.new m.attributes = { 'foo' => 'bar' } assert_equal 'bar', m.foo assert_equal 'bar', m.foo_test end test 'should not interfere with method_missing if the attr has a private/protected method' do m = ModelWithAttributes2.new m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' } # dispatches to the *method*, not the attribute assert_equal '<3 <3', m.send(:private_method) assert_equal 'O_o O_o', m.send(:protected_method) # sees that a method is already defined, so doesn't intervene assert_raises(NoMethodError) { m.private_method } assert_raises(NoMethodError) { m.protected_method } end class ClassWithProtected protected def protected_method end end test 'should not interfere with respond_to? if the attribute has a private/protected method' do m = ModelWithAttributes2.new m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' } assert !m.respond_to?(:private_method) assert m.respond_to?(:private_method, true) c = ClassWithProtected.new # This is messed up, but it's how Ruby works at the moment. Apparently it will be changed # in the future. assert_equal c.respond_to?(:protected_method), m.respond_to?(:protected_method) assert m.respond_to?(:protected_method, true) end test 'should use attribute_missing to dispatch a missing attribute' do m = ModelWithAttributes2.new m.attributes = { 'foo' => 'bar' } def m.attribute_missing(match, *args, &block) match end match = m.foo_test assert_equal 'foo', match.attr_name assert_equal 'attribute_test', match.target assert_equal 'foo_test', match.method_name end end rails-4.2.6/activemodel/test/cases/callbacks_test.rb000066400000000000000000000062251266740050600225240ustar00rootroot00000000000000require "cases/helper" class CallbacksTest < ActiveModel::TestCase class CallbackValidator def around_create(model) model.callbacks << :before_around_create yield model.callbacks << :after_around_create end end class ModelCallbacks attr_reader :callbacks extend ActiveModel::Callbacks define_model_callbacks :create define_model_callbacks :initialize, only: :after define_model_callbacks :multiple, only: [:before, :around] define_model_callbacks :empty, only: [] before_create :before_create around_create CallbackValidator.new after_create do |model| model.callbacks << :after_create end after_create "@callbacks << :final_callback" def initialize(valid=true) @callbacks, @valid = [], valid end def before_create @callbacks << :before_create end def create run_callbacks :create do @callbacks << :create @valid end end end test "complete callback chain" do model = ModelCallbacks.new model.create assert_equal model.callbacks, [ :before_create, :before_around_create, :create, :after_around_create, :after_create, :final_callback] end test "after callbacks are always appended" do model = ModelCallbacks.new model.create assert_equal model.callbacks.last, :final_callback end test "after callbacks are not executed if the block returns false" do model = ModelCallbacks.new(false) model.create assert_equal model.callbacks, [ :before_create, :before_around_create, :create, :after_around_create] end test "only selects which types of callbacks should be created" do assert !ModelCallbacks.respond_to?(:before_initialize) assert !ModelCallbacks.respond_to?(:around_initialize) assert_respond_to ModelCallbacks, :after_initialize end test "only selects which types of callbacks should be created from an array list" do assert_respond_to ModelCallbacks, :before_multiple assert_respond_to ModelCallbacks, :around_multiple assert !ModelCallbacks.respond_to?(:after_multiple) end test "no callbacks should be created" do assert !ModelCallbacks.respond_to?(:before_empty) assert !ModelCallbacks.respond_to?(:around_empty) assert !ModelCallbacks.respond_to?(:after_empty) end class Violin attr_reader :history def initialize @history = [] end extend ActiveModel::Callbacks define_model_callbacks :create def callback1; self.history << 'callback1'; end def callback2; self.history << 'callback2'; end def create run_callbacks(:create) {} self end end class Violin1 < Violin after_create :callback1, :callback2 end class Violin2 < Violin after_create :callback1 after_create :callback2 end test "after_create callbacks with both callbacks declared in one line" do assert_equal ["callback1", "callback2"], Violin1.new.create.history end test "after_create callbacks with both callbacks declared in different lines" do assert_equal ["callback1", "callback2"], Violin2.new.create.history end end rails-4.2.6/activemodel/test/cases/conversion_test.rb000066400000000000000000000030241266740050600227640ustar00rootroot00000000000000require 'cases/helper' require 'models/contact' require 'models/helicopter' class ConversionTest < ActiveModel::TestCase test "to_model default implementation returns self" do contact = Contact.new assert_equal contact, contact.to_model end test "to_key default implementation returns nil for new records" do assert_nil Contact.new.to_key end test "to_key default implementation returns the id in an array for persisted records" do assert_equal [1], Contact.new(id: 1).to_key end test "to_param default implementation returns nil for new records" do assert_nil Contact.new.to_param end test "to_param default implementation returns a string of ids for persisted records" do assert_equal "1", Contact.new(id: 1).to_param end test "to_param returns the string joined by '-'" do assert_equal "abc-xyz", Contact.new(id: ["abc", "xyz"]).to_param end test "to_param returns nil if to_key is nil" do klass = Class.new(Contact) do def persisted? true end end assert_nil klass.new.to_param end test "to_partial_path default implementation returns a string giving a relative path" do assert_equal "contacts/contact", Contact.new.to_partial_path assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path, "ActiveModel::Conversion#to_partial_path caching should be class-specific" end test "to_partial_path handles namespaced models" do assert_equal "helicopter/comanches/comanche", Helicopter::Comanche.new.to_partial_path end end rails-4.2.6/activemodel/test/cases/dirty_test.rb000066400000000000000000000130711266740050600217350ustar00rootroot00000000000000require "cases/helper" class DirtyTest < ActiveModel::TestCase class DirtyModel include ActiveModel::Dirty define_attribute_methods :name, :color, :size def initialize @name = nil @color = nil @size = nil end def name @name end def name=(val) name_will_change! @name = val end def color @color end def color=(val) color_will_change! unless val == @color @color = val end def size @size end def size=(val) attribute_will_change!(:size) unless val == @size @size = val end def save changes_applied end def reload clear_changes_information end def deprecated_reload reset_changes end end setup do @model = DirtyModel.new end test "setting attribute will result in change" do assert !@model.changed? assert !@model.name_changed? @model.name = "Ringo" assert @model.changed? assert @model.name_changed? end test "list of changed attribute keys" do assert_equal [], @model.changed @model.name = "Paul" assert_equal ['name'], @model.changed end test "changes to attribute values" do assert !@model.changes['name'] @model.name = "John" assert_equal [nil, "John"], @model.changes['name'] end test "checking if an attribute has changed to a particular value" do @model.name = "Ringo" assert @model.name_changed?(from: nil, to: "Ringo") assert_not @model.name_changed?(from: "Pete", to: "Ringo") assert @model.name_changed?(to: "Ringo") assert_not @model.name_changed?(to: "Pete") assert @model.name_changed?(from: nil) assert_not @model.name_changed?(from: "Pete") end test "changes accessible through both strings and symbols" do @model.name = "David" assert_not_nil @model.changes[:name] assert_not_nil @model.changes['name'] end test "be consistent with symbols arguments after the changes are applied" do @model.name = "David" assert @model.attribute_changed?(:name) @model.save @model.name = 'Rafael' assert @model.attribute_changed?(:name) end test "attribute mutation" do @model.instance_variable_set("@name", "Yam") assert !@model.name_changed? @model.name.replace("Hadad") assert !@model.name_changed? @model.name_will_change! @model.name.replace("Baal") assert @model.name_changed? end test "resetting attribute" do @model.name = "Bob" @model.restore_name! assert_nil @model.name assert !@model.name_changed? end test "setting color to same value should not result in change being recorded" do @model.color = "red" assert @model.color_changed? @model.save assert !@model.color_changed? assert !@model.changed? @model.color = "red" assert !@model.color_changed? assert !@model.changed? end test "saving should reset model's changed status" do @model.name = "Alf" assert @model.changed? @model.save assert !@model.changed? assert !@model.name_changed? end test "saving should preserve previous changes" do @model.name = "Jericho Cane" @model.save assert_equal [nil, "Jericho Cane"], @model.previous_changes['name'] end test "previous value is preserved when changed after save" do assert_equal({}, @model.changed_attributes) @model.name = "Paul" assert_equal({ "name" => nil }, @model.changed_attributes) @model.save @model.name = "John" assert_equal({ "name" => "Paul" }, @model.changed_attributes) end test "changing the same attribute multiple times retains the correct original value" do @model.name = "Otto" @model.save @model.name = "DudeFella ManGuy" @model.name = "Mr. Manfredgensonton" assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change assert_equal @model.name_was, "Otto" end test "using attribute_will_change! with a symbol" do @model.size = 1 assert @model.size_changed? end test "reload should reset all changes" do @model.name = 'Dmitry' @model.name_changed? @model.save @model.name = 'Bob' assert_equal [nil, 'Dmitry'], @model.previous_changes['name'] assert_equal 'Dmitry', @model.changed_attributes['name'] @model.reload assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes end test "reset_changes is deprecated" do @model.name = 'Dmitry' @model.name_changed? @model.save @model.name = 'Bob' assert_equal [nil, 'Dmitry'], @model.previous_changes['name'] assert_equal 'Dmitry', @model.changed_attributes['name'] assert_deprecated do @model.deprecated_reload end assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes end test "restore_attributes should restore all previous data" do @model.name = 'Dmitry' @model.color = 'Red' @model.save @model.name = 'Bob' @model.color = 'White' @model.restore_attributes assert_not @model.changed? assert_equal 'Dmitry', @model.name assert_equal 'Red', @model.color end test "restore_attributes can restore only some attributes" do @model.name = 'Dmitry' @model.color = 'Red' @model.save @model.name = 'Bob' @model.color = 'White' @model.restore_attributes(['name']) assert @model.changed? assert_equal 'Dmitry', @model.name assert_equal 'White', @model.color end end rails-4.2.6/activemodel/test/cases/errors_test.rb000066400000000000000000000235471266740050600221270ustar00rootroot00000000000000require "cases/helper" class ErrorsTest < ActiveModel::TestCase class Person extend ActiveModel::Naming def initialize @errors = ActiveModel::Errors.new(self) end attr_accessor :name, :age attr_reader :errors def validate! errors.add(:name, "cannot be nil") if name == nil end def read_attribute_for_validation(attr) send(attr) end def self.human_attribute_name(attr, options = {}) attr end def self.lookup_ancestors [self] end end def test_delete errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' errors.delete(:foo) assert_empty errors[:foo] end def test_include? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' assert errors.include?(:foo), 'errors should include :foo' end def test_dup errors = ActiveModel::Errors.new(self) errors[:foo] = 'bar' errors_dup = errors.dup errors_dup[:bar] = 'omg' assert_not_same errors_dup.messages, errors.messages end def test_has_key? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' assert_equal true, errors.has_key?(:foo), 'errors should have key :foo' end def test_has_no_key errors = ActiveModel::Errors.new(self) assert_equal false, errors.has_key?(:name), 'errors should not have key :name' end def test_key? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' assert_equal true, errors.key?(:foo), 'errors should have key :foo' end def test_no_key errors = ActiveModel::Errors.new(self) assert_equal false, errors.key?(:name), 'errors should not have key :name' end test "clear errors" do person = Person.new person.validate! assert_equal 1, person.errors.count person.errors.clear assert person.errors.empty? end test "get returns the errors for the provided key" do errors = ActiveModel::Errors.new(self) errors[:foo] = "omg" assert_equal ["omg"], errors.get(:foo) end test "sets the error with the provided key" do errors = ActiveModel::Errors.new(self) errors.set(:foo, "omg") assert_equal({ foo: "omg" }, errors.messages) end test "error access is indifferent" do errors = ActiveModel::Errors.new(self) errors[:foo] = "omg" assert_equal ["omg"], errors["foo"] end test "values returns an array of messages" do errors = ActiveModel::Errors.new(self) errors.set(:foo, "omg") errors.set(:baz, "zomg") assert_equal ["omg", "zomg"], errors.values end test "keys returns the error keys" do errors = ActiveModel::Errors.new(self) errors.set(:foo, "omg") errors.set(:baz, "zomg") assert_equal [:foo, :baz], errors.keys end test "detecting whether there are errors with empty?, blank?, include?" do person = Person.new person.errors[:foo] assert person.errors.empty? assert person.errors.blank? assert !person.errors.include?(:foo) end test "adding errors using conditionals with Person#validate!" do person = Person.new person.validate! assert_equal ["name cannot be nil"], person.errors.full_messages assert_equal ["cannot be nil"], person.errors[:name] end test "assign error" do person = Person.new person.errors[:name] = 'should not be nil' assert_equal ["should not be nil"], person.errors[:name] end test "add an error message on a specific attribute" do person = Person.new person.errors.add(:name, "cannot be blank") assert_equal ["cannot be blank"], person.errors[:name] end test "add an error with a symbol" do person = Person.new person.errors.add(:name, :blank) message = person.errors.generate_message(:name, :blank) assert_equal [message], person.errors[:name] end test "add an error with a proc" do person = Person.new message = Proc.new { "cannot be blank" } person.errors.add(:name, message) assert_equal ["cannot be blank"], person.errors[:name] end test "added? detects if a specific error was added to the object" do person = Person.new person.errors.add(:name, "cannot be blank") assert person.errors.added?(:name, "cannot be blank") end test "added? handles symbol message" do person = Person.new person.errors.add(:name, :blank) assert person.errors.added?(:name, :blank) end test "added? handles proc messages" do person = Person.new message = Proc.new { "cannot be blank" } person.errors.add(:name, message) assert person.errors.added?(:name, message) end test "added? defaults message to :invalid" do person = Person.new person.errors.add(:name) assert person.errors.added?(:name) end test "added? matches the given message when several errors are present for the same attribute" do person = Person.new person.errors.add(:name, "cannot be blank") person.errors.add(:name, "is invalid") assert person.errors.added?(:name, "cannot be blank") end test "added? returns false when no errors are present" do person = Person.new assert !person.errors.added?(:name) end test "added? returns false when checking a nonexisting error and other errors are present for the given attribute" do person = Person.new person.errors.add(:name, "is invalid") assert !person.errors.added?(:name, "cannot be blank") end test "size calculates the number of error messages" do person = Person.new person.errors.add(:name, "cannot be blank") assert_equal 1, person.errors.size end test "to_a returns the list of errors with complete messages containing the attribute names" do person = Person.new person.errors.add(:name, "cannot be blank") person.errors.add(:name, "cannot be nil") assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.to_a end test "to_hash returns the error messages hash" do person = Person.new person.errors.add(:name, "cannot be blank") assert_equal({ name: ["cannot be blank"] }, person.errors.to_hash) end test "full_messages creates a list of error messages with the attribute name included" do person = Person.new person.errors.add(:name, "cannot be blank") person.errors.add(:name, "cannot be nil") assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.full_messages end test "full_messages_for contains all the error messages for the given attribute" do person = Person.new person.errors.add(:name, "cannot be blank") person.errors.add(:name, "cannot be nil") assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.full_messages_for(:name) end test "full_messages_for does not contain error messages from other attributes" do person = Person.new person.errors.add(:name, "cannot be blank") person.errors.add(:email, "cannot be blank") assert_equal ["name cannot be blank"], person.errors.full_messages_for(:name) end test "full_messages_for returns an empty list in case there are no errors for the given attribute" do person = Person.new person.errors.add(:name, "cannot be blank") assert_equal [], person.errors.full_messages_for(:email) end test "full_message returns the given message when attribute is :base" do person = Person.new assert_equal "press the button", person.errors.full_message(:base, "press the button") end test "full_message returns the given message with the attribute name included" do person = Person.new assert_equal "name cannot be blank", person.errors.full_message(:name, "cannot be blank") assert_equal "name_test cannot be blank", person.errors.full_message(:name_test, "cannot be blank") end test "as_json creates a json formatted representation of the errors hash" do person = Person.new person.validate! assert_equal({ name: ["cannot be nil"] }, person.errors.as_json) end test "as_json with :full_messages option creates a json formatted representation of the errors containing complete messages" do person = Person.new person.validate! assert_equal({ name: ["name cannot be nil"] }, person.errors.as_json(full_messages: true)) end test "generate_message works without i18n_scope" do person = Person.new assert !Person.respond_to?(:i18n_scope) assert_nothing_raised { person.errors.generate_message(:name, :blank) } end test "add_on_empty generates message" do person = Person.new person.errors.expects(:generate_message).with(:name, :empty, {}) person.errors.add_on_empty :name end test "add_on_empty generates message for multiple attributes" do person = Person.new person.errors.expects(:generate_message).with(:name, :empty, {}) person.errors.expects(:generate_message).with(:age, :empty, {}) person.errors.add_on_empty [:name, :age] end test "add_on_empty generates message with custom default message" do person = Person.new person.errors.expects(:generate_message).with(:name, :empty, { message: 'custom' }) person.errors.add_on_empty :name, message: 'custom' end test "add_on_empty generates message with empty string value" do person = Person.new person.name = '' person.errors.expects(:generate_message).with(:name, :empty, {}) person.errors.add_on_empty :name end test "add_on_blank generates message" do person = Person.new person.errors.expects(:generate_message).with(:name, :blank, {}) person.errors.add_on_blank :name end test "add_on_blank generates message for multiple attributes" do person = Person.new person.errors.expects(:generate_message).with(:name, :blank, {}) person.errors.expects(:generate_message).with(:age, :blank, {}) person.errors.add_on_blank [:name, :age] end test "add_on_blank generates message with custom default message" do person = Person.new person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' }) person.errors.add_on_blank :name, message: 'custom' end end rails-4.2.6/activemodel/test/cases/forbidden_attributes_protection_test.rb000066400000000000000000000020141266740050600272450ustar00rootroot00000000000000require 'cases/helper' require 'active_support/core_ext/hash/indifferent_access' require 'models/account' class ProtectedParams < ActiveSupport::HashWithIndifferentAccess attr_accessor :permitted alias :permitted? :permitted def initialize(attributes) super(attributes) @permitted = false end def permit! @permitted = true self end end class ActiveModelMassUpdateProtectionTest < ActiveSupport::TestCase test "forbidden attributes cannot be used for mass updating" do params = ProtectedParams.new({ "a" => "b" }) assert_raises(ActiveModel::ForbiddenAttributesError) do Account.new.sanitize_for_mass_assignment(params) end end test "permitted attributes can be used for mass updating" do params = ProtectedParams.new({ "a" => "b" }).permit! assert_equal({ "a" => "b" }, Account.new.sanitize_for_mass_assignment(params)) end test "regular attributes should still be allowed" do assert_equal({ a: "b" }, Account.new.sanitize_for_mass_assignment(a: "b")) end end rails-4.2.6/activemodel/test/cases/helper.rb000066400000000000000000000012011266740050600210120ustar00rootroot00000000000000require File.expand_path('../../../../load_paths', __FILE__) require 'config' require 'active_model' require 'active_support/core_ext/string/access' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false require 'active_support/testing/autorun' require 'mocha/setup' # FIXME: stop using mocha # FIXME: we have tests that depend on run order, we should fix that and # remove this method call. require 'active_support/test_case' ActiveSupport::TestCase.test_order = :sorted rails-4.2.6/activemodel/test/cases/lint_test.rb000066400000000000000000000005051266740050600215460ustar00rootroot00000000000000require 'cases/helper' class LintTest < ActiveModel::TestCase include ActiveModel::Lint::Tests class CompliantModel extend ActiveModel::Naming include ActiveModel::Conversion def persisted?() false end def errors Hash.new([]) end end def setup @model = CompliantModel.new end end rails-4.2.6/activemodel/test/cases/model_test.rb000066400000000000000000000031751266740050600217060ustar00rootroot00000000000000require 'cases/helper' class ModelTest < ActiveModel::TestCase include ActiveModel::Lint::Tests module DefaultValue def self.included(klass) klass.class_eval { attr_accessor :hello } end def initialize(*args) @attr ||= 'default value' super end end class BasicModel include DefaultValue include ActiveModel::Model attr_accessor :attr end class BasicModelWithReversedMixins include ActiveModel::Model include DefaultValue attr_accessor :attr end class SimpleModel include ActiveModel::Model attr_accessor :attr end def setup @model = BasicModel.new end def test_initialize_with_params object = BasicModel.new(attr: "value") assert_equal "value", object.attr end def test_initialize_with_params_and_mixins_reversed object = BasicModelWithReversedMixins.new(attr: "value") assert_equal "value", object.attr end def test_initialize_with_nil_or_empty_hash_params_does_not_explode assert_nothing_raised do BasicModel.new() BasicModel.new(nil) BasicModel.new({}) SimpleModel.new(attr: 'value') end end def test_persisted_is_always_false object = BasicModel.new(attr: "value") assert object.persisted? == false end def test_mixin_inclusion_chain object = BasicModel.new assert_equal 'default value', object.attr end def test_mixin_initializer_when_args_exist object = BasicModel.new(hello: 'world') assert_equal 'world', object.hello end def test_mixin_initializer_when_args_dont_exist assert_raises(NoMethodError) { SimpleModel.new(hello: 'world') } end end rails-4.2.6/activemodel/test/cases/naming_test.rb000066400000000000000000000136221266740050600220550ustar00rootroot00000000000000require 'cases/helper' require 'models/contact' require 'models/sheep' require 'models/track_back' require 'models/blog_post' class NamingTest < ActiveModel::TestCase def setup @model_name = ActiveModel::Name.new(Post::TrackBack) end def test_singular assert_equal 'post_track_back', @model_name.singular end def test_plural assert_equal 'post_track_backs', @model_name.plural end def test_element assert_equal 'track_back', @model_name.element end def test_collection assert_equal 'post/track_backs', @model_name.collection end def test_human assert_equal 'Track back', @model_name.human end def test_route_key assert_equal 'post_track_backs', @model_name.route_key end def test_param_key assert_equal 'post_track_back', @model_name.param_key end def test_i18n_key assert_equal :"post/track_back", @model_name.i18n_key end end class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase def setup @model_name = ActiveModel::Name.new(Blog::Post, Blog) end def test_singular assert_equal 'blog_post', @model_name.singular end def test_plural assert_equal 'blog_posts', @model_name.plural end def test_element assert_equal 'post', @model_name.element end def test_collection assert_equal 'blog/posts', @model_name.collection end def test_human assert_equal 'Post', @model_name.human end def test_route_key assert_equal 'posts', @model_name.route_key end def test_param_key assert_equal 'post', @model_name.param_key end def test_i18n_key assert_equal :"blog/post", @model_name.i18n_key end end class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase def setup @model_name = ActiveModel::Name.new(Blog::Post) end def test_singular assert_equal 'blog_post', @model_name.singular end def test_plural assert_equal 'blog_posts', @model_name.plural end def test_element assert_equal 'post', @model_name.element end def test_collection assert_equal 'blog/posts', @model_name.collection end def test_human assert_equal 'Post', @model_name.human end def test_route_key assert_equal 'blog_posts', @model_name.route_key end def test_param_key assert_equal 'blog_post', @model_name.param_key end def test_i18n_key assert_equal :"blog/post", @model_name.i18n_key end end class NamingWithSuppliedModelNameTest < ActiveModel::TestCase def setup @model_name = ActiveModel::Name.new(Blog::Post, nil, 'Article') end def test_singular assert_equal 'article', @model_name.singular end def test_plural assert_equal 'articles', @model_name.plural end def test_element assert_equal 'article', @model_name.element end def test_collection assert_equal 'articles', @model_name.collection end def test_human assert_equal 'Article', @model_name.human end def test_route_key assert_equal 'articles', @model_name.route_key end def test_param_key assert_equal 'article', @model_name.param_key end def test_i18n_key assert_equal :"article", @model_name.i18n_key end end class NamingUsingRelativeModelNameTest < ActiveModel::TestCase def setup @model_name = Blog::Post.model_name end def test_singular assert_equal 'blog_post', @model_name.singular end def test_plural assert_equal 'blog_posts', @model_name.plural end def test_element assert_equal 'post', @model_name.element end def test_collection assert_equal 'blog/posts', @model_name.collection end def test_human assert_equal 'Post', @model_name.human end def test_route_key assert_equal 'posts', @model_name.route_key end def test_param_key assert_equal 'post', @model_name.param_key end def test_i18n_key assert_equal :"blog/post", @model_name.i18n_key end end class NamingHelpersTest < ActiveModel::TestCase def setup @klass = Contact @record = @klass.new @singular = 'contact' @plural = 'contacts' @uncountable = Sheep @singular_route_key = 'contact' @route_key = 'contacts' @param_key = 'contact' end def test_to_model_called_on_record assert_equal 'post_named_track_backs', plural(Post::TrackBack.new) end def test_singular assert_equal @singular, singular(@record) end def test_singular_for_class assert_equal @singular, singular(@klass) end def test_plural assert_equal @plural, plural(@record) end def test_plural_for_class assert_equal @plural, plural(@klass) end def test_route_key assert_equal @route_key, route_key(@record) assert_equal @singular_route_key, singular_route_key(@record) end def test_route_key_for_class assert_equal @route_key, route_key(@klass) assert_equal @singular_route_key, singular_route_key(@klass) end def test_param_key assert_equal @param_key, param_key(@record) end def test_param_key_for_class assert_equal @param_key, param_key(@klass) end def test_uncountable assert uncountable?(@uncountable), "Expected 'sheep' to be uncountable" assert !uncountable?(@klass), "Expected 'contact' to be countable" end def test_uncountable_route_key assert_equal "sheep", singular_route_key(@uncountable) assert_equal "sheep_index", route_key(@uncountable) end private def method_missing(method, *args) ActiveModel::Naming.send(method, *args) end end class NameWithAnonymousClassTest < ActiveModel::TestCase def test_anonymous_class_without_name_argument assert_raises(ArgumentError) do ActiveModel::Name.new(Class.new) end end def test_anonymous_class_with_name_argument model_name = ActiveModel::Name.new(Class.new, nil, "Anonymous") assert_equal "Anonymous", model_name end end class NamingMethodDelegationTest < ActiveModel::TestCase def test_model_name assert_equal Blog::Post.model_name, Blog::Post.new.model_name end end rails-4.2.6/activemodel/test/cases/railtie_test.rb000066400000000000000000000014651266740050600222370ustar00rootroot00000000000000require 'cases/helper' require 'active_support/testing/isolation' class RailtieTest < ActiveModel::TestCase include ActiveSupport::Testing::Isolation def setup require 'active_model/railtie' # Set a fake logger to avoid creating the log directory automatically fake_logger = Logger.new(nil) @app ||= Class.new(::Rails::Application) do config.eager_load = false config.logger = fake_logger end end test 'secure password min_cost is false in the development environment' do Rails.env = 'development' @app.initialize! assert_equal false, ActiveModel::SecurePassword.min_cost end test 'secure password min_cost is true in the test environment' do Rails.env = 'test' @app.initialize! assert_equal true, ActiveModel::SecurePassword.min_cost end end rails-4.2.6/activemodel/test/cases/secure_password_test.rb000066400000000000000000000176051266740050600240210ustar00rootroot00000000000000require 'cases/helper' require 'models/user' require 'models/visitor' class SecurePasswordTest < ActiveModel::TestCase setup do # Used only to speed up tests @original_min_cost = ActiveModel::SecurePassword.min_cost ActiveModel::SecurePassword.min_cost = true @user = User.new @visitor = Visitor.new # Simulate loading an existing user from the DB @existing_user = User.new @existing_user.password_digest = BCrypt::Password.create('password', cost: BCrypt::Engine::MIN_COST) end teardown do ActiveModel::SecurePassword.min_cost = @original_min_cost end test "automatically include ActiveModel::Validations when validations are enabled" do assert_respond_to @user, :valid? end test "don't include ActiveModel::Validations when validations are disabled" do assert_not_respond_to @visitor, :valid? end test "create a new user with validations and valid password/confirmation" do @user.password = 'password' @user.password_confirmation = 'password' assert @user.valid?(:create), 'user should be valid' @user.password = 'a' * 72 @user.password_confirmation = 'a' * 72 assert @user.valid?(:create), 'user should be valid' end test "create a new user with validation and a spaces only password" do @user.password = ' ' * 72 assert @user.valid?(:create), 'user should be valid' end test "create a new user with validation and a blank password" do @user.password = '' assert !@user.valid?(:create), 'user should be invalid' assert_equal 1, @user.errors.count assert_equal ["can't be blank"], @user.errors[:password] end test "create a new user with validation and a nil password" do @user.password = nil assert !@user.valid?(:create), 'user should be invalid' assert_equal 1, @user.errors.count assert_equal ["can't be blank"], @user.errors[:password] end test 'create a new user with validation and password length greater than 72' do @user.password = 'a' * 73 @user.password_confirmation = 'a' * 73 assert !@user.valid?(:create), 'user should be invalid' assert_equal 1, @user.errors.count assert_equal ["is too long (maximum is 72 characters)"], @user.errors[:password] end test "create a new user with validation and a blank password confirmation" do @user.password = 'password' @user.password_confirmation = '' assert !@user.valid?(:create), 'user should be invalid' assert_equal 1, @user.errors.count assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] end test "create a new user with validation and a nil password confirmation" do @user.password = 'password' @user.password_confirmation = nil assert @user.valid?(:create), 'user should be valid' end test "create a new user with validation and an incorrect password confirmation" do @user.password = 'password' @user.password_confirmation = 'something else' assert !@user.valid?(:create), 'user should be invalid' assert_equal 1, @user.errors.count assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] end test "update an existing user with validation and no change in password" do assert @existing_user.valid?(:update), 'user should be valid' end test "update an existing user with validations and valid password/confirmation" do @existing_user.password = 'password' @existing_user.password_confirmation = 'password' assert @existing_user.valid?(:update), 'user should be valid' @existing_user.password = 'a' * 72 @existing_user.password_confirmation = 'a' * 72 assert @existing_user.valid?(:update), 'user should be valid' end test "updating an existing user with validation and a blank password" do @existing_user.password = '' assert @existing_user.valid?(:update), 'user should be valid' end test "updating an existing user with validation and a spaces only password" do @user.password = ' ' * 72 assert @user.valid?(:update), 'user should be valid' end test "updating an existing user with validation and a blank password and password_confirmation" do @existing_user.password = '' @existing_user.password_confirmation = '' assert @existing_user.valid?(:update), 'user should be valid' end test "updating an existing user with validation and a nil password" do @existing_user.password = nil assert !@existing_user.valid?(:update), 'user should be invalid' assert_equal 1, @existing_user.errors.count assert_equal ["can't be blank"], @existing_user.errors[:password] end test 'updating an existing user with validation and password length greater than 72' do @existing_user.password = 'a' * 73 @existing_user.password_confirmation = 'a' * 73 assert !@existing_user.valid?(:update), 'user should be invalid' assert_equal 1, @existing_user.errors.count assert_equal ["is too long (maximum is 72 characters)"], @existing_user.errors[:password] end test "updating an existing user with validation and a blank password confirmation" do @existing_user.password = 'password' @existing_user.password_confirmation = '' assert !@existing_user.valid?(:update), 'user should be invalid' assert_equal 1, @existing_user.errors.count assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] end test "updating an existing user with validation and a nil password confirmation" do @existing_user.password = 'password' @existing_user.password_confirmation = nil assert @existing_user.valid?(:update), 'user should be valid' end test "updating an existing user with validation and an incorrect password confirmation" do @existing_user.password = 'password' @existing_user.password_confirmation = 'something else' assert !@existing_user.valid?(:update), 'user should be invalid' assert_equal 1, @existing_user.errors.count assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] end test "updating an existing user with validation and a blank password digest" do @existing_user.password_digest = '' assert !@existing_user.valid?(:update), 'user should be invalid' assert_equal 1, @existing_user.errors.count assert_equal ["can't be blank"], @existing_user.errors[:password] end test "updating an existing user with validation and a nil password digest" do @existing_user.password_digest = nil assert !@existing_user.valid?(:update), 'user should be invalid' assert_equal 1, @existing_user.errors.count assert_equal ["can't be blank"], @existing_user.errors[:password] end test "setting a blank password should not change an existing password" do @existing_user.password = '' assert @existing_user.password_digest == 'password' end test "setting a nil password should clear an existing password" do @existing_user.password = nil assert_equal nil, @existing_user.password_digest end test "authenticate" do @user.password = "secret" assert !@user.authenticate("wrong") assert @user.authenticate("secret") end test "Password digest cost defaults to bcrypt default cost when min_cost is false" do ActiveModel::SecurePassword.min_cost = false @user.password = "secret" assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost end test "Password digest cost honors bcrypt cost attribute when min_cost is false" do begin original_bcrypt_cost = BCrypt::Engine.cost ActiveModel::SecurePassword.min_cost = false BCrypt::Engine.cost = 5 @user.password = "secret" assert_equal BCrypt::Engine.cost, @user.password_digest.cost ensure BCrypt::Engine.cost = original_bcrypt_cost end end test "Password digest cost can be set to bcrypt min cost to speed up tests" do ActiveModel::SecurePassword.min_cost = true @user.password = "secret" assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost end end rails-4.2.6/activemodel/test/cases/serialization_test.rb000066400000000000000000000146431266740050600234650ustar00rootroot00000000000000require "cases/helper" require 'active_support/core_ext/object/instance_variables' class SerializationTest < ActiveModel::TestCase class User include ActiveModel::Serialization attr_accessor :name, :email, :gender, :address, :friends def initialize(name, email, gender) @name, @email, @gender = name, email, gender @friends = [] end def attributes instance_values.except("address", "friends") end def foo 'i_am_foo' end end class Address include ActiveModel::Serialization attr_accessor :street, :city, :state, :zip def attributes instance_values end end setup do @user = User.new('David', 'david@example.com', 'male') @user.address = Address.new @user.address.street = "123 Lane" @user.address.city = "Springfield" @user.address.state = "CA" @user.address.zip = 11111 @user.friends = [User.new('Joe', 'joe@example.com', 'male'), User.new('Sue', 'sue@example.com', 'female')] end def test_method_serializable_hash_should_work expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} assert_equal expected, @user.serializable_hash end def test_method_serializable_hash_should_work_with_only_option expected = {"name"=>"David"} assert_equal expected, @user.serializable_hash(only: [:name]) end def test_method_serializable_hash_should_work_with_except_option expected = {"gender"=>"male", "email"=>"david@example.com"} assert_equal expected, @user.serializable_hash(except: [:name]) end def test_method_serializable_hash_should_work_with_methods_option expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"} assert_equal expected, @user.serializable_hash(methods: [:foo]) end def test_method_serializable_hash_should_work_with_only_and_methods expected = {"foo"=>"i_am_foo"} assert_equal expected, @user.serializable_hash(only: [], methods: [:foo]) end def test_method_serializable_hash_should_work_with_except_and_methods expected = {"gender"=>"male", "foo"=>"i_am_foo"} assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo]) end def test_should_not_call_methods_that_dont_respond expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} assert_equal expected, @user.serializable_hash(methods: [:bar]) end def test_should_use_read_attribute_for_serialization def @user.read_attribute_for_serialization(n) "Jon" end expected = { "name" => "Jon" } assert_equal expected, @user.serializable_hash(only: :name) end def test_include_option_with_singular_association expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} assert_equal expected, @user.serializable_hash(include: :address) end def test_include_option_with_plural_association expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} assert_equal expected, @user.serializable_hash(include: :friends) end def test_include_option_with_empty_association @user.friends = [] expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[]} assert_equal expected, @user.serializable_hash(include: :friends) end class FriendList def initialize(friends) @friends = friends end def to_ary @friends end end def test_include_option_with_ary @user.friends = FriendList.new(@user.friends) expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} assert_equal expected, @user.serializable_hash(include: :friends) end def test_multiple_includes expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} assert_equal expected, @user.serializable_hash(include: [:address, :friends]) end def test_include_with_options expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "address"=>{"street"=>"123 Lane"}} assert_equal expected, @user.serializable_hash(include: { address: { only: "street" } }) end def test_nested_include @user.friends.first.friends = [@user] expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', "friends"=> [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', "friends"=> []}]} assert_equal expected, @user.serializable_hash(include: { friends: { include: :friends } }) end def test_only_include expected = {"name"=>"David", "friends" => [{"name" => "Joe"}, {"name" => "Sue"}]} assert_equal expected, @user.serializable_hash(only: :name, include: { friends: { only: :name } }) end def test_except_include expected = {"name"=>"David", "email"=>"david@example.com", "friends"=> [{"name" => 'Joe', "email" => 'joe@example.com'}, {"name" => "Sue", "email" => 'sue@example.com'}]} assert_equal expected, @user.serializable_hash(except: :gender, include: { friends: { except: :gender } }) end def test_multiple_includes_with_options expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "address"=>{"street"=>"123 Lane"}, "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} assert_equal expected, @user.serializable_hash(include: [{ address: {only: "street" } }, :friends]) end end rails-4.2.6/activemodel/test/cases/serializers/000077500000000000000000000000001266740050600215505ustar00rootroot00000000000000rails-4.2.6/activemodel/test/cases/serializers/json_serialization_test.rb000066400000000000000000000162351266740050600270510ustar00rootroot00000000000000require 'cases/helper' require 'models/contact' require 'active_support/core_ext/object/instance_variables' class JsonSerializationTest < ActiveModel::TestCase def setup @contact = Contact.new @contact.name = 'Konata Izumi' @contact.age = 16 @contact.created_at = Time.utc(2006, 8, 1) @contact.awesome = true @contact.preferences = { 'shows' => 'anime' } end test "should not include root in json (class method)" do json = @contact.to_json assert_no_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end test "should include root in json if include_root_in_json is true" do begin original_include_root_in_json = Contact.include_root_in_json Contact.include_root_in_json = true json = @contact.to_json assert_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json ensure Contact.include_root_in_json = original_include_root_in_json end end test "should include root in json (option) even if the default is set to false" do json = @contact.to_json(root: true) assert_match %r{^\{"contact":\{}, json end test "should not include root in json (option)" do json = @contact.to_json(root: false) assert_no_match %r{^\{"contact":\{}, json end test "should include custom root in json" do json = @contact.to_json(root: 'json_contact') assert_match %r{^\{"json_contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end test "should encode all encodable attributes" do json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end test "should allow attribute filtering with only" do json = @contact.to_json(only: [:name, :age]) assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert_no_match %r{"awesome":true}, json assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_no_match %r{"preferences":\{"shows":"anime"\}}, json end test "should allow attribute filtering with except" do json = @contact.to_json(except: [:name, :age]) assert_no_match %r{"name":"Konata Izumi"}, json assert_no_match %r{"age":16}, json assert_match %r{"awesome":true}, json assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_match %r{"preferences":\{"shows":"anime"\}}, json end test "methods are called on object" do # Define methods on fixture. def @contact.label; "Has cheezburger"; end def @contact.favorite_quote; "Constraints are liberating"; end # Single method. assert_match %r{"label":"Has cheezburger"}, @contact.to_json(only: :name, methods: :label) # Both methods. methods_json = @contact.to_json(only: :name, methods: [:label, :favorite_quote]) assert_match %r{"label":"Has cheezburger"}, methods_json assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json end test "should return Hash for errors" do contact = Contact.new contact.errors.add :name, "can't be blank" contact.errors.add :name, "is too short (minimum is 2 characters)" contact.errors.add :age, "must be 16 or over" hash = {} hash[:name] = ["can't be blank", "is too short (minimum is 2 characters)"] hash[:age] = ["must be 16 or over"] assert_equal hash.to_json, contact.errors.to_json end test "serializable_hash should not modify options passed in argument" do options = { except: :name } @contact.serializable_hash(options) assert_nil options[:only] assert_equal :name, options[:except] end test "as_json should return a hash if include_root_in_json is true" do begin original_include_root_in_json = Contact.include_root_in_json Contact.include_root_in_json = true json = @contact.as_json assert_kind_of Hash, json assert_kind_of Hash, json['contact'] %w(name age created_at awesome preferences).each do |field| assert_equal @contact.send(field), json['contact'][field] end ensure Contact.include_root_in_json = original_include_root_in_json end end test "from_json should work without a root (class attribute)" do json = @contact.to_json result = Contact.new.from_json(json) assert_equal result.name, @contact.name assert_equal result.age, @contact.age assert_equal Time.parse(result.created_at), @contact.created_at assert_equal result.awesome, @contact.awesome assert_equal result.preferences, @contact.preferences end test "from_json should work without a root (method parameter)" do json = @contact.to_json result = Contact.new.from_json(json, false) assert_equal result.name, @contact.name assert_equal result.age, @contact.age assert_equal Time.parse(result.created_at), @contact.created_at assert_equal result.awesome, @contact.awesome assert_equal result.preferences, @contact.preferences end test "from_json should work with a root (method parameter)" do json = @contact.to_json(root: :true) result = Contact.new.from_json(json, true) assert_equal result.name, @contact.name assert_equal result.age, @contact.age assert_equal Time.parse(result.created_at), @contact.created_at assert_equal result.awesome, @contact.awesome assert_equal result.preferences, @contact.preferences end test "custom as_json should be honored when generating json" do def @contact.as_json(options); { name: name, created_at: created_at }; end json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json assert_no_match %r{"awesome":}, json assert_no_match %r{"preferences":}, json end test "custom as_json options should be extensible" do def @contact.as_json(options = {}); super(options.merge(only: [:name])); end json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json assert_no_match %r{"created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json assert_no_match %r{"awesome":}, json assert_no_match %r{"preferences":}, json end test "Class.model_name should be json encodable" do assert_match %r{"Contact"}, Contact.model_name.to_json end end rails-4.2.6/activemodel/test/cases/serializers/xml_serialization_test.rb000066400000000000000000000200511266740050600266670ustar00rootroot00000000000000require 'cases/helper' require 'models/contact' require 'active_support/core_ext/object/instance_variables' require 'ostruct' module Admin class Contact < ::Contact end end class Customer < Struct.new(:name) end class Address include ActiveModel::Serializers::Xml attr_accessor :street, :city, :state, :zip, :apt_number def attributes instance_values end end class SerializableContact < Contact def serializable_hash(options={}) super(options.merge(only: [:name, :age])) end end class XmlSerializationTest < ActiveModel::TestCase def setup @contact = Contact.new @contact.name = 'aaron stack' @contact.age = 25 @contact.created_at = Time.utc(2006, 8, 1) @contact.awesome = false customer = Customer.new customer.name = "John" @contact.preferences = customer @contact.address = Address.new @contact.address.city = "Springfield" @contact.address.apt_number = 35 @contact.friends = [Contact.new, Contact.new] @contact.contact = SerializableContact.new end test "should serialize default root" do xml = @contact.to_xml assert_match %r{^}, xml assert_match %r{$}, xml end test "should serialize namespaced root" do xml = Admin::Contact.new(@contact.attributes).to_xml assert_match %r{^}, xml assert_match %r{$}, xml end test "should serialize default root with namespace" do xml = @contact.to_xml namespace: "http://xml.rubyonrails.org/contact" assert_match %r{^}, xml assert_match %r{$}, xml end test "should serialize custom root" do xml = @contact.to_xml root: 'xml_contact' assert_match %r{^}, xml assert_match %r{$}, xml end test "should allow undasherized tags" do xml = @contact.to_xml root: 'xml_contact', dasherize: false assert_match %r{^}, xml assert_match %r{$}, xml assert_match %r{}, xml assert_match %r{$}, xml assert_match %r{}, xml assert_match %r{$}, xml assert_match %r{aaron stack}, xml assert_match %r{25}, xml assert_no_match %r{}, xml end test "should allow skipped types" do xml = @contact.to_xml skip_types: true assert_match %r{25}, xml end test "should include yielded additions" do xml_output = @contact.to_xml do |xml| xml.creator "David" end assert_match %r{David}, xml_output end test "should serialize string" do assert_match %r{aaron stack}, @contact.to_xml end test "should serialize nil" do assert_match %r{}, @contact.to_xml(methods: :pseudonyms) end test "should serialize integer" do assert_match %r{25}, @contact.to_xml end test "should serialize datetime" do assert_match %r{2006-08-01T00:00:00Z}, @contact.to_xml end test "should serialize boolean" do assert_match %r{false}, @contact.to_xml end test "should serialize array" do assert_match %r{\s*twitter\s*github\s*}, @contact.to_xml(methods: :social) end test "should serialize hash" do assert_match %r{\s*github\s*}, @contact.to_xml(methods: :network) end test "should serialize yaml" do assert_match %r{--- !ruby/struct:Customer(\s*)\nname: John\n}, @contact.to_xml end test "should call proc on object" do proc = Proc.new { |options| options[:builder].tag!('nationality', 'unknown') } xml = @contact.to_xml(procs: [ proc ]) assert_match %r{unknown}, xml end test "should supply serializable to second proc argument" do proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) } xml = @contact.to_xml(procs: [ proc ]) assert_match %r{kcats noraa}, xml end test "should serialize string correctly when type passed" do xml = @contact.to_xml type: 'Contact' assert_match %r{}, xml assert_match %r{aaron stack}, xml end test "include option with singular association" do xml = @contact.to_xml include: :address, indent: 0 assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true)) end test "include option with plural association" do xml = @contact.to_xml include: :friends, indent: 0 assert_match %r{}, xml assert_match %r{}, xml end class FriendList def initialize(friends) @friends = friends end def to_ary @friends end end test "include option with ary" do @contact.friends = FriendList.new(@contact.friends) xml = @contact.to_xml include: :friends, indent: 0 assert_match %r{}, xml assert_match %r{}, xml end test "multiple includes" do xml = @contact.to_xml indent: 0, skip_instruct: true, include: [ :address, :friends ] assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true)) assert_match %r{}, xml assert_match %r{}, xml end test "include with options" do xml = @contact.to_xml indent: 0, skip_instruct: true, include: { address: { only: :city } } assert xml.include?(%(>
    Springfield
    )) end test "propagates skip_types option to included associations" do xml = @contact.to_xml include: :friends, indent: 0, skip_types: true assert_match %r{}, xml assert_match %r{}, xml end test "propagates skip-types option to included associations and attributes" do xml = @contact.to_xml skip_types: true, include: :address, indent: 0 assert_match %r{
    }, xml assert_match %r{}, xml end test "propagates camelize option to included associations and attributes" do xml = @contact.to_xml camelize: true, include: :address, indent: 0 assert_match %r{
    }, xml assert_match %r{}, xml end test "propagates dasherize option to included associations and attributes" do xml = @contact.to_xml dasherize: false, include: :address, indent: 0 assert_match %r{}, xml end test "don't propagate skip_types if skip_types is defined at the included association level" do xml = @contact.to_xml skip_types: true, include: { address: { skip_types: false } }, indent: 0 assert_match %r{
    }, xml assert_match %r{}, xml end test "don't propagate camelize if camelize is defined at the included association level" do xml = @contact.to_xml camelize: true, include: { address: { camelize: false } }, indent: 0 assert_match %r{
    }, xml assert_match %r{}, xml end test "don't propagate dasherize if dasherize is defined at the included association level" do xml = @contact.to_xml dasherize: false, include: { address: { dasherize: true } }, indent: 0 assert_match %r{
    }, xml assert_match %r{}, xml end test "association with sti" do xml = @contact.to_xml(include: :contact) assert xml.include?(%()) end end rails-4.2.6/activemodel/test/cases/translation_test.rb000066400000000000000000000106621266740050600231430ustar00rootroot00000000000000require 'cases/helper' require 'models/person' class ActiveModelI18nTests < ActiveModel::TestCase def setup I18n.backend = I18n::Backend::Simple.new end def teardown I18n.backend.reload! end def test_translated_model_attributes I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute' } } } assert_equal 'person name attribute', Person.human_attribute_name('name') end def test_translated_model_attributes_with_default I18n.backend.store_translations 'en', attributes: { name: 'name default attribute' } assert_equal 'name default attribute', Person.human_attribute_name('name') end def test_translated_model_attributes_using_default_option assert_equal 'name default attribute', Person.human_attribute_name('name', default: "name default attribute") end def test_translated_model_attributes_using_default_option_as_symbol I18n.backend.store_translations 'en', default_name: 'name default attribute' assert_equal 'name default attribute', Person.human_attribute_name('name', default: :default_name) end def test_translated_model_attributes_falling_back_to_default assert_equal 'Name', Person.human_attribute_name('name') end def test_translated_model_attributes_using_default_option_as_symbol_and_falling_back_to_default assert_equal 'Name', Person.human_attribute_name('name', default: :default_name) end def test_translated_model_attributes_with_symbols I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute'} } } assert_equal 'person name attribute', Person.human_attribute_name(:name) end def test_translated_model_attributes_with_ancestor I18n.backend.store_translations 'en', activemodel: { attributes: { child: { name: 'child name attribute'} } } assert_equal 'child name attribute', Child.human_attribute_name('name') end def test_translated_model_attributes_with_ancestors_fallback I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute'} } } assert_equal 'person name attribute', Child.human_attribute_name('name') end def test_translated_model_attributes_with_attribute_matching_namespaced_model_name I18n.backend.store_translations 'en', activemodel: { attributes: { person: { gender: 'person gender'}, :"person/gender" => { attribute: 'person gender attribute' } } } assert_equal 'person gender', Person.human_attribute_name('gender') assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute') end def test_translated_deeply_nested_model_attributes I18n.backend.store_translations 'en', activemodel: { attributes: { :"person/contacts/addresses" => { street: 'Deeply Nested Address Street' } } } assert_equal 'Deeply Nested Address Street', Person.human_attribute_name('contacts.addresses.street') end def test_translated_nested_model_attributes I18n.backend.store_translations 'en', activemodel: { attributes: { :"person/addresses" => { street: 'Person Address Street' } } } assert_equal 'Person Address Street', Person.human_attribute_name('addresses.street') end def test_translated_nested_model_attributes_with_namespace_fallback I18n.backend.store_translations 'en', activemodel: { attributes: { addresses: { street: 'Cool Address Street' } } } assert_equal 'Cool Address Street', Person.human_attribute_name('addresses.street') end def test_translated_model_names I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } } assert_equal 'person model', Person.model_name.human end def test_translated_model_names_with_sti I18n.backend.store_translations 'en', activemodel: { models: { child: 'child model' } } assert_equal 'child model', Child.model_name.human end def test_translated_model_names_with_ancestors_fallback I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } } assert_equal 'person model', Child.model_name.human end def test_human_does_not_modify_options options = { default: 'person model' } Person.model_name.human(options) assert_equal({ default: 'person model' }, options) end def test_human_attribute_name_does_not_modify_options options = { default: 'Cool gender' } Person.human_attribute_name('gender', options) assert_equal({ default: 'Cool gender' }, options) end end rails-4.2.6/activemodel/test/cases/validations/000077500000000000000000000000001266740050600215315ustar00rootroot00000000000000rails-4.2.6/activemodel/test/cases/validations/absence_validation_test.rb000066400000000000000000000036051266740050600267330ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/person' require 'models/custom_reader' class AbsenceValidationTest < ActiveModel::TestCase teardown do Topic.clear_validators! Person.clear_validators! CustomReader.clear_validators! end def test_validates_absence_of Topic.validates_absence_of(:title, :content) t = Topic.new t.title = "foo" t.content = "bar" assert t.invalid? assert_equal ["must be blank"], t.errors[:title] assert_equal ["must be blank"], t.errors[:content] t.title = "" t.content = "something" assert t.invalid? assert_equal ["must be blank"], t.errors[:content] assert_equal [], t.errors[:title] t.content = "" assert t.valid? end def test_validates_absence_of_with_array_arguments Topic.validates_absence_of %w(title content) t = Topic.new t.title = "foo" t.content = "bar" assert t.invalid? assert_equal ["must be blank"], t.errors[:title] assert_equal ["must be blank"], t.errors[:content] end def test_validates_absence_of_with_custom_error_using_quotes Person.validates_absence_of :karma, message: "This string contains 'single' and \"double\" quotes" p = Person.new p.karma = "good" assert p.invalid? assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last end def test_validates_absence_of_for_ruby_class Person.validates_absence_of :karma p = Person.new p.karma = "good" assert p.invalid? assert_equal ["must be blank"], p.errors[:karma] p.karma = nil assert p.valid? end def test_validates_absence_of_for_ruby_class_with_custom_reader CustomReader.validates_absence_of :karma p = CustomReader.new p[:karma] = "excellent" assert p.invalid? assert_equal ["must be blank"], p.errors[:karma] p[:karma] = "" assert p.valid? end end rails-4.2.6/activemodel/test/cases/validations/acceptance_validation_test.rb000066400000000000000000000031751266740050600274230ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/reply' require 'models/person' class AcceptanceValidationTest < ActiveModel::TestCase def teardown Topic.clear_validators! end def test_terms_of_service_agreement_no_acceptance Topic.validates_acceptance_of(:terms_of_service) t = Topic.new("title" => "We should not be confirmed") assert t.valid? end def test_terms_of_service_agreement Topic.validates_acceptance_of(:terms_of_service) t = Topic.new("title" => "We should be confirmed","terms_of_service" => "") assert t.invalid? assert_equal ["must be accepted"], t.errors[:terms_of_service] t.terms_of_service = "1" assert t.valid? end def test_eula Topic.validates_acceptance_of(:eula, message: "must be abided") t = Topic.new("title" => "We should be confirmed","eula" => "") assert t.invalid? assert_equal ["must be abided"], t.errors[:eula] t.eula = "1" assert t.valid? end def test_terms_of_service_agreement_with_accept_value Topic.validates_acceptance_of(:terms_of_service, accept: "I agree.") t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "") assert t.invalid? assert_equal ["must be accepted"], t.errors[:terms_of_service] t.terms_of_service = "I agree." assert t.valid? end def test_validates_acceptance_of_for_ruby_class Person.validates_acceptance_of :karma p = Person.new p.karma = "" assert p.invalid? assert_equal ["must be accepted"], p.errors[:karma] p.karma = "1" assert p.valid? ensure Person.clear_validators! end end rails-4.2.6/activemodel/test/cases/validations/callbacks_test.rb000066400000000000000000000061301266740050600250340ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' class Dog include ActiveModel::Validations include ActiveModel::Validations::Callbacks attr_accessor :name, :history def initialize @history = [] end end class DogWithMethodCallbacks < Dog before_validation :set_before_validation_marker after_validation :set_after_validation_marker def set_before_validation_marker; self.history << 'before_validation_marker'; end def set_after_validation_marker; self.history << 'after_validation_marker' ; end end class DogValidatorsAreProc < Dog before_validation { self.history << 'before_validation_marker' } after_validation { self.history << 'after_validation_marker' } end class DogWithTwoValidators < Dog before_validation { self.history << 'before_validation_marker1' } before_validation { self.history << 'before_validation_marker2' } end class DogValidatorReturningFalse < Dog before_validation { false } before_validation { self.history << 'before_validation_marker2' } end class DogWithMissingName < Dog before_validation { self.history << 'before_validation_marker' } validates_presence_of :name end class DogValidatorWithIfCondition < Dog before_validation :set_before_validation_marker1, if: -> { true } before_validation :set_before_validation_marker2, if: -> { false } after_validation :set_after_validation_marker1, if: -> { true } after_validation :set_after_validation_marker2, if: -> { false } def set_before_validation_marker1; self.history << 'before_validation_marker1'; end def set_before_validation_marker2; self.history << 'before_validation_marker2' ; end def set_after_validation_marker1; self.history << 'after_validation_marker1'; end def set_after_validation_marker2; self.history << 'after_validation_marker2' ; end end class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase def test_if_condition_is_respected_for_before_validation d = DogValidatorWithIfCondition.new d.valid? assert_equal ["before_validation_marker1", "after_validation_marker1"], d.history end def test_before_validation_and_after_validation_callbacks_should_be_called d = DogWithMethodCallbacks.new d.valid? assert_equal ['before_validation_marker', 'after_validation_marker'], d.history end def test_before_validation_and_after_validation_callbacks_should_be_called_with_proc d = DogValidatorsAreProc.new d.valid? assert_equal ['before_validation_marker', 'after_validation_marker'], d.history end def test_before_validation_and_after_validation_callbacks_should_be_called_in_declared_order d = DogWithTwoValidators.new d.valid? assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history end def test_further_callbacks_should_not_be_called_if_before_validation_returns_false d = DogValidatorReturningFalse.new output = d.valid? assert_equal [], d.history assert_equal false, output end def test_validation_test_should_be_done d = DogWithMissingName.new output = d.valid? assert_equal ['before_validation_marker'], d.history assert_equal false, output end end rails-4.2.6/activemodel/test/cases/validations/conditional_validation_test.rb000066400000000000000000000122571266740050600276410ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' class ConditionalValidationTest < ActiveModel::TestCase def teardown Topic.clear_validators! end def test_if_validation_using_method_true # When the method returns true Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors["title"] end def test_unless_validation_using_method_true # When the method returns true Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] end def test_if_validation_using_method_false # When the method returns false Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true_but_its_not) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] end def test_unless_validation_using_method_false # When the method returns false Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true_but_its_not) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors["title"] end def test_if_validation_using_string_true # When the evaluated string returns true Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "a = 1; a == 1") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors["title"] end def test_unless_validation_using_string_true # When the evaluated string returns true Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] end def test_if_validation_using_string_false # When the evaluated string returns false Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] end def test_unless_validation_using_string_false # When the evaluated string returns false Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "false") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors["title"] end def test_if_validation_using_block_true # When the block returns true Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: Proc.new { |r| r.content.size > 4 }) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors["title"] end def test_unless_validation_using_block_true # When the block returns true Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: Proc.new { |r| r.content.size > 4 }) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] end def test_if_validation_using_block_false # When the block returns false Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: Proc.new { |r| r.title != "uhohuhoh"}) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] end def test_unless_validation_using_block_false # When the block returns false Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: Proc.new { |r| r.title != "uhohuhoh"} ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors["title"] end # previous implementation of validates_presence_of eval'd the # string with the wrong binding, this regression test is to # ensure that it works correctly def test_validation_with_if_as_string Topic.validates_presence_of(:title) Topic.validates_presence_of(:author_name, if: "title.to_s.match('important')") t = Topic.new assert t.invalid?, "A topic without a title should not be valid" assert_empty t.errors[:author_name], "A topic without an 'important' title should not require an author" t.title = "Just a title" assert t.valid?, "A topic with a basic title should be valid" t.title = "A very important title" assert t.invalid?, "A topic with an important title, but without an author, should not be valid" assert t.errors[:author_name].any?, "A topic with an 'important' title should require an author" t.author_name = "Hubert J. Farnsworth" assert t.valid?, "A topic with an important title and author should be valid" end end rails-4.2.6/activemodel/test/cases/validations/confirmation_validation_test.rb000066400000000000000000000053161266740050600300240ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/person' class ConfirmationValidationTest < ActiveModel::TestCase def teardown Topic.clear_validators! end def test_no_title_confirmation Topic.validates_confirmation_of(:title) t = Topic.new(author_name: "Plutarch") assert t.valid? t.title_confirmation = "Parallel Lives" assert t.invalid? t.title_confirmation = nil t.title = "Parallel Lives" assert t.valid? t.title_confirmation = "Parallel Lives" assert t.valid? end def test_title_confirmation Topic.validates_confirmation_of(:title) t = Topic.new("title" => "We should be confirmed","title_confirmation" => "") assert t.invalid? t.title_confirmation = "We should be confirmed" assert t.valid? end def test_validates_confirmation_of_for_ruby_class Person.validates_confirmation_of :karma p = Person.new p.karma_confirmation = "None" assert p.invalid? assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation] p.karma = "None" assert p.valid? ensure Person.clear_validators! end def test_title_confirmation_with_i18n_attribute begin @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new I18n.backend.store_translations('en', { errors: { messages: { confirmation: "doesn't match %{attribute}" } }, activemodel: { attributes: { topic: { title: 'Test Title'} } } }) Topic.validates_confirmation_of(:title) t = Topic.new("title" => "We should be confirmed","title_confirmation" => "") assert t.invalid? assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation] ensure I18n.load_path.replace @old_load_path I18n.backend = @old_backend I18n.backend.reload! end end test "does not override confirmation reader if present" do klass = Class.new do include ActiveModel::Validations def title_confirmation "expected title" end validates_confirmation_of :title end assert_equal "expected title", klass.new.title_confirmation, "confirmation validation should not override the reader" end test "does not override confirmation writer if present" do klass = Class.new do include ActiveModel::Validations def title_confirmation=(value) @title_confirmation = "expected title" end validates_confirmation_of :title end model = klass.new model.title_confirmation = "new title" assert_equal "expected title", model.title_confirmation, "confirmation validation should not override the writer" end end rails-4.2.6/activemodel/test/cases/validations/exclusion_validation_test.rb000066400000000000000000000042271266740050600273450ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/person' class ExclusionValidationTest < ActiveModel::TestCase def teardown Topic.clear_validators! end def test_validates_exclusion_of Topic.validates_exclusion_of(:title, in: %w( abe monkey )) assert Topic.new("title" => "something", "content" => "abc").valid? assert Topic.new("title" => "monkey", "content" => "abc").invalid? end def test_validates_exclusion_of_with_formatted_message Topic.validates_exclusion_of(:title, in: %w( abe monkey ), message: "option %{value} is restricted") assert Topic.new("title" => "something", "content" => "abc") t = Topic.new("title" => "monkey") assert t.invalid? assert t.errors[:title].any? assert_equal ["option monkey is restricted"], t.errors[:title] end def test_validates_exclusion_of_with_within_option Topic.validates_exclusion_of(:title, within: %w( abe monkey )) assert Topic.new("title" => "something", "content" => "abc") t = Topic.new("title" => "monkey") assert t.invalid? assert t.errors[:title].any? end def test_validates_exclusion_of_for_ruby_class Person.validates_exclusion_of :karma, in: %w( abe monkey ) p = Person.new p.karma = "abe" assert p.invalid? assert_equal ["is reserved"], p.errors[:karma] p.karma = "Lifo" assert p.valid? ensure Person.clear_validators! end def test_validates_exclusion_of_with_lambda Topic.validates_exclusion_of :title, in: lambda { |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } t = Topic.new t.title = "elephant" t.author_name = "sikachu" assert t.invalid? t.title = "wasabi" assert t.valid? end def test_validates_inclusion_of_with_symbol Person.validates_exclusion_of :karma, in: :reserved_karmas p = Person.new p.karma = "abe" def p.reserved_karmas %w(abe) end assert p.invalid? assert_equal ["is reserved"], p.errors[:karma] p = Person.new p.karma = "abe" def p.reserved_karmas %w() end assert p.valid? ensure Person.clear_validators! end end rails-4.2.6/activemodel/test/cases/validations/format_validation_test.rb000066400000000000000000000104201266740050600266140ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/person' class PresenceValidationTest < ActiveModel::TestCase def teardown Topic.clear_validators! end def test_validate_format Topic.validates_format_of(:title, :content, with: /\AValidation\smacros \w+!\z/, message: "is bad data") t = Topic.new("title" => "i'm incorrect", "content" => "Validation macros rule!") assert t.invalid?, "Shouldn't be valid" assert_equal ["is bad data"], t.errors[:title] assert t.errors[:content].empty? t.title = "Validation macros rule!" assert t.valid? assert t.errors[:title].empty? assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) } end def test_validate_format_with_allow_blank Topic.validates_format_of(:title, with: /\AValidation\smacros \w+!\z/, allow_blank: true) assert Topic.new("title" => "Shouldn't be valid").invalid? assert Topic.new("title" => "").valid? assert Topic.new("title" => nil).valid? assert Topic.new("title" => "Validation macros rule!").valid? end # testing ticket #3142 def test_validate_format_numeric Topic.validates_format_of(:title, :content, with: /\A[1-9][0-9]*\z/, message: "is bad data") t = Topic.new("title" => "72x", "content" => "6789") assert t.invalid?, "Shouldn't be valid" assert_equal ["is bad data"], t.errors[:title] assert t.errors[:content].empty? t.title = "-11" assert t.invalid?, "Shouldn't be valid" t.title = "03" assert t.invalid?, "Shouldn't be valid" t.title = "z44" assert t.invalid?, "Shouldn't be valid" t.title = "5v7" assert t.invalid?, "Shouldn't be valid" t.title = "1" assert t.valid? assert t.errors[:title].empty? end def test_validate_format_with_formatted_message Topic.validates_format_of(:title, with: /\AValid Title\z/, message: "can't be %{value}") t = Topic.new(title: 'Invalid title') assert t.invalid? assert_equal ["can't be Invalid title"], t.errors[:title] end def test_validate_format_of_with_multiline_regexp_should_raise_error assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: /^Valid Title$/) } end def test_validate_format_of_with_multiline_regexp_and_option assert_nothing_raised(ArgumentError) do Topic.validates_format_of(:title, with: /^Valid Title$/, multiline: true) end end def test_validate_format_with_not_option Topic.validates_format_of(:title, without: /foo/, message: "should not contain foo") t = Topic.new t.title = "foobar" t.valid? assert_equal ["should not contain foo"], t.errors[:title] t.title = "something else" t.valid? assert_equal [], t.errors[:title] end def test_validate_format_of_without_any_regexp_should_raise_error assert_raise(ArgumentError) { Topic.validates_format_of(:title) } end def test_validates_format_of_with_both_regexps_should_raise_error assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: /this/, without: /that/) } end def test_validates_format_of_when_with_isnt_a_regexp_should_raise_error assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: "clearly not a regexp") } end def test_validates_format_of_when_not_isnt_a_regexp_should_raise_error assert_raise(ArgumentError) { Topic.validates_format_of(:title, without: "clearly not a regexp") } end def test_validates_format_of_with_lambda Topic.validates_format_of :content, with: lambda { |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ } t = Topic.new t.title = "digit" t.content = "Pixies" assert t.invalid? t.content = "1234" assert t.valid? end def test_validates_format_of_without_lambda Topic.validates_format_of :content, without: lambda { |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ } t = Topic.new t.title = "characters" t.content = "1234" assert t.invalid? t.content = "Pixies" assert t.valid? end def test_validates_format_of_for_ruby_class Person.validates_format_of :karma, with: /\A\d+\Z/ p = Person.new p.karma = "Pixies" assert p.invalid? assert_equal ["is invalid"], p.errors[:karma] p.karma = "1234" assert p.valid? ensure Person.clear_validators! end end rails-4.2.6/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb000066400000000000000000000163401266740050600314700ustar00rootroot00000000000000require "cases/helper" require 'models/person' class I18nGenerateMessageValidationTest < ActiveModel::TestCase def setup Person.clear_validators! @person = Person.new end # validates_inclusion_of: generate_message(attr_name, :inclusion, message: custom_message, value: value) def test_generate_message_inclusion_with_default_message assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, value: 'title') end def test_generate_message_inclusion_with_custom_message assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, message: 'custom message %{value}', value: 'title') end # validates_exclusion_of: generate_message(attr_name, :exclusion, message: custom_message, value: value) def test_generate_message_exclusion_with_default_message assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, value: 'title') end def test_generate_message_exclusion_with_custom_message assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, message: 'custom message %{value}', value: 'title') end # validates_format_of: generate_message(attr_name, :invalid, message: custom_message, value: value) def test_generate_message_invalid_with_default_message assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, value: 'title') end def test_generate_message_invalid_with_custom_message assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, message: 'custom message %{value}', value: 'title') end # validates_confirmation_of: generate_message(attr_name, :confirmation, message: custom_message) def test_generate_message_confirmation_with_default_message assert_equal "doesn't match Title", @person.errors.generate_message(:title, :confirmation) end def test_generate_message_confirmation_with_custom_message assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, message: 'custom message') end # validates_acceptance_of: generate_message(attr_name, :accepted, message: custom_message) def test_generate_message_accepted_with_default_message assert_equal "must be accepted", @person.errors.generate_message(:title, :accepted) end def test_generate_message_accepted_with_custom_message assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, message: 'custom message') end # add_on_empty: generate_message(attr, :empty, message: custom_message) def test_generate_message_empty_with_default_message assert_equal "can't be empty", @person.errors.generate_message(:title, :empty) end def test_generate_message_empty_with_custom_message assert_equal 'custom message', @person.errors.generate_message(:title, :empty, message: 'custom message') end # add_on_blank: generate_message(attr, :blank, message: custom_message) def test_generate_message_blank_with_default_message assert_equal "can't be blank", @person.errors.generate_message(:title, :blank) end def test_generate_message_blank_with_custom_message assert_equal 'custom message', @person.errors.generate_message(:title, :blank, message: 'custom message') end # validates_length_of: generate_message(attr, :too_long, message: custom_message, count: option_value.end) def test_generate_message_too_long_with_default_message_plural assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, count: 10) end def test_generate_message_too_long_with_default_message_singular assert_equal "is too long (maximum is 1 character)", @person.errors.generate_message(:title, :too_long, count: 1) end def test_generate_message_too_long_with_custom_message assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, message: 'custom message %{count}', count: 10) end # validates_length_of: generate_message(attr, :too_short, default: custom_message, count: option_value.begin) def test_generate_message_too_short_with_default_message_plural assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, count: 10) end def test_generate_message_too_short_with_default_message_singular assert_equal "is too short (minimum is 1 character)", @person.errors.generate_message(:title, :too_short, count: 1) end def test_generate_message_too_short_with_custom_message assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, message: 'custom message %{count}', count: 10) end # validates_length_of: generate_message(attr, :wrong_length, message: custom_message, count: option_value) def test_generate_message_wrong_length_with_default_message_plural assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, count: 10) end def test_generate_message_wrong_length_with_default_message_singular assert_equal "is the wrong length (should be 1 character)", @person.errors.generate_message(:title, :wrong_length, count: 1) end def test_generate_message_wrong_length_with_custom_message assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, message: 'custom message %{count}', count: 10) end # validates_numericality_of: generate_message(attr_name, :not_a_number, value: raw_value, message: custom_message) def test_generate_message_not_a_number_with_default_message assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, value: 'title') end def test_generate_message_not_a_number_with_custom_message assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, message: 'custom message %{value}', value: 'title') end # validates_numericality_of: generate_message(attr_name, option, value: raw_value, default: custom_message) def test_generate_message_greater_than_with_default_message assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, value: 'title', count: 10) end def test_generate_message_greater_than_or_equal_to_with_default_message assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, value: 'title', count: 10) end def test_generate_message_equal_to_with_default_message assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, value: 'title', count: 10) end def test_generate_message_less_than_with_default_message assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, value: 'title', count: 10) end def test_generate_message_less_than_or_equal_to_with_default_message assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, value: 'title', count: 10) end def test_generate_message_odd_with_default_message assert_equal "must be odd", @person.errors.generate_message(:title, :odd, value: 'title', count: 10) end def test_generate_message_even_with_default_message assert_equal "must be even", @person.errors.generate_message(:title, :even, value: 'title', count: 10) end end rails-4.2.6/activemodel/test/cases/validations/i18n_validation_test.rb000066400000000000000000000357721266740050600261240ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "cases/helper" require 'models/person' class I18nValidationTest < ActiveModel::TestCase def setup Person.clear_validators! @person = Person.new @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new I18n.backend.store_translations('en', errors: { messages: { custom: nil } }) end def teardown Person.clear_validators! I18n.load_path.replace @old_load_path I18n.backend = @old_backend I18n.backend.reload! end def test_full_message_encoding I18n.backend.store_translations('en', errors: { messages: { too_short: '猫舌' } }) Person.validates_length_of :title, within: 3..5 @person.valid? assert_equal ['Title 猫舌'], @person.errors.full_messages end def test_errors_full_messages_translates_human_attribute_name_for_model_attributes @person.errors.add(:name, 'not found') Person.expects(:human_attribute_name).with(:name, default: 'Name').returns("Person's name") assert_equal ["Person's name not found"], @person.errors.full_messages end def test_errors_full_messages_uses_format I18n.backend.store_translations('en', errors: { format: "Field %{attribute} %{message}" }) @person.errors.add('name', 'empty') assert_equal ["Field Name empty"], @person.errors.full_messages end # ActiveModel::Validations # A set of common cases for ActiveModel::Validations message generation that # are used to generate tests to keep things DRY # COMMON_CASES = [ # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], [ "given custom message", { message: "custom" }, { message: "custom" }], [ "given if condition", { if: lambda { true }}, {}], [ "given unless condition", { unless: lambda { false }}, {}], [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }] ] # validates_confirmation_of w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_confirmation_of on generated message #{name}" do Person.validates_confirmation_of :title, validation_options @person.title_confirmation = 'foo' @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(attribute: 'Title')) @person.valid? end end # validates_acceptance_of w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_acceptance_of on generated message #{name}" do Person.validates_acceptance_of :title, validation_options.merge(allow_nil: false) @person.errors.expects(:generate_message).with(:title, :accepted, generate_message_options) @person.valid? end end # validates_presence_of w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_presence_of on generated message #{name}" do Person.validates_presence_of :title, validation_options @person.errors.expects(:generate_message).with(:title, :blank, generate_message_options) @person.valid? end end # validates_length_of :within too short w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_length_of for :withing on generated message when too short #{name}" do Person.validates_length_of :title, validation_options.merge(within: 3..5) @person.errors.expects(:generate_message).with(:title, :too_short, generate_message_options.merge(count: 3)) @person.valid? end end # validates_length_of :within too long w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_length_of for :too_long generated message #{name}" do Person.validates_length_of :title, validation_options.merge(within: 3..5) @person.title = 'this title is too long' @person.errors.expects(:generate_message).with(:title, :too_long, generate_message_options.merge(count: 5)) @person.valid? end end # validates_length_of :is w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_length_of for :is on generated message #{name}" do Person.validates_length_of :title, validation_options.merge(is: 5) @person.errors.expects(:generate_message).with(:title, :wrong_length, generate_message_options.merge(count: 5)) @person.valid? end end # validates_format_of w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_format_of on generated message #{name}" do Person.validates_format_of :title, validation_options.merge(with: /\A[1-9][0-9]*\z/) @person.title = '72x' @person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(value: '72x')) @person.valid? end end # validates_inclusion_of w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_inclusion_of on generated message #{name}" do Person.validates_inclusion_of :title, validation_options.merge(in: %w(a b c)) @person.title = 'z' @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(value: 'z')) @person.valid? end end # validates_inclusion_of using :within w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_inclusion_of using :within on generated message #{name}" do Person.validates_inclusion_of :title, validation_options.merge(within: %w(a b c)) @person.title = 'z' @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(value: 'z')) @person.valid? end end # validates_exclusion_of w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_exclusion_of generated message #{name}" do Person.validates_exclusion_of :title, validation_options.merge(in: %w(a b c)) @person.title = 'a' @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(value: 'a')) @person.valid? end end # validates_exclusion_of using :within w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_exclusion_of using :within generated message #{name}" do Person.validates_exclusion_of :title, validation_options.merge(within: %w(a b c)) @person.title = 'a' @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(value: 'a')) @person.valid? end end # validates_numericality_of without :only_integer w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of generated message #{name}" do Person.validates_numericality_of :title, validation_options @person.title = 'a' @person.errors.expects(:generate_message).with(:title, :not_a_number, generate_message_options.merge(value: 'a')) @person.valid? end end # validates_numericality_of with :only_integer w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of for :only_integer on generated message #{name}" do Person.validates_numericality_of :title, validation_options.merge(only_integer: true) @person.title = '0.0' @person.errors.expects(:generate_message).with(:title, :not_an_integer, generate_message_options.merge(value: '0.0')) @person.valid? end end # validates_numericality_of :odd w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of for :odd on generated message #{name}" do Person.validates_numericality_of :title, validation_options.merge(only_integer: true, odd: true) @person.title = 0 @person.errors.expects(:generate_message).with(:title, :odd, generate_message_options.merge(value: 0)) @person.valid? end end # validates_numericality_of :less_than w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of for :less_than on generated message #{name}" do Person.validates_numericality_of :title, validation_options.merge(only_integer: true, less_than: 0) @person.title = 1 @person.errors.expects(:generate_message).with(:title, :less_than, generate_message_options.merge(value: 1, count: 0)) @person.valid? end end # To make things DRY this macro is defined to define 3 tests for every validation case. def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation) if error_type == :confirmation attribute = :title_confirmation else attribute = :title end # test "validates_confirmation_of finds custom model key translation when blank" test "#{validation} finds custom model key translation when #{error_type}" do I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => 'custom message' } } } } } } I18n.backend.store_translations 'en', errors: { messages: { error_type => 'global message'}} yield(@person, {}) @person.valid? assert_equal ['custom message'], @person.errors[attribute] end # test "validates_confirmation_of finds custom model key translation with interpolation when blank" test "#{validation} finds custom model key translation with interpolation when #{error_type}" do I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => 'custom message with %{extra}' } } } } } } I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} } yield(@person, { extra: "extra information" }) @person.valid? assert_equal ['custom message with extra information'], @person.errors[attribute] end # test "validates_confirmation_of finds global default key translation when blank" test "#{validation} finds global default key translation when #{error_type}" do I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} } yield(@person, {}) @person.valid? assert_equal ['global message'], @person.errors[attribute] end end # validates_confirmation_of w/o mocha set_expectations_for_validation "validates_confirmation_of", :confirmation do |person, options_to_merge| Person.validates_confirmation_of :title, options_to_merge person.title_confirmation = 'foo' end # validates_acceptance_of w/o mocha set_expectations_for_validation "validates_acceptance_of", :accepted do |person, options_to_merge| Person.validates_acceptance_of :title, options_to_merge.merge(allow_nil: false) end # validates_presence_of w/o mocha set_expectations_for_validation "validates_presence_of", :blank do |person, options_to_merge| Person.validates_presence_of :title, options_to_merge end # validates_length_of :within w/o mocha set_expectations_for_validation "validates_length_of", :too_short do |person, options_to_merge| Person.validates_length_of :title, options_to_merge.merge(within: 3..5) end set_expectations_for_validation "validates_length_of", :too_long do |person, options_to_merge| Person.validates_length_of :title, options_to_merge.merge(within: 3..5) person.title = "too long" end # validates_length_of :is w/o mocha set_expectations_for_validation "validates_length_of", :wrong_length do |person, options_to_merge| Person.validates_length_of :title, options_to_merge.merge(is: 5) end # validates_format_of w/o mocha set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge| Person.validates_format_of :title, options_to_merge.merge(with: /\A[1-9][0-9]*\z/) end # validates_inclusion_of w/o mocha set_expectations_for_validation "validates_inclusion_of", :inclusion do |person, options_to_merge| Person.validates_inclusion_of :title, options_to_merge.merge(in: %w(a b c)) end # validates_exclusion_of w/o mocha set_expectations_for_validation "validates_exclusion_of", :exclusion do |person, options_to_merge| Person.validates_exclusion_of :title, options_to_merge.merge(in: %w(a b c)) person.title = 'a' end # validates_numericality_of without :only_integer w/o mocha set_expectations_for_validation "validates_numericality_of", :not_a_number do |person, options_to_merge| Person.validates_numericality_of :title, options_to_merge person.title = 'a' end # validates_numericality_of with :only_integer w/o mocha set_expectations_for_validation "validates_numericality_of", :not_an_integer do |person, options_to_merge| Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true) person.title = '1.0' end # validates_numericality_of :odd w/o mocha set_expectations_for_validation "validates_numericality_of", :odd do |person, options_to_merge| Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, odd: true) person.title = 0 end # validates_numericality_of :less_than w/o mocha set_expectations_for_validation "validates_numericality_of", :less_than do |person, options_to_merge| Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, less_than: 0) person.title = 1 end # test with validates_with def test_validations_with_message_symbol_must_translate I18n.backend.store_translations 'en', errors: { messages: { custom_error: "I am a custom error" } } Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? assert_equal ["I am a custom error"], @person.errors[:title] end def test_validates_with_message_symbol_must_translate_per_attribute I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { title: { custom_error: "I am a custom error" } } } } } } Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? assert_equal ["I am a custom error"], @person.errors[:title] end def test_validates_with_message_symbol_must_translate_per_model I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { custom_error: "I am a custom error" } } } } Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? assert_equal ["I am a custom error"], @person.errors[:title] end def test_validates_with_message_string Person.validates_presence_of :title, message: "I am a custom error" @person.title = nil @person.valid? assert_equal ["I am a custom error"], @person.errors[:title] end end rails-4.2.6/activemodel/test/cases/validations/inclusion_validation_test.rb000066400000000000000000000116761266740050600273450ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'active_support/all' require 'models/topic' require 'models/person' class InclusionValidationTest < ActiveModel::TestCase def teardown Topic.clear_validators! end def test_validates_inclusion_of_range Topic.validates_inclusion_of(:title, in: 'aaa'..'bbb') assert Topic.new("title" => "bbc", "content" => "abc").invalid? assert Topic.new("title" => "aa", "content" => "abc").invalid? assert Topic.new("title" => "aaab", "content" => "abc").invalid? assert Topic.new("title" => "aaa", "content" => "abc").valid? assert Topic.new("title" => "abc", "content" => "abc").valid? assert Topic.new("title" => "bbb", "content" => "abc").valid? end def test_validates_inclusion_of_time_range Topic.validates_inclusion_of(:created_at, in: 1.year.ago..Time.now) assert Topic.new(title: 'aaa', created_at: 2.years.ago).invalid? assert Topic.new(title: 'aaa', created_at: 3.months.ago).valid? assert Topic.new(title: 'aaa', created_at: 37.weeks.from_now).invalid? end def test_validates_inclusion_of_date_range Topic.validates_inclusion_of(:created_at, in: 1.year.until(Date.today)..Date.today) assert Topic.new(title: 'aaa', created_at: 2.years.until(Date.today)).invalid? assert Topic.new(title: 'aaa', created_at: 3.months.until(Date.today)).valid? assert Topic.new(title: 'aaa', created_at: 37.weeks.since(Date.today)).invalid? end def test_validates_inclusion_of_date_time_range Topic.validates_inclusion_of(:created_at, in: 1.year.until(DateTime.current)..DateTime.current) assert Topic.new(title: 'aaa', created_at: 2.years.until(DateTime.current)).invalid? assert Topic.new(title: 'aaa', created_at: 3.months.until(DateTime.current)).valid? assert Topic.new(title: 'aaa', created_at: 37.weeks.since(DateTime.current)).invalid? end def test_validates_inclusion_of Topic.validates_inclusion_of(:title, in: %w( a b c d e f g )) assert Topic.new("title" => "a!", "content" => "abc").invalid? assert Topic.new("title" => "a b", "content" => "abc").invalid? assert Topic.new("title" => nil, "content" => "def").invalid? t = Topic.new("title" => "a", "content" => "I know you are but what am I?") assert t.valid? t.title = "uhoh" assert t.invalid? assert t.errors[:title].any? assert_equal ["is not included in the list"], t.errors[:title] assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: nil) } assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: 0) } assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: "hi!") } assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: {}) } assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: []) } end def test_validates_inclusion_of_with_allow_nil Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ), allow_nil: true) assert Topic.new("title" => "a!", "content" => "abc").invalid? assert Topic.new("title" => "", "content" => "abc").invalid? assert Topic.new("title" => nil, "content" => "abc").valid? end def test_validates_inclusion_of_with_formatted_message Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ), message: "option %{value} is not in the list") assert Topic.new("title" => "a", "content" => "abc").valid? t = Topic.new("title" => "uhoh", "content" => "abc") assert t.invalid? assert t.errors[:title].any? assert_equal ["option uhoh is not in the list"], t.errors[:title] end def test_validates_inclusion_of_with_within_option Topic.validates_inclusion_of(:title, within: %w( a b c d e f g )) assert Topic.new("title" => "a", "content" => "abc").valid? t = Topic.new("title" => "uhoh", "content" => "abc") assert t.invalid? assert t.errors[:title].any? end def test_validates_inclusion_of_for_ruby_class Person.validates_inclusion_of :karma, in: %w( abe monkey ) p = Person.new p.karma = "Lifo" assert p.invalid? assert_equal ["is not included in the list"], p.errors[:karma] p.karma = "monkey" assert p.valid? ensure Person.clear_validators! end def test_validates_inclusion_of_with_lambda Topic.validates_inclusion_of :title, in: lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } t = Topic.new t.title = "wasabi" t.author_name = "sikachu" assert t.invalid? t.title = "elephant" assert t.valid? end def test_validates_inclusion_of_with_symbol Person.validates_inclusion_of :karma, in: :available_karmas p = Person.new p.karma = "Lifo" def p.available_karmas %w() end assert p.invalid? assert_equal ["is not included in the list"], p.errors[:karma] p = Person.new p.karma = "Lifo" def p.available_karmas %w(Lifo) end assert p.valid? ensure Person.clear_validators! end end rails-4.2.6/activemodel/test/cases/validations/length_validation_test.rb000066400000000000000000000314251266740050600266150ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/person' class LengthValidationTest < ActiveModel::TestCase def teardown Topic.clear_validators! end def test_validates_length_of_with_allow_nil Topic.validates_length_of( :title, is: 5, allow_nil: true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").invalid? assert Topic.new("title" => nil).valid? assert Topic.new("title" => "abcde").valid? end def test_validates_length_of_with_allow_blank Topic.validates_length_of( :title, is: 5, allow_blank: true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").valid? assert Topic.new("title" => nil).valid? assert Topic.new("title" => "abcde").valid? end def test_validates_length_of_using_minimum Topic.validates_length_of :title, minimum: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? t.title = "not" assert t.invalid? assert t.errors[:title].any? assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title] t.title = "" assert t.invalid? assert t.errors[:title].any? assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title] t.title = nil assert t.invalid? assert t.errors[:title].any? assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"] end def test_validates_length_of_using_maximum_should_allow_nil Topic.validates_length_of :title, maximum: 10 t = Topic.new assert t.valid? end def test_optionally_validates_length_of_using_minimum Topic.validates_length_of :title, minimum: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? t.title = nil assert t.valid? end def test_validates_length_of_using_maximum Topic.validates_length_of :title, maximum: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? t.title = "notvalid" assert t.invalid? assert t.errors[:title].any? assert_equal ["is too long (maximum is 5 characters)"], t.errors[:title] t.title = "" assert t.valid? end def test_optionally_validates_length_of_using_maximum Topic.validates_length_of :title, maximum: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? t.title = nil assert t.valid? end def test_validates_length_of_using_within Topic.validates_length_of(:title, :content, within: 3..5) t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long") assert t.invalid? assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title] assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content] t.title = nil t.content = nil assert t.invalid? assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title] assert_equal ["is too short (minimum is 3 characters)"], t.errors[:content] t.title = "abe" t.content = "mad" assert t.valid? end def test_validates_length_of_using_within_with_exclusive_range Topic.validates_length_of(:title, within: 4...10) t = Topic.new("title" => "9 chars!!") assert t.valid? t.title = "Now I'm 10" assert t.invalid? assert_equal ["is too long (maximum is 9 characters)"], t.errors[:title] t.title = "Four" assert t.valid? end def test_optionally_validates_length_of_using_within Topic.validates_length_of :title, :content, within: 3..5, allow_nil: true t = Topic.new('title' => 'abc', 'content' => 'abcd') assert t.valid? t.title = nil assert t.valid? end def test_validates_length_of_using_is Topic.validates_length_of :title, is: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? t.title = "notvalid" assert t.invalid? assert t.errors[:title].any? assert_equal ["is the wrong length (should be 5 characters)"], t.errors[:title] t.title = "" assert t.invalid? t.title = nil assert t.invalid? end def test_optionally_validates_length_of_using_is Topic.validates_length_of :title, is: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? t.title = nil assert t.valid? end def test_validates_length_of_using_bignum bigmin = 2 ** 30 bigmax = 2 ** 32 bigrange = bigmin...bigmax assert_nothing_raised do Topic.validates_length_of :title, is: bigmin + 5 Topic.validates_length_of :title, within: bigrange Topic.validates_length_of :title, in: bigrange Topic.validates_length_of :title, minimum: bigmin Topic.validates_length_of :title, maximum: bigmax end end def test_validates_length_of_nasty_params assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: -6) } assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: 6) } assert_raise(ArgumentError) { Topic.validates_length_of(:title, minimum: "a") } assert_raise(ArgumentError) { Topic.validates_length_of(:title, maximum: "a") } assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: "a") } assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: "a") } end def test_validates_length_of_custom_errors_for_minimum_with_message Topic.validates_length_of( :title, minimum: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["boo 5"], t.errors[:title] end def test_validates_length_of_custom_errors_for_minimum_with_too_short Topic.validates_length_of( :title, minimum: 5, too_short: "hoo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors[:title] end def test_validates_length_of_custom_errors_for_maximum_with_message Topic.validates_length_of( :title, maximum: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["boo 5"], t.errors[:title] end def test_validates_length_of_custom_errors_for_in Topic.validates_length_of(:title, in: 10..20, message: "hoo %{count}") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 10"], t.errors["title"] t = Topic.new("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 20"], t.errors["title"] end def test_validates_length_of_custom_errors_for_maximum_with_too_long Topic.validates_length_of( :title, maximum: 5, too_long: "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors["title"] end def test_validates_length_of_custom_errors_for_both_too_short_and_too_long Topic.validates_length_of :title, minimum: 3, maximum: 5, too_short: 'too short', too_long: 'too long' t = Topic.new(title: 'a') assert t.invalid? assert t.errors[:title].any? assert_equal ['too short'], t.errors['title'] t = Topic.new(title: 'aaaaaa') assert t.invalid? assert t.errors[:title].any? assert_equal ['too long'], t.errors['title'] end def test_validates_length_of_custom_errors_for_is_with_message Topic.validates_length_of( :title, is: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["boo 5"], t.errors["title"] end def test_validates_length_of_custom_errors_for_is_with_wrong_length Topic.validates_length_of( :title, is: 5, wrong_length: "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["hoo 5"], t.errors["title"] end def test_validates_length_of_using_minimum_utf8 Topic.validates_length_of :title, minimum: 5 t = Topic.new("title" => "一二三四五", "content" => "whatever") assert t.valid? t.title = "一二三四" assert t.invalid? assert t.errors[:title].any? assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"] end def test_validates_length_of_using_maximum_utf8 Topic.validates_length_of :title, maximum: 5 t = Topic.new("title" => "一二三四五", "content" => "whatever") assert t.valid? t.title = "一二34五六" assert t.invalid? assert t.errors[:title].any? assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"] end def test_validates_length_of_using_within_utf8 Topic.validates_length_of(:title, :content, within: 3..5) t = Topic.new("title" => "一二", "content" => "12三四五六七") assert t.invalid? assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title] assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content] t.title = "一二三" t.content = "12三" assert t.valid? end def test_optionally_validates_length_of_using_within_utf8 Topic.validates_length_of :title, within: 3..5, allow_nil: true t = Topic.new(title: "一二三四五") assert t.valid?, t.errors.inspect t = Topic.new(title: "一二三") assert t.valid?, t.errors.inspect t.title = nil assert t.valid?, t.errors.inspect end def test_validates_length_of_using_is_utf8 Topic.validates_length_of :title, is: 5 t = Topic.new("title" => "一二345", "content" => "whatever") assert t.valid? t.title = "一二345å…­" assert t.invalid? assert t.errors[:title].any? assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"] end def test_validates_length_of_with_block Topic.validates_length_of :content, minimum: 5, too_short: "Your essay must be at least %{count} words.", tokenizer: lambda {|str| str.scan(/\w+/) } t = Topic.new(content: "this content should be long enough") assert t.valid? t.content = "not long enough" assert t.invalid? assert t.errors[:content].any? assert_equal ["Your essay must be at least 5 words."], t.errors[:content] end def test_validates_length_of_for_fixnum Topic.validates_length_of(:approved, is: 4) t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1) assert t.invalid? assert t.errors[:approved].any? t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1234) assert t.valid? end def test_validates_length_of_for_ruby_class Person.validates_length_of :karma, minimum: 5 p = Person.new p.karma = "Pix" assert p.invalid? assert_equal ["is too short (minimum is 5 characters)"], p.errors[:karma] p.karma = "The Smiths" assert p.valid? ensure Person.clear_validators! end def test_validates_length_of_for_infinite_maxima Topic.validates_length_of(:title, within: 5..Float::INFINITY) t = Topic.new("title" => "1234") assert t.invalid? assert t.errors[:title].any? t.title = "12345" assert t.valid? Topic.validates_length_of(:author_name, maximum: Float::INFINITY) assert t.valid? t.author_name = "A very long author name that should still be valid." * 100 assert t.valid? end def test_validates_length_of_using_maximum_should_not_allow_nil_when_nil_not_allowed Topic.validates_length_of :title, maximum: 10, allow_nil: false t = Topic.new assert t.invalid? end def test_validates_length_of_using_maximum_should_not_allow_nil_and_empty_string_when_blank_not_allowed Topic.validates_length_of :title, maximum: 10, allow_blank: false t = Topic.new assert t.invalid? t.title = "" assert t.invalid? end def test_validates_length_of_using_both_minimum_and_maximum_should_not_allow_nil Topic.validates_length_of :title, minimum: 5, maximum: 10 t = Topic.new assert t.invalid? end def test_validates_length_of_using_minimum_0_should_not_allow_nil Topic.validates_length_of :title, minimum: 0 t = Topic.new assert t.invalid? t.title = "" assert t.valid? end def test_validates_length_of_using_is_0_should_not_allow_nil Topic.validates_length_of :title, is: 0 t = Topic.new assert t.invalid? t.title = "" assert t.valid? end def test_validates_with_diff_in_option Topic.validates_length_of(:title, is: 5) Topic.validates_length_of(:title, is: 5, if: Proc.new { false } ) assert Topic.new("title" => "david").valid? assert Topic.new("title" => "david2").invalid? end end rails-4.2.6/activemodel/test/cases/validations/numericality_validation_test.rb000066400000000000000000000152341266740050600300410ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/person' require 'bigdecimal' class NumericalityValidationTest < ActiveModel::TestCase def teardown Topic.clear_validators! end NIL = [nil] BLANK = ["", " ", " \t \r \n"] BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significant digits FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5) INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090) FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS INTEGERS = [0, 10, -10] + INTEGER_STRINGS BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) } JUNK = ["not a number", "42 not a number", "0xdeadbeef", "0xinvalidhex", "0Xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"] INFINITY = [1.0/0.0] def test_default_validates_numericality_of Topic.validates_numericality_of :approved invalid!(NIL + BLANK + JUNK) valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY) end def test_validates_numericality_of_with_nil_allowed Topic.validates_numericality_of :approved, allow_nil: true invalid!(JUNK + BLANK) valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY) end def test_validates_numericality_of_with_integer_only Topic.validates_numericality_of :approved, only_integer: true invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY) valid!(INTEGERS) end def test_validates_numericality_of_with_integer_only_and_nil_allowed Topic.validates_numericality_of :approved, only_integer: true, allow_nil: true invalid!(JUNK + BLANK + FLOATS + BIGDECIMAL + INFINITY) valid!(NIL + INTEGERS) end def test_validates_numericality_of_with_integer_only_and_symbol_as_value Topic.validates_numericality_of :approved, only_integer: :condition_is_true_but_its_not invalid!(NIL + BLANK + JUNK) valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY) end def test_validates_numericality_of_with_integer_only_and_proc_as_value Topic.send(:define_method, :allow_only_integers?, lambda { false }) Topic.validates_numericality_of :approved, only_integer: Proc.new {|topic| topic.allow_only_integers? } invalid!(NIL + BLANK + JUNK) valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY) end def test_validates_numericality_with_greater_than Topic.validates_numericality_of :approved, greater_than: 10 invalid!([-10, 10], 'must be greater than 10') valid!([11]) end def test_validates_numericality_with_greater_than_or_equal Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10 invalid!([-9, 9], 'must be greater than or equal to 10') valid!([10]) end def test_validates_numericality_with_equal_to Topic.validates_numericality_of :approved, equal_to: 10 invalid!([-10, 11] + INFINITY, 'must be equal to 10') valid!([10]) end def test_validates_numericality_with_less_than Topic.validates_numericality_of :approved, less_than: 10 invalid!([10], 'must be less than 10') valid!([-9, 9]) end def test_validates_numericality_with_less_than_or_equal_to Topic.validates_numericality_of :approved, less_than_or_equal_to: 10 invalid!([11], 'must be less than or equal to 10') valid!([-10, 10]) end def test_validates_numericality_with_odd Topic.validates_numericality_of :approved, odd: true invalid!([-2, 2], 'must be odd') valid!([-1, 1]) end def test_validates_numericality_with_even Topic.validates_numericality_of :approved, even: true invalid!([-1, 1], 'must be even') valid!([-2, 2]) end def test_validates_numericality_with_greater_than_less_than_and_even Topic.validates_numericality_of :approved, greater_than: 1, less_than: 4, even: true invalid!([1, 3, 4]) valid!([2]) end def test_validates_numericality_with_other_than Topic.validates_numericality_of :approved, other_than: 0 invalid!([0, 0.0]) valid!([-1, 42]) end def test_validates_numericality_with_proc Topic.send(:define_method, :min_approved, lambda { 5 }) Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new {|topic| topic.min_approved } invalid!([3, 4]) valid!([5, 6]) ensure Topic.send(:remove_method, :min_approved) end def test_validates_numericality_with_symbol Topic.send(:define_method, :max_approved, lambda { 5 }) Topic.validates_numericality_of :approved, less_than_or_equal_to: :max_approved invalid!([6]) valid!([4, 5]) ensure Topic.send(:remove_method, :max_approved) end def test_validates_numericality_with_numeric_message Topic.validates_numericality_of :approved, less_than: 4, message: "smaller than %{count}" topic = Topic.new("title" => "numeric test", "approved" => 10) assert !topic.valid? assert_equal ["smaller than 4"], topic.errors[:approved] Topic.validates_numericality_of :approved, greater_than: 4, message: "greater than %{count}" topic = Topic.new("title" => "numeric test", "approved" => 1) assert !topic.valid? assert_equal ["greater than 4"], topic.errors[:approved] end def test_validates_numericality_of_for_ruby_class Person.validates_numericality_of :karma, allow_nil: false p = Person.new p.karma = "Pix" assert p.invalid? assert_equal ["is not a number"], p.errors[:karma] p.karma = "1234" assert p.valid? ensure Person.clear_validators! end def test_validates_numericality_with_invalid_args assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" } assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" } assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than: "foo" } assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than: "foo" } assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, equal_to: "foo" } end private def invalid!(values, error = nil) with_each_topic_approved_value(values) do |topic, value| assert topic.invalid?, "#{value.inspect} not rejected as a number" assert topic.errors[:approved].any?, "FAILED for #{value.inspect}" assert_equal error, topic.errors[:approved].first if error end end def valid!(values) with_each_topic_approved_value(values) do |topic, value| assert topic.valid?, "#{value.inspect} not accepted as a number" end end def with_each_topic_approved_value(values) topic = Topic.new(title: "numeric test", content: "whatever") values.each do |value| topic.approved = value yield topic, value end end end rails-4.2.6/activemodel/test/cases/validations/presence_validation_test.rb000066400000000000000000000051161266740050600271360ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/person' require 'models/custom_reader' class PresenceValidationTest < ActiveModel::TestCase teardown do Topic.clear_validators! Person.clear_validators! CustomReader.clear_validators! end def test_validate_presences Topic.validates_presence_of(:title, :content) t = Topic.new assert t.invalid? assert_equal ["can't be blank"], t.errors[:title] assert_equal ["can't be blank"], t.errors[:content] t.title = "something" t.content = " " assert t.invalid? assert_equal ["can't be blank"], t.errors[:content] t.content = "like stuff" assert t.valid? end def test_accepts_array_arguments Topic.validates_presence_of %w(title content) t = Topic.new assert t.invalid? assert_equal ["can't be blank"], t.errors[:title] assert_equal ["can't be blank"], t.errors[:content] end def test_validates_acceptance_of_with_custom_error_using_quotes Person.validates_presence_of :karma, message: "This string contains 'single' and \"double\" quotes" p = Person.new assert p.invalid? assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last end def test_validates_presence_of_for_ruby_class Person.validates_presence_of :karma p = Person.new assert p.invalid? assert_equal ["can't be blank"], p.errors[:karma] p.karma = "Cold" assert p.valid? end def test_validates_presence_of_for_ruby_class_with_custom_reader CustomReader.validates_presence_of :karma p = CustomReader.new assert p.invalid? assert_equal ["can't be blank"], p.errors[:karma] p[:karma] = "Cold" assert p.valid? end def test_validates_presence_of_with_allow_nil_option Topic.validates_presence_of(:title, allow_nil: true) t = Topic.new(title: "something") assert t.valid?, t.errors.full_messages t.title = "" assert t.invalid? assert_equal ["can't be blank"], t.errors[:title] t.title = " " assert t.invalid?, t.errors.full_messages assert_equal ["can't be blank"], t.errors[:title] t.title = nil assert t.valid?, t.errors.full_messages end def test_validates_presence_of_with_allow_blank_option Topic.validates_presence_of(:title, allow_blank: true) t = Topic.new(title: "something") assert t.valid?, t.errors.full_messages t.title = "" assert t.valid?, t.errors.full_messages t.title = " " assert t.valid?, t.errors.full_messages t.title = nil assert t.valid?, t.errors.full_messages end end rails-4.2.6/activemodel/test/cases/validations/validates_test.rb000066400000000000000000000115361266740050600250770ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/person' require 'models/topic' require 'models/person_with_validator' require 'validators/namespace/email_validator' class ValidatesTest < ActiveModel::TestCase setup :reset_callbacks teardown :reset_callbacks def reset_callbacks Person.clear_validators! Topic.clear_validators! PersonWithValidator.clear_validators! end def test_validates_with_messages_empty Person.validates :title, presence: { message: "" } person = Person.new assert !person.valid?, 'person should not be valid.' end def test_validates_with_built_in_validation Person.validates :title, numericality: true person = Person.new person.valid? assert_equal ['is not a number'], person.errors[:title] end def test_validates_with_attribute_specified_as_string Person.validates "title", numericality: true person = Person.new person.valid? assert_equal ['is not a number'], person.errors[:title] person = Person.new person.title = 123 assert person.valid? end def test_validates_with_built_in_validation_and_options Person.validates :salary, numericality: { message: 'my custom message' } person = Person.new person.valid? assert_equal ['my custom message'], person.errors[:salary] end def test_validates_with_validator_class Person.validates :karma, email: true person = Person.new person.valid? assert_equal ['is not an email'], person.errors[:karma] end def test_validates_with_namespaced_validator_class Person.validates :karma, :'namespace/email' => true person = Person.new person.valid? assert_equal ['is not an email'], person.errors[:karma] end def test_validates_with_if_as_local_conditions Person.validates :karma, presence: true, email: { unless: :condition_is_true } person = Person.new person.valid? assert_equal ["can't be blank"], person.errors[:karma] end def test_validates_with_if_as_shared_conditions Person.validates :karma, presence: true, email: true, if: :condition_is_true person = Person.new person.valid? assert_equal ["can't be blank", "is not an email"], person.errors[:karma].sort end def test_validates_with_unless_shared_conditions Person.validates :karma, presence: true, email: true, unless: :condition_is_true person = Person.new assert person.valid? end def test_validates_with_allow_nil_shared_conditions Person.validates :karma, length: { minimum: 20 }, email: true, allow_nil: true person = Person.new assert person.valid? end def test_validates_with_regexp Person.validates :karma, format: /positive|negative/ person = Person.new assert person.invalid? assert_equal ['is invalid'], person.errors[:karma] person.karma = "positive" assert person.valid? end def test_validates_with_array Person.validates :gender, inclusion: %w(m f) person = Person.new assert person.invalid? assert_equal ['is not included in the list'], person.errors[:gender] person.gender = "m" assert person.valid? end def test_validates_with_range Person.validates :karma, length: 6..20 person = Person.new assert person.invalid? assert_equal ['is too short (minimum is 6 characters)'], person.errors[:karma] person.karma = 'something' assert person.valid? end def test_validates_with_validator_class_and_options Person.validates :karma, email: { message: 'my custom message' } person = Person.new person.valid? assert_equal ['my custom message'], person.errors[:karma] end def test_validates_with_unknown_validator assert_raise(ArgumentError) { Person.validates :karma, unknown: true } end def test_validates_with_included_validator PersonWithValidator.validates :title, presence: true person = PersonWithValidator.new person.valid? assert_equal ['Local validator'], person.errors[:title] end def test_validates_with_included_validator_and_options PersonWithValidator.validates :title, presence: { custom: ' please' } person = PersonWithValidator.new person.valid? assert_equal ['Local validator please'], person.errors[:title] end def test_validates_with_included_validator_and_wildcard_shortcut # Shortcut for PersonWithValidator.validates :title, like: { with: "Mr." } PersonWithValidator.validates :title, like: "Mr." person = PersonWithValidator.new person.title = "Ms. Pacman" person.valid? assert_equal ['does not appear to be like Mr.'], person.errors[:title] end def test_defining_extra_default_keys_for_validates Topic.validates :title, confirmation: true, message: 'Y U NO CONFIRM' topic = Topic.new topic.title = "What's happening" topic.title_confirmation = "Not this" assert !topic.valid? assert_equal ['Y U NO CONFIRM'], topic.errors[:title_confirmation] end end rails-4.2.6/activemodel/test/cases/validations/validations_context_test.rb000066400000000000000000000035221266740050600272000ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' class ValidationsContextTest < ActiveModel::TestCase def teardown Topic.clear_validators! end ERROR_MESSAGE = "Validation error from validator" class ValidatorThatAddsErrors < ActiveModel::Validator def validate(record) record.errors[:base] << ERROR_MESSAGE end end test "with a class that adds errors on create and validating a new model with no arguments" do Topic.validates_with(ValidatorThatAddsErrors, on: :create) topic = Topic.new assert topic.valid?, "Validation doesn't run on valid? if 'on' is set to create" end test "with a class that adds errors on update and validating a new model" do Topic.validates_with(ValidatorThatAddsErrors, on: :update) topic = Topic.new assert topic.valid?(:create), "Validation doesn't run on create if 'on' is set to update" end test "with a class that adds errors on create and validating a new model" do Topic.validates_with(ValidatorThatAddsErrors, on: :create) topic = Topic.new assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create" assert topic.errors[:base].include?(ERROR_MESSAGE) end test "with a class that adds errors on multiple contexts and validating a new model" do Topic.validates_with(ValidatorThatAddsErrors, on: [:context1, :context2]) topic = Topic.new assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2" assert topic.invalid?(:context1), "Validation did not run on context1 when 'on' is set to context1 and context2" assert topic.errors[:base].include?(ERROR_MESSAGE) assert topic.invalid?(:context2), "Validation did not run on context2 when 'on' is set to context1 and context2" assert topic.errors[:base].include?(ERROR_MESSAGE) end end rails-4.2.6/activemodel/test/cases/validations/with_validation_test.rb000066400000000000000000000122611266740050600263040ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' class ValidatesWithTest < ActiveModel::TestCase def teardown Topic.clear_validators! end ERROR_MESSAGE = "Validation error from validator" OTHER_ERROR_MESSAGE = "Validation error from other validator" class ValidatorThatAddsErrors < ActiveModel::Validator def validate(record) record.errors[:base] << ERROR_MESSAGE end end class OtherValidatorThatAddsErrors < ActiveModel::Validator def validate(record) record.errors[:base] << OTHER_ERROR_MESSAGE end end class ValidatorThatDoesNotAddErrors < ActiveModel::Validator def validate(record) end end class ValidatorThatValidatesOptions < ActiveModel::Validator def validate(record) if options[:field] == :first_name record.errors[:base] << ERROR_MESSAGE end end end class ValidatorPerEachAttribute < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << "Value is #{value}" end end class ValidatorCheckValidity < ActiveModel::EachValidator def check_validity! raise "boom!" end end test "validation with class that adds errors" do Topic.validates_with(ValidatorThatAddsErrors) topic = Topic.new assert topic.invalid?, "A class that adds errors causes the record to be invalid" assert topic.errors[:base].include?(ERROR_MESSAGE) end test "with a class that returns valid" do Topic.validates_with(ValidatorThatDoesNotAddErrors) topic = Topic.new assert topic.valid?, "A class that does not add errors does not cause the record to be invalid" end test "with multiple classes" do Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors) topic = Topic.new assert topic.invalid? assert topic.errors[:base].include?(ERROR_MESSAGE) assert topic.errors[:base].include?(OTHER_ERROR_MESSAGE) end test "with if statements that return false" do Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 2") topic = Topic.new assert topic.valid? end test "with if statements that return true" do Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1") topic = Topic.new assert topic.invalid? assert topic.errors[:base].include?(ERROR_MESSAGE) end test "with unless statements that return true" do Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 1") topic = Topic.new assert topic.valid? end test "with unless statements that returns false" do Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2") topic = Topic.new assert topic.invalid? assert topic.errors[:base].include?(ERROR_MESSAGE) end test "passes all configuration options to the validator class" do topic = Topic.new validator = mock() validator.expects(:new).with(foo: :bar, if: "1 == 1", class: Topic).returns(validator) validator.expects(:validate).with(topic) Topic.validates_with(validator, if: "1 == 1", foo: :bar) assert topic.valid? end test "validates_with with options" do Topic.validates_with(ValidatorThatValidatesOptions, field: :first_name) topic = Topic.new assert topic.invalid? assert topic.errors[:base].include?(ERROR_MESSAGE) end test "validates_with each validator" do Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content]) topic = Topic.new title: "Title", content: "Content" assert topic.invalid? assert_equal ["Value is Title"], topic.errors[:title] assert_equal ["Value is Content"], topic.errors[:content] end test "each validator checks validity" do assert_raise RuntimeError do Topic.validates_with(ValidatorCheckValidity, attributes: [:title]) end end test "each validator expects attributes to be given" do assert_raise ArgumentError do Topic.validates_with(ValidatorPerEachAttribute) end end test "each validator skip nil values if :allow_nil is set to true" do Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content], allow_nil: true) topic = Topic.new content: "" assert topic.invalid? assert topic.errors[:title].empty? assert_equal ["Value is "], topic.errors[:content] end test "each validator skip blank values if :allow_blank is set to true" do Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content], allow_blank: true) topic = Topic.new content: "" assert topic.valid? assert topic.errors[:title].empty? assert topic.errors[:content].empty? end test "validates_with can validate with an instance method" do Topic.validates :title, with: :my_validation topic = Topic.new title: "foo" assert topic.valid? assert topic.errors[:title].empty? topic = Topic.new assert !topic.valid? assert_equal ['is missing'], topic.errors[:title] end test "optionally pass in the attribute being validated when validating with an instance method" do Topic.validates :title, :content, with: :my_validation_with_arg topic = Topic.new title: "foo" assert !topic.valid? assert topic.errors[:title].empty? assert_equal ['is missing'], topic.errors[:content] end end rails-4.2.6/activemodel/test/cases/validations_test.rb000066400000000000000000000303471266740050600231240ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/topic' require 'models/reply' require 'models/custom_reader' require 'active_support/json' require 'active_support/xml_mini' class ValidationsTest < ActiveModel::TestCase class CustomStrictValidationException < StandardError; end def teardown Topic.clear_validators! end def test_single_field_validation r = Reply.new r.title = "There's no content!" assert r.invalid?, "A reply without content should be invalid" assert r.after_validation_performed, "after_validation callback should be called" r.content = "Messa content!" assert r.valid?, "A reply with content should be valid" assert r.after_validation_performed, "after_validation callback should be called" end def test_single_attr_validation_and_error_msg r = Reply.new r.title = "There's no content!" assert r.invalid? assert r.errors[:content].any?, "A reply without content should mark that attribute as invalid" assert_equal ["is Empty"], r.errors["content"], "A reply without content should contain an error" assert_equal 1, r.errors.count end def test_double_attr_validation_and_error_msg r = Reply.new assert r.invalid? assert r.errors[:title].any?, "A reply without title should mark that attribute as invalid" assert_equal ["is Empty"], r.errors["title"], "A reply without title should contain an error" assert r.errors[:content].any?, "A reply without content should mark that attribute as invalid" assert_equal ["is Empty"], r.errors["content"], "A reply without content should contain an error" assert_equal 2, r.errors.count end def test_single_error_per_attr_iteration r = Reply.new r.valid? errors = r.errors.collect {|attr, messages| [attr.to_s, messages]} assert errors.include?(["title", "is Empty"]) assert errors.include?(["content", "is Empty"]) end def test_multiple_errors_per_attr_iteration_with_full_error_composition r = Reply.new r.title = "" r.content = "" r.valid? errors = r.errors.to_a assert_equal "Content is Empty", errors[0] assert_equal "Title is Empty", errors[1] assert_equal 2, r.errors.count end def test_errors_on_nested_attributes_expands_name t = Topic.new t.errors["replies.name"] << "can't be blank" assert_equal ["Replies name can't be blank"], t.errors.full_messages end def test_errors_on_base r = Reply.new r.content = "Mismatch" r.valid? r.errors.add(:base, "Reply is not dignifying") errors = r.errors.to_a.inject([]) { |result, error| result + [error] } assert_equal ["Reply is not dignifying"], r.errors[:base] assert errors.include?("Title is Empty") assert errors.include?("Reply is not dignifying") assert_equal 2, r.errors.count end def test_errors_on_base_with_symbol_message r = Reply.new r.content = "Mismatch" r.valid? r.errors.add(:base, :invalid) errors = r.errors.to_a.inject([]) { |result, error| result + [error] } assert_equal ["is invalid"], r.errors[:base] assert errors.include?("Title is Empty") assert errors.include?("is invalid") assert_equal 2, r.errors.count end def test_errors_empty_after_errors_on_check t = Topic.new assert t.errors[:id].empty? assert t.errors.empty? end def test_validates_each hits = 0 Topic.validates_each(:title, :content, [:title, :content]) do |record, attr| record.errors.add attr, 'gotcha' hits += 1 end t = Topic.new("title" => "valid", "content" => "whatever") assert t.invalid? assert_equal 4, hits assert_equal %w(gotcha gotcha), t.errors[:title] assert_equal %w(gotcha gotcha), t.errors[:content] end def test_validates_each_custom_reader hits = 0 CustomReader.validates_each(:title, :content, [:title, :content]) do |record, attr| record.errors.add attr, 'gotcha' hits += 1 end t = CustomReader.new("title" => "valid", "content" => "whatever") assert t.invalid? assert_equal 4, hits assert_equal %w(gotcha gotcha), t.errors[:title] assert_equal %w(gotcha gotcha), t.errors[:content] ensure CustomReader.clear_validators! end def test_validate_block Topic.validate { errors.add("title", "will never be valid") } t = Topic.new("title" => "Title", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["will never be valid"], t.errors["title"] end def test_validate_block_with_params Topic.validate { |topic| topic.errors.add("title", "will never be valid") } t = Topic.new("title" => "Title", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? assert_equal ["will never be valid"], t.errors["title"] end def test_invalid_validator Topic.validate :i_dont_exist assert_raises(NoMethodError) do t = Topic.new t.valid? end end def test_invalid_options_to_validate error = assert_raises(ArgumentError) do # A common mistake -- we meant to call 'validates' Topic.validate :title, presence: true end message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless, :prepend. Perhaps you meant to call `validates` instead of `validate`?' assert_equal message, error.message end def test_callback_options_to_validate klass = Class.new(Topic) do attr_reader :call_sequence def initialize(*) super @call_sequence = [] end private def validator_a @call_sequence << :a end def validator_b @call_sequence << :b end def validator_c @call_sequence << :c end end assert_nothing_raised do klass.validate :validator_a, if: ->{ true } klass.validate :validator_b, prepend: true klass.validate :validator_c, unless: ->{ true } end t = klass.new assert_predicate t, :valid? assert_equal [:b, :a], t.call_sequence end def test_errors_conversions Topic.validates_presence_of %w(title content) t = Topic.new assert t.invalid? xml = t.errors.to_xml assert_match %r{}, xml assert_match %r{Title can't be blank}, xml assert_match %r{Content can't be blank}, xml hash = {} hash[:title] = ["can't be blank"] hash[:content] = ["can't be blank"] assert_equal t.errors.to_json, hash.to_json end def test_validation_order Topic.validates_presence_of :title Topic.validates_length_of :title, minimum: 2 t = Topic.new("title" => "") assert t.invalid? assert_equal "can't be blank", t.errors["title"].first Topic.validates_presence_of :title, :author_name Topic.validate {errors.add('author_email_address', 'will never be valid')} Topic.validates_length_of :title, :content, minimum: 2 t = Topic.new title: '' assert t.invalid? assert_equal :title, key = t.errors.keys[0] assert_equal "can't be blank", t.errors[key][0] assert_equal 'is too short (minimum is 2 characters)', t.errors[key][1] assert_equal :author_name, key = t.errors.keys[1] assert_equal "can't be blank", t.errors[key][0] assert_equal :author_email_address, key = t.errors.keys[2] assert_equal 'will never be valid', t.errors[key][0] assert_equal :content, key = t.errors.keys[3] assert_equal 'is too short (minimum is 2 characters)', t.errors[key][0] end def test_validation_with_if_and_on Topic.validates_presence_of :title, if: Proc.new{|x| x.author_name = "bad"; true }, on: :update t = Topic.new(title: "") # If block should not fire assert t.valid? assert t.author_name.nil? # If block should fire assert t.invalid?(:update) assert t.author_name == "bad" end def test_invalid_should_be_the_opposite_of_valid Topic.validates_presence_of :title t = Topic.new assert t.invalid? assert t.errors[:title].any? t.title = 'Things are going to change' assert !t.invalid? end def test_validation_with_message_as_proc Topic.validates_presence_of(:title, message: proc { "no blanks here".upcase }) t = Topic.new assert t.invalid? assert_equal ["NO BLANKS HERE"], t.errors[:title] end def test_list_of_validators_for_model Topic.validates_presence_of :title Topic.validates_length_of :title, minimum: 2 assert_equal 2, Topic.validators.count assert_equal [:presence, :length], Topic.validators.map(&:kind) end def test_list_of_validators_on_an_attribute Topic.validates_presence_of :title, :content Topic.validates_length_of :title, minimum: 2 assert_equal 2, Topic.validators_on(:title).count assert_equal [:presence, :length], Topic.validators_on(:title).map(&:kind) assert_equal 1, Topic.validators_on(:content).count assert_equal [:presence], Topic.validators_on(:content).map(&:kind) end def test_accessing_instance_of_validator_on_an_attribute Topic.validates_length_of :title, minimum: 10 assert_equal 10, Topic.validators_on(:title).first.options[:minimum] end def test_list_of_validators_on_multiple_attributes Topic.validates :title, length: { minimum: 10 } Topic.validates :author_name, presence: true, format: /a/ validators = Topic.validators_on(:title, :author_name) assert_equal [ ActiveModel::Validations::FormatValidator, ActiveModel::Validations::LengthValidator, ActiveModel::Validations::PresenceValidator ], validators.map { |v| v.class }.sort_by { |c| c.to_s } end def test_list_of_validators_will_be_empty_when_empty Topic.validates :title, length: { minimum: 10 } assert_equal [], Topic.validators_on(:author_name) end def test_validations_on_the_instance_level Topic.validates :title, :author_name, presence: true Topic.validates :content, length: { minimum: 10 } topic = Topic.new assert topic.invalid? assert_equal 3, topic.errors.size topic.title = 'Some Title' topic.author_name = 'Some Author' topic.content = 'Some Content Whose Length is more than 10.' assert topic.valid? end def test_validate Topic.validate do validates_presence_of :title, :author_name validates_length_of :content, minimum: 10 end topic = Topic.new assert_empty topic.errors topic.validate assert_not_empty topic.errors end def test_strict_validation_in_validates Topic.validates :title, strict: true, presence: true assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end end def test_strict_validation_not_fails Topic.validates :title, strict: true, presence: true assert Topic.new(title: "hello").valid? end def test_strict_validation_particular_validator Topic.validates :title, presence: { strict: true } assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end end def test_strict_validation_in_custom_validator_helper Topic.validates_presence_of :title, strict: true assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end end def test_strict_validation_custom_exception Topic.validates_presence_of :title, strict: CustomStrictValidationException assert_raises CustomStrictValidationException do Topic.new.valid? end end def test_validates_with_bang Topic.validates! :title, presence: true assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end end def test_validates_with_false_hash_value Topic.validates :title, presence: false assert Topic.new.valid? end def test_strict_validation_error_message Topic.validates :title, strict: true, presence: true exception = assert_raises(ActiveModel::StrictValidationFailed) do Topic.new.valid? end assert_equal "Title can't be blank", exception.message end def test_does_not_modify_options_argument options = { presence: true } Topic.validates :title, options assert_equal({ presence: true }, options) end def test_dup_validity_is_independent Topic.validates_presence_of :title topic = Topic.new("title" => "Literature") topic.valid? duped = topic.dup duped.title = nil assert duped.invalid? topic.title = nil duped.title = 'Mathematics' assert topic.invalid? assert duped.valid? end end rails-4.2.6/activemodel/test/config.rb000066400000000000000000000002201266740050600177020ustar00rootroot00000000000000TEST_ROOT = File.expand_path(File.dirname(__FILE__)) FIXTURES_ROOT = TEST_ROOT + "/fixtures" SCHEMA_FILE = TEST_ROOT + "/schema.rb" rails-4.2.6/activemodel/test/models/000077500000000000000000000000001266740050600174015ustar00rootroot00000000000000rails-4.2.6/activemodel/test/models/account.rb000066400000000000000000000001571266740050600213650ustar00rootroot00000000000000class Account include ActiveModel::ForbiddenAttributesProtection public :sanitize_for_mass_assignment end rails-4.2.6/activemodel/test/models/blog_post.rb000066400000000000000000000001701266740050600217140ustar00rootroot00000000000000module Blog def self.use_relative_model_naming? true end class Post extend ActiveModel::Naming end end rails-4.2.6/activemodel/test/models/contact.rb000066400000000000000000000013521266740050600213620ustar00rootroot00000000000000class Contact extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations include ActiveModel::Serializers::JSON include ActiveModel::Serializers::Xml attr_accessor :id, :name, :age, :created_at, :awesome, :preferences attr_accessor :address, :friends, :contact def social %w(twitter github) end def network { git: :github } end def initialize(options = {}) options.each { |name, value| send("#{name}=", value) } end def pseudonyms nil end def persisted? id end def attributes=(hash) hash.each do |k, v| instance_variable_set("@#{k}", v) end end def attributes instance_values.except("address", "friends", "contact") end end rails-4.2.6/activemodel/test/models/custom_reader.rb000066400000000000000000000003401266740050600225570ustar00rootroot00000000000000class CustomReader include ActiveModel::Validations def initialize(data = {}) @data = data end def []=(key, value) @data[key] = value end def read_attribute_for_validation(key) @data[key] end endrails-4.2.6/activemodel/test/models/helicopter.rb000066400000000000000000000001711266740050600220630ustar00rootroot00000000000000class Helicopter include ActiveModel::Conversion end class Helicopter::Comanche include ActiveModel::Conversion end rails-4.2.6/activemodel/test/models/person.rb000066400000000000000000000004071266740050600212350ustar00rootroot00000000000000class Person include ActiveModel::Validations extend ActiveModel::Translation attr_accessor :title, :karma, :salary, :gender def condition_is_true true end end class Person::Gender extend ActiveModel::Translation end class Child < Person end rails-4.2.6/activemodel/test/models/person_with_validator.rb000066400000000000000000000011351266740050600243340ustar00rootroot00000000000000class PersonWithValidator include ActiveModel::Validations class PresenceValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << "Local validator#{options[:custom]}" if value.blank? end end class LikeValidator < ActiveModel::EachValidator def initialize(options) @with = options[:with] super end def validate_each(record, attribute, value) unless value[@with] record.errors.add attribute, "does not appear to be like #{@with}" end end end attr_accessor :title, :karma end rails-4.2.6/activemodel/test/models/project.rb000066400000000000000000000001121266740050600213660ustar00rootroot00000000000000class Project include ActiveModel::DeprecatedMassAssignmentSecurity end rails-4.2.6/activemodel/test/models/reply.rb000066400000000000000000000014631266740050600210650ustar00rootroot00000000000000require 'models/topic' class Reply < Topic validate :errors_on_empty_content validate :title_is_wrong_create, on: :create validate :check_empty_title validate :check_content_mismatch, on: :create validate :check_wrong_update, on: :update def check_empty_title errors[:title] << "is Empty" unless title && title.size > 0 end def errors_on_empty_content errors[:content] << "is Empty" unless content && content.size > 0 end def check_content_mismatch if title && content && content == "Mismatch" errors[:title] << "is Content Mismatch" end end def title_is_wrong_create errors[:title] << "is Wrong Create" if title && title == "Wrong Create" end def check_wrong_update errors[:title] << "is Wrong Update" if title && title == "Wrong Update" end end rails-4.2.6/activemodel/test/models/sheep.rb000066400000000000000000000000551266740050600210320ustar00rootroot00000000000000class Sheep extend ActiveModel::Naming end rails-4.2.6/activemodel/test/models/topic.rb000066400000000000000000000014201266740050600210410ustar00rootroot00000000000000class Topic include ActiveModel::Validations include ActiveModel::Validations::Callbacks def self._validates_default_keys super | [ :message ] end attr_accessor :title, :author_name, :content, :approved, :created_at attr_accessor :after_validation_performed after_validation :perform_after_validation def initialize(attributes = {}) attributes.each do |key, value| send "#{key}=", value end end def condition_is_true true end def condition_is_true_but_its_not false end def perform_after_validation self.after_validation_performed = true end def my_validation errors.add :title, "is missing" unless title end def my_validation_with_arg(attr) errors.add attr, "is missing" unless send(attr) end end rails-4.2.6/activemodel/test/models/track_back.rb000066400000000000000000000002251266740050600220110ustar00rootroot00000000000000class Post class TrackBack def to_model NamedTrackBack.new end end class NamedTrackBack extend ActiveModel::Naming end endrails-4.2.6/activemodel/test/models/user.rb000066400000000000000000000002621266740050600207040ustar00rootroot00000000000000class User extend ActiveModel::Callbacks include ActiveModel::SecurePassword define_model_callbacks :create has_secure_password attr_accessor :password_digest end rails-4.2.6/activemodel/test/models/visitor.rb000066400000000000000000000003371266740050600214300ustar00rootroot00000000000000class Visitor extend ActiveModel::Callbacks include ActiveModel::SecurePassword define_model_callbacks :create has_secure_password(validations: false) attr_accessor :password_digest, :password_confirmation end rails-4.2.6/activemodel/test/validators/000077500000000000000000000000001266740050600202665ustar00rootroot00000000000000rails-4.2.6/activemodel/test/validators/email_validator.rb000066400000000000000000000003641266740050600237520ustar00rootroot00000000000000class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << (options[:message] || "is not an email") unless value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i end endrails-4.2.6/activemodel/test/validators/namespace/000077500000000000000000000000001266740050600222225ustar00rootroot00000000000000rails-4.2.6/activemodel/test/validators/namespace/email_validator.rb000066400000000000000000000001531266740050600257020ustar00rootroot00000000000000require 'validators/email_validator' module Namespace class EmailValidator < ::EmailValidator end end rails-4.2.6/activerecord/000077500000000000000000000000001266740050600153155ustar00rootroot00000000000000rails-4.2.6/activerecord/CHANGELOG.md000066400000000000000000001431711266740050600171350ustar00rootroot00000000000000## Rails 4.2.6 (March 07, 2016) ## * Fix a bug where using `t.foreign_key` twice with the same `to_table` within the same table definition would only create one foreign key. *George Millo* * Fix regression in dirty attribute tracking after #dup. Changes to the clone no longer show as changed attributes in the original object. *Dominic Cleal* * Fix regression when loading fixture files with symbol keys. Closes #22584. *Yves Senn* * Fix `rake db:structure:dump` on Postgres when multiple schemas are used. Fixes #22346. *Nick Muerdter*, *ckoenig* * Introduce `connection.data_sources` and `connection.data_source_exists?`. These methods determine what relations can be used to back Active Record models (usually tables and views). *Yves Senn*, *Matthew Draper* ## Rails 4.2.5.2 (February 26, 2016) ## * No changes. ## Rails 4.2.5.1 (January 25, 2015) ## * No changes. ## Rails 4.2.5 (November 12, 2015) ## * No longer pass deprecated option `-i` to `pg_dump`. *Paul Sadauskas* * Set `scope.reordering_value` to `true` if :reordering values are specified. Fixes #21886. *Hiroaki Izu* * Avoid disabling errors on the PostgreSQL connection when enabling the standard_conforming_strings setting. Errors were previously disabled because the setting wasn't writable in Postgres 8.1 and didn't exist in earlier versions. Now Rails only supports Postgres 8.2+ we're fine to assume the setting exists. Disabling errors caused problems when using a connection pooling tool like PgBouncer because it's not guaranteed to have the same connection between calls to `execute` and it could leave the connection with errors disabled. Fixes #22101. *Harry Marr* * Includes HABTM returns correct size now. It's caused by the join dependency only instantiates one HABTM object because the join table hasn't a primary key. Fixes #16032. Examples: before: Project.first.salaried_developers.size # => 3 Project.includes(:salaried_developers).first.salaried_developers.size # => 1 after: Project.first.salaried_developers.size # => 3 Project.includes(:salaried_developers).first.salaried_developers.size # => 3 *Bigxiang* * Descriptive error message when fixtures contain a missing column. Closes #21201. *Yves Senn* * `bin/rake db:migrate` uses `ActiveRecord::Tasks::DatabaseTasks.migrations_paths` instead of `Migrator.migrations_paths`. *Tobias Bielohlawek* * Fix `rewhere` in a `has_many` association. Fixes #21955. *Josh Branchaud*, *Kal* * Added run_cmd class method to ActiveRecord::Tasks::DatabaseTasks for drying up Kernel.system() calls within this namespace and to avoid shell expansion by using a paramter list instead of string as arguments for Kernel.system(). Thanks to Nate Berkopec for supply patch to get test units passing. *Bryan Paxton* * Avoid leaking the first relation we call `first` on, per model. Fixes #21921. *Matthew Draper*, *Jean Boussier* * Allow deserialization of Active Record models that were YAML encoded prior to Rails 4.2 *Sean Griffin* * Correctly apply `unscope` when preloading through associations. *Jimmy Bourassa* * Ensure `select` quotes aliased attributes, even when using `from`. Fixes #21488 *Sean Griffin & @johanlunds* * Correct query for PostgreSQL 8.2 compatibility. *Ben Murphy*, *Matthew Draper* * Uniqueness validator raises descriptive error when running on a persisted record without primary key. Closes #21304. *Yves Senn* ## Rails 4.2.4 (August 24, 2015) ## * Skip statement cache on through association reader. If the through class has default scopes we should skip the statement cache. Closes #20745. *Rafael Mendonça França* * Fixes #19420. When generating schema.rb using Postgres BigInt[] data type the limit: 8 was not coming through. This caused it to become Int[] data type after doing a rebuild off of schema.rb. *Jake Waller* * Fix state being carried over from previous transaction. Considering the following example where `name` is a required attribute. Before we had `new_record?` returning `true` for a persisted record: author = Author.create! name: 'foo' author.name = nil author.save # => false author.new_record? # => true Fixes #20824. *Roque Pinel* * Correctly ignore `mark_for_destruction` when `autosave` isn't set to `true` when validating associations. Fixes #20882. *Sean Griffin* * Fix through associations using scopes having the scope merged multiple times. Fixes #20721. Fixes #20727. *Sean Griffin* * `ActiveRecord::Base.dump_schema_after_migration` applies migration tasks other than `db:migrate`. (eg. `db:rollback`, `db:migrate:dup`, ...) Fixes #20743. *Yves Senn* * Correctly raise `ActiveRecord::AssociationTypeMismatch` when assigning a wrong type to a namespaced association. Fixes #20545. *Diego Carrion* * Prevent error when using `force_reload: true` on an unassigned polymorphic belongs_to association. Fixes #20426. *James Dabbs* ## Rails 4.2.3 (June 25, 2015) ## * Let `WITH` queries (Common Table Expressions) be explainable. *Vladimir Kochnev* * Fix n+1 query problem when eager loading nil associations (fixes #18312) *Sammy Larbi* * Fixed an error which would occur in dirty checking when calling `update_attributes` from a getter. Fixes #20531. *Sean Griffin* * Ensure symbols passed to `ActiveRecord::Relation#select` are always treated as columns. Fixes #20360. *Sean Griffin* * Clear query cache when `ActiveRecord::Base#reload` is called. *Shane Hender* * Pass `:extend` option for `has_and_belongs_to_many` associations to the underlying `has_many :through`. *Jaehyun Shin* * Make `unscope` aware of "less than" and "greater than" conditions. *TAKAHASHI Kazuaki* * Revert behavior of `db:schema:load` back to loading the full environment. This ensures that initializers are run. Fixes #19545. *Yves Senn* * Fix missing index when using `timestamps` with the `index` option. The `index` option used with `timestamps` should be passed to both `column` definitions for `created_at` and `updated_at` rather than just the first. *Paul Mucur* * Rename `:class` to `:anonymous_class` in association options. Fixes #19659. *Andrew White* * Fixed a bug where uniqueness validations would error on out of range values, even if an validation should have prevented it from hitting the database. *Andrey Voronkov* * Foreign key related methods in the migration DSL respect `ActiveRecord::Base.pluralize_table_names = false`. Fixes #19643. *Mehmet Emin İNAÇ* * Reduce memory usage from loading types on pg. Fixes #19578. *Sean Griffin* * Fix referencing wrong table aliases while joining tables of has many through association (only when calling calculation methods). Fixes #19276. *pinglamb* * Don't attempt to update counter caches, when the column wasn't selected. Fixes #19437. *Sean Griffin* * Correctly persist a serialized attribute that has been returned to its default value by an in-place modification. Fixes #19467. *Matthew Draper* * Fix default `format` value in `ActiveRecord::Tasks::DatabaseTasks#schema_file`. *James Cox* * Dont enroll records in the transaction if they dont have commit callbacks. That was causing a memory grow problem when creating a lot of records inside a transaction. Fixes #15549. *Will Bryant*, *Aaron Patterson* * Correctly create through records when created on a has many through association when using `where`. Fixes #19073. *Sean Griffin* ## Rails 4.2.2 (June 16, 2015) ## * No Changes * ## Rails 4.2.1 (March 19, 2015) ## * Fixed ActiveRecord::Relation#becomes! and changed_attributes issues for type column Fixes #17139. *Miklos Fazekas* * `remove_reference` with `foreign_key: true` removes the foreign key before removing the column. This fixes a bug where it was not possible to remove the column on MySQL. Fixes #18664. *Yves Senn* * Add a `:foreign_key` option to `references` and associated migration methods. The model and migration generators now use this option, rather than the `add_foreign_key` form. *Sean Griffin* * Fix rounding problem for PostgreSQL timestamp column. If timestamp column have the precision, it need to format according to the precision of timestamp column. *Ryuta Kamizono* * Respect the database default charset for `schema_migrations` table. The charset of `version` column in `schema_migrations` table is depend on the database default charset and collation rather than the encoding of the connection. *Ryuta Kamizono* * Respect custom primary keys for associations when calling `Relation#where` Fixes #18813. *Sean Griffin* * Fixed several edge cases which could result in a counter cache updating twice or not updating at all for `has_many` and `has_many :through`. Fixes #10865. *Sean Griffin* * Foreign keys added by migrations were given random, generated names. This meant a different `structure.sql` would be generated every time a developer ran migrations on their machine. The generated part of foreign key names is now a hash of the table name and column name, which is consistent every time you run the migration. *Chris Sinjakli* * Fixed ActiveRecord::Relation#group method when argument is SQL reserved key word: SplitTest.group(:key).count Property.group(:value).count *Bogdan Gusiev* * Don't define autosave association callbacks twice from `accepts_nested_attributes_for`. Fixes #18704. *Sean Griffin* * Integer types will no longer raise a `RangeError` when assigning an attribute, but will instead raise when going to the database. Fixes several vague issues which were never reported directly. See the commit message from the commit which added this line for some examples. *Sean Griffin* * Values which would error while being sent to the database (such as an ASCII-8BIT string with invalid UTF-8 bytes on Sqlite3), no longer error on assignment. They will still error when sent to the database, but you are given the ability to re-assign it to a valid value. Fixes #18580. *Sean Griffin* * Don't remove join dependencies in `Relation#exists?` Fixes #18632. *Sean Griffin* * Invalid values assigned to a JSON column are assumed to be `nil`. Fixes #18629. *Sean Griffin* * No longer issue deprecation warning when including a scope with extensions. Previously every scope with extension methods was transformed into an instance dependent scope. Including such a scope would wrongfully issue a deprecation warning. This is no longer the case. Fixes #18467. *Yves Senn* * Correctly use the type provided by `serialize` when updating records using optimistic locking. Fixes #18385. *Sean Griffin* * `attribute_will_change!` will no longer cause non-persistable attributes to be sent to the database. Fixes #18407. *Sean Griffin* * Format the datetime string according to the precision of the datetime field. Incompatible to rounding behavior between MySQL 5.6 and earlier. In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`: http://bugs.mysql.com/bug.php?id=68760 *Ryuta Kamizono* * Allow precision option for MySQL datetimes. *Ryuta Kamizono* * Clear query cache on rollback. *Florian Weingarten* * Fixed setting of foreign_key for through associations while building of new record. Fixes #12698. *Ivan Antropov* * Fixed automatic inverse_of for models nested in module. *Andrew McCloud* * Fix `reaping_frequency` option when the value is a string. This usually happens when it is configured using `DATABASE_URL`. *korbin* * Fix error message when trying to create an associated record and the foreign key is missing. Before this fix the following exception was being raised: NoMethodError: undefined method `val' for # Now the message is: ActiveRecord::UnknownAttributeError: unknown attribute 'foreign_key' for Model. *Rafael Mendonça França* * Fix change detection problem for PostgreSQL bytea type and `ArgumentError: string contains null byte` exception with pg-0.18. Fixes #17680. *Lars Kanis* * When a table has a composite primary key, the `primary_key` method for SQLite3 and PostgreSQL adapters was only returning the first field of the key. Ensures that it will return nil instead, as Active Record doesn't support composite primary keys. Fixes #18070. *arthurnn* * Ensure `first!` and friends work on loaded associations. Fixes #18237. *Sean Griffin* * Dump the default `nil` for PostgreSQL UUID primary key. *Ryuta Kamizono* * Don't raise when writing an attribute with an out-of-range datetime passed by the user. *Grey Baker* * Fixes bug with 'ActiveRecord::Type::Numeric' that causes negative values to be marked as having changed when set to the same negative value. Fixes #18161. *Daniel Fox* ## Rails 4.2.0 (December 20, 2014) ## * Introduce `force: :cascade` option for `create_table`. Using this option will recreate tables even if they have dependent objects (like foreign keys). `db/schema.rb` now uses `force: :cascade`. This makes it possible to reload the schema when foreign keys are in place. *Matthew Draper*, *Yves Senn* * `db:schema:load` and `db:structure:load` no longer purge the database before loading the schema. This is left for the user to do. `db:test:prepare` will still purge the database. Fixes #17945. *Yves Senn* * Fix undesirable RangeError by Type::Integer. Add Type::UnsignedInteger. *Ryuta Kamizono* * Add `foreign_type` option to `has_one` and `has_many` association macros. This option enables to define the column name of associated object's type for polymorphic associations. *Ulisses Almeida, Kassio Borges* * `add_timestamps` and `remove_timestamps` now properly reversible with options. *Noam Gagliardi-Rabinovich* * Bring back `db:test:prepare` to synchronize the test database schema. Manual synchronization using `bin/rake db:test:prepare` is required when a migration is rolled-back, edited and reapplied. `ActiveRecord::Base.maintain_test_schema` now uses `db:test:prepare` to synchronize the schema. Plugins can use this task as a hook to provide custom behavior after the schema has been loaded. NOTE: `test:prepare` runs before the schema is synchronized. Fixes #17171, #15787. *Yves Senn* * Change `reflections` public api to return the keys as String objects. Fixes #16928. *arthurnn* * Renaming a table in pg also renames the primary key index. Fixes #12856 *Sean Griffin* * Make it possible to access fixtures excluded by a `default_scope`. *Yves Senn* * Fix preloading of associations with a scope containing joins along with conditions on the joined association. *Siddharth Sharma* * Add `Table#name` to match `TableDefinition#name`. *Cody Cutrer* * Cache `CollectionAssociation#reader` proxies separately before and after the owner has been saved so that the proxy is not cached without the owner's id. *Ben Woosley* * `ActiveRecord::ReadOnlyRecord` now has a descriptive message. *Franky W.* * Fix preloading of associations which unscope a default scope. Fixes #11036. *Byron Bischoff* * Added SchemaDumper support for tables with jsonb columns. *Ted O'Meara* * Deprecate `sanitize_sql_hash_for_conditions` without replacement. Using a `Relation` for performing queries and updates is the prefered API. *Sean Griffin* * Queries now properly type cast values that are part of a join statement, even when using type decorators such as `serialize`. *Melanie Gilman & Sean Griffin* * MySQL enum type lookups, with values matching another type, no longer result in an endless loop. Fixes #17402. *Yves Senn* * Raise `ArgumentError` when the body of a scope is not callable. *Mauro George* * Use type column first in multi-column indexes created with `add-reference`. *Derek Prior* * Fix `Relation.rewhere` to work with Range values. *Dan Olson* * `AR::UnknownAttributeError` now includes the class name of a record. User.new(name: "Yuki Nishijima", project_attributes: {name: "kaminari"}) # => ActiveRecord::UnknownAttributeError: unknown attribute 'name' for User. *Yuki Nishijima* * Fix a regression causing `after_create` callbacks to run before associated records are autosaved. Fixes #17209. *Agis Anastasopoulos* * Honor overridden `rack.test` in Rack environment for the connection management middleware. *Simon Eskildsen* * Add a truncate method to the connection. *Aaron Patterson* * Don't autosave unchanged has_one through records. *Alan Kennedy*, *Steve Parrington* * Do not dump foreign keys for ignored tables. *Yves Senn* * PostgreSQL adapter correctly dumps foreign keys targeting tables outside the schema search path. Fixes #16907. *Matthew Draper*, *Yves Senn* * When a thread is killed, rollback the active transaction, instead of committing it during the stack unwind. Previously, we could commit half- completed work. This fix only works for Ruby 2.0+; on 1.9, we can't distinguish a thread kill from an ordinary non-local (block) return, so must default to committing. *Chris Hanks* * A `NullRelation` should represent nothing. This fixes a bug where `Comment.where(post_id: Post.none)` returned a non-empty result. Fixes #15176. *Matthew Draper*, *Yves Senn* * Include default column limits in schema.rb. Allows defaults to be changed in the future without affecting old migrations that assumed old defaults. *Jeremy Kemper* * MySQL: schema.rb now includes TEXT and BLOB column limits. *Jeremy Kemper* * MySQL: correct LONGTEXT and LONGBLOB limits from 2GB to their true 4GB. *Jeremy Kemper* * SQLite3Adapter now checks for views in `table_exists?`. Fixes #14041. *Girish Sonawane* * Introduce `connection.supports_views?` to check whether the current adapter has support for SQL views. Connection adapters should define this method. *Yves Senn* * Allow included modules to override association methods. Fixes #16684. *Yves Senn* * Schema loading rake tasks (like `db:schema:load` and `db:setup`) maintain the database connection to the current environment. Fixes #16757. *Joshua Cody*, *Yves Senn* * MySQL: set the connection collation along with the charset. Sets the connection collation to the database collation configured in database.yml. Otherwise, `SET NAMES utf8mb4` will use the default collation for that charset (utf8mb4_general_ci) when you may have chosen a different collation, like utf8mb4_unicode_ci. This only applies to literal string comparisons, not column values, so it is unlikely to affect you. *Jeremy Kemper* * `default_sequence_name` from the PostgreSQL adapter returns a `String`. *Yves Senn* * Fix a regression where whitespaces were stripped from DISTINCT queries in PostgreSQL. *Agis Anastasopoulos* Fixes #16623. * Fix has_many :through relation merging failing when dynamic conditions are passed as a lambda with an arity of one. Fixes #16128. *Agis Anastasopoulos* * Fix `Relation#exists?` to work with polymorphic associations. Fixes #15821. *Kassio Borges* * Currently, Active Record rescues any errors raised within `after_rollback`/`after_create` callbacks and prints them to the logs. Future versions of Rails will not rescue these errors anymore and just bubble them up like the other callbacks. This commit adds an opt-in flag to enable not rescuing the errors. Example: # Do not swallow errors in after_commit/after_rollback callbacks. config.active_record.raise_in_transactional_callbacks = true Fixes #13460. *arthurnn* * Fix an issue where custom accessor methods (such as those generated by `enum`) with the same name as a global method are incorrectly overridden when subclassing. Fixes #16288. *Godfrey Chan* * `*_was` and `changes` now work correctly for in-place attribute changes as well. *Sean Griffin* * Fix regression on `after_commit` that did not fire with nested transactions. Fixes #16425. *arthurnn* * Do not try to write timestamps when a table has no timestamps columns. Fixes #8813. *Sergey Potapov* * `index_exists?` with `:name` option does verify specified columns. Example: add_index :articles, :title, name: "idx_title" # Before: index_exists? :articles, :title, name: "idx_title" # => `true` index_exists? :articles, :body, name: "idx_title" # => `true` # After: index_exists? :articles, :title, name: "idx_title" # => `true` index_exists? :articles, :body, name: "idx_title" # => `false` *Yves Senn*, *Matthew Draper* * `add_timestamps` and `t.timestamps` now require you to pass the `:null` option. Not passing the option is deprecated but the default is still `null: true`. With Rails 5 this will change to `null: false`. *Sean Griffin* * When calling `update_columns` on a record that is not persisted, the error message now reflects whether that object is a new record or has been destroyed. *Lachlan Sylvester* * Define `id_was` to get the previous value of the primary key. Currently when we call `id_was` and we have a custom primary key name, Active Record will return the current value of the primary key. This makes it impossible to correctly do an update operation if you change the id. Fixes #16413. *Rafael Mendonça França* * Deprecate `DatabaseTasks.load_schema` to act on the current connection. Use `.load_schema_current` instead. In the future `load_schema` will require the `configuration` to act on as an argument. *Yves Senn* * Fix automatic maintaining test schema to properly handle sql structure schema format. Fixes #15394. *Wojciech WnÄ™trzak* * Fix type casting to Decimal from Float with large precision. *Tomohiro Hashidate* * Deprecate `Reflection#source_macro` `Reflection#source_macro` is no longer needed in Active Record source so it has been deprecated. Code that used `source_macro` was removed in #16353. *Eileen M. Uchtitelle*, *Aaron Patterson* * No verbose backtrace by `db:drop` when database does not exist. Fixes #16295. *Kenn Ejima* * Add support for PostgreSQL JSONB. Example: create_table :posts do |t| t.jsonb :meta_data end *Philippe Creux*, *Chris Teague* * `db:purge` with MySQL respects `Rails.env`. *Yves Senn* * `change_column_default :table, :column, nil` with PostgreSQL will issue a `DROP DEFAULT` instead of a `DEFAULT NULL` query. Fixes #16261. *Matthew Draper*, *Yves Senn* * Allow to specify a type for the foreign key column in `references` and `add_reference`. Example: change_table :vehicle do |t| t.references :station, type: :uuid end *Andrey Novikov*, *Åukasz Sarnacki* * `create_join_table` removes a common prefix when generating the join table. This matches the existing behavior of HABTM associations. Fixes #13683. *Stefan Kanev* * Do not swallow errors on `compute_type` when having a bad `alias_method` on a class. *arthurnn* * PostgreSQL invalid `uuid` are convert to nil. *Abdelkader Boudih* * Restore 4.0 behavior for using serialize attributes with `JSON` as coder. With 4.1.x, `serialize` started returning a string when `JSON` was passed as the second attribute. It will now return a hash as per previous versions. Example: class Post < ActiveRecord::Base serialize :comment, JSON end class Comment include ActiveModel::Model attr_accessor :category, :text end post = Post.create! post.comment = Comment.new(category: "Animals", text: "This is a comment about squirrels.") post.save! # 4.0 post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."} # 4.1 before post.comment # => "#" # 4.1 after post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."} When using `JSON` as the coder in `serialize`, Active Record will use the new `ActiveRecord::Coders::JSON` coder which delegates its `dump/load` to `ActiveSupport::JSON.encode/decode`. This ensures special objects are dumped correctly using the `#as_json` hook. To keep the previous behaviour, supply a custom coder instead ([example](https://gist.github.com/jenncoop/8c4142bbe59da77daa63)). Fixes #15594. *Jenn Cooper* * Do not use `RENAME INDEX` syntax for MariaDB 10.0. Fixes #15931. *Jeff Browning* * Calling `#empty?` on a `has_many` association would use the value from the counter cache if one exists. *David Verhasselt* * Fix the schema dump generated for tables without constraints and with primary key with default value of custom PostgreSQL function result. Fixes #16111. *Andrey Novikov* * Fix the SQL generated when a `delete_all` is run on an association to not produce an `IN` statements. Before: UPDATE "categorizations" SET "category_id" = NULL WHERE "categorizations"."category_id" = 1 AND "categorizations"."id" IN (1, 2) After: UPDATE "categorizations" SET "category_id" = NULL WHERE "categorizations"."category_id" = 1 *Eileen M. Uchitelle, Aaron Patterson* * Avoid type casting boolean and `ActiveSupport::Duration` values to numeric values for string columns. Otherwise, in some database, the string column values will be coerced to a numeric allowing false or 0.seconds match any string starting with a non-digit. Example: App.where(apikey: false) # => SELECT * FROM users WHERE apikey = '0' *Dylan Thacker-Smith* * Add a `:required` option to singular associations, providing a nicer API for presence validations on associations. *Sean Griffin* * Fix an error in `reset_counters` when associations have `select` scope. (Call to `count` generated invalid SQL.) *Cade Truitt* * After a successful `reload`, `new_record?` is always false. Fixes #12101. *Matthew Draper* * PostgreSQL renaming table doesn't attempt to rename non existent sequences. *Abdelkader Boudih* * Move 'dependent: :destroy' handling for `belongs_to` from `before_destroy` to `after_destroy` callback chain Fixes #12380. *Ivan Antropov* * Detect in-place modifications on String attributes. Before this change, an attribute modified in-place had to be marked as changed in order for it to be persisted in the database. Now it is no longer required. Before: user = User.first user.name << ' Griffin' user.name_will_change! user.save user.reload.name # => "Sean Griffin" After: user = User.first user.name << ' Griffin' user.save user.reload.name # => "Sean Griffin" *Sean Griffin* * Add `ActiveRecord::Base#validate!` that raises `RecordInvalid` if the record is invalid. *Bogdan Gusiev*, *Marc Schütz* * Support for adding and removing foreign keys. Foreign keys are now a part of `schema.rb`. This is supported by Mysql2Adapter, MysqlAdapter and PostgreSQLAdapter. Many thanks to *Matthew Higgins* for laying the foundation with his work on [foreigner](https://github.com/matthuhiggins/foreigner). Example: # within your migrations: add_foreign_key :articles, :authors remove_foreign_key :articles, :authors *Yves Senn* * Fix subtle bugs regarding attribute assignment on models with no primary key. `'id'` will no longer be part of the attributes hash. *Sean Griffin* * Deprecate automatic counter caches on `has_many :through`. The behavior was broken and inconsistent. *Sean Griffin* * `preload` preserves readonly flag for associations. See #15853. *Yves Senn* * Assume numeric types have changed if they were assigned to a value that would fail numericality validation, regardless of the old value. Previously this would only occur if the old value was 0. Example: model = Model.create!(number: 5) model.number = '5wibble' model.number_changed? # => true Fixes #14731. *Sean Griffin* * `reload` no longer merges with the existing attributes. The attribute hash is fully replaced. The record is put into the same state as it would be with `Model.find(model.id)`. *Sean Griffin* * The object returned from `select_all` must respond to `column_types`. If this is not the case a `NoMethodError` is raised. *Sean Griffin* * Detect in-place modifications of PG array types *Sean Griffin* * Add `bin/rake db:purge` task to empty the current database. *Yves Senn* * Deprecate `serialized_attributes` without replacement. *Sean Griffin* * Correctly extract IPv6 addresses from `DATABASE_URI`: the square brackets are part of the URI structure, not the actual host. Fixes #15705. *Andy Bakun*, *Aaron Stone* * Ensure both parent IDs are set on join records when both sides of a through association are new. *Sean Griffin* * `ActiveRecord::Dirty` now detects in-place changes to mutable values. Serialized attributes on ActiveRecord models will no longer save when unchanged. Fixes #8328. *Sean Griffin* * `Pluck` now works when selecting columns from different tables with the same name. Fixes #15649. *Sean Griffin* * Remove `cache_attributes` and friends. All attributes are cached. *Sean Griffin* * Remove deprecated method `ActiveRecord::Base.quoted_locking_column`. *Akshay Vishnoi* * `ActiveRecord::FinderMethods.find` with block can handle proc parameter as `Enumerable#find` does. Fixes #15382. *James Yang* * Make timezone aware attributes work with PostgreSQL array columns. Fixes #13402. *Kuldeep Aggarwal*, *Sean Griffin* * `ActiveRecord::SchemaMigration` has no primary key regardless of the `primary_key_prefix_type` configuration. Fixes #15051. *JoseLuis Torres*, *Yves Senn* * `rake db:migrate:status` works with legacy migration numbers like `00018_xyz.rb`. Fixes #15538. *Yves Senn* * Baseclass becomes! subclass. Before this change, a record which changed its STI type, could not be updated. Fixes #14785. *Matthew Draper*, *Earl St Sauver*, *Edo Balvers* * Remove deprecated `ActiveRecord::Migrator.proper_table_name`. Use the `proper_table_name` instance method on `ActiveRecord::Migration` instead. *Akshay Vishnoi* * Fix regression on eager loading association based on SQL query rather than existing column. Fixes #15480. *Lauro Caetano*, *Carlos Antonio da Silva* * Deprecate returning `nil` from `column_for_attribute` when no column exists. It will return a null object in Rails 5.0 *Sean Griffin* * Implemented `ActiveRecord::Base#pretty_print` to work with PP. *Ethan* * Preserve type when dumping PostgreSQL point, bit, bit varying and money columns. *Yves Senn* * New records remain new after YAML serialization. *Sean Griffin* * PostgreSQL support default values for enum types. Fixes #7814. *Yves Senn* * PostgreSQL `default_sequence_name` respects schema. Fixes #7516. *Yves Senn* * Fix `columns_for_distinct` of PostgreSQL adapter to work correctly with orders without sort direction modifiers. *Nikolay Kondratyev* * PostgreSQL `reset_pk_sequence!` respects schemas. Fixes #14719. *Yves Senn* * Keep PostgreSQL `hstore` and `json` attributes as `Hash` in `@attributes`. Fixes duplication in combination with `store_accessor`. Fixes #15369. *Yves Senn* * `rake railties:install:migrations` respects the order of railties. *Arun Agrawal* * Fix redefine a `has_and_belongs_to_many` inside inherited class Fixing regression case, where redefining the same `has_and_belongs_to_many` definition into a subclass would raise. Fixes #14983. *arthurnn* * Fix `has_and_belongs_to_many` public reflection. When defining a `has_and_belongs_to_many`, internally we convert that to two has_many. But as `reflections` is a public API, people expect to see the right macro. Fixes #14682. *arthurnn* * Fix serialization for records with an attribute named `format`. Fixes #15188. *Godfrey Chan* * When a `group` is set, `sum`, `size`, `average`, `minimum` and `maximum` on a NullRelation should return a Hash. *Kuldeep Aggarwal* * Fix serialized fields returning serialized data after being updated with `update_column`. *Simon Hørup Eskildsen* * Fix polymorphic eager loading when using a String as foreign key. Fixes #14734. *Lauro Caetano* * Change belongs_to touch to be consistent with timestamp updates If a model is set up with a belongs_to: touch relationship the parent record will only be touched if the record was modified. This makes it consistent with timestamp updating on the record itself. *Brock Trappitt* * Fix the inferred table name of a `has_and_belongs_to_many` auxiliary table inside a schema. Fixes #14824. *Eric Chahin* * Remove unused `:timestamp` type. Transparently alias it to `:datetime` in all cases. Fixes inconsistencies when column types are sent outside of `ActiveRecord`, such as for XML Serialization. *Sean Griffin* * Fix bug that added `table_name_prefix` and `table_name_suffix` to extension names in PostgreSQL when migrating. *Joao Carlos* * The `:index` option in migrations, which previously was only available for `references`, now works with any column types. *Marc Schütz* * Add support for counter name to be passed as parameter on `CounterCache::ClassMethods#reset_counters`. *jnormore* * Restrict deletion of record when using `delete_all` with `uniq`, `group`, `having` or `offset`. In these cases the generated query ignored them and that caused unintended records to be deleted. Fixes #11985. *Leandro Facchinetti* * Floats with limit >= 25 that get turned into doubles in MySQL no longer have their limit dropped from the schema. Fixes #14135. *Aaron Nelson* * Fix how to calculate associated class name when using namespaced `has_and_belongs_to_many` association. Fixes #14709. *Kassio Borges* * `ActiveRecord::Relation::Merger#filter_binds` now compares equivalent symbols and strings in column names as equal. This fixes a rare case in which more bind values are passed than there are placeholders for them in the generated SQL statement, which can make PostgreSQL throw a `StatementInvalid` exception. *Nat Budin* * Fix `stored_attributes` to correctly merge the details of stored attributes defined in parent classes. Fixes #14672. *Brad Bennett*, *Jessica Yao*, *Lakshmi Parthasarathy* * `change_column_default` allows `[]` as argument to `change_column_default`. Fixes #11586. *Yves Senn* * Handle `name` and `"char"` column types in the PostgreSQL adapter. `name` and `"char"` are special character types used internally by PostgreSQL and are used by internal system catalogs. These field types can sometimes show up in structure-sniffing queries that feature internal system structures or with certain PostgreSQL extensions. *J Smith*, *Yves Senn* * Fix `PostgreSQLAdapter::OID::Float#type_cast` to convert Infinity and NaN PostgreSQL values into a native Ruby `Float::INFINITY` and `Float::NAN` Before: Point.create(value: 1.0/0) Point.last.value # => 0.0 After: Point.create(value: 1.0/0) Point.last.value # => Infinity *Innokenty Mikhailov* * Allow the PostgreSQL adapter to handle bigserial primary key types again. Fixes #10410. *Patrick Robertson* * Deprecate joining, eager loading and preloading of instance dependent associations without replacement. These operations happen before instances are created. The current behavior is unexpected and can result in broken behavior. Fixes #15024. *Yves Senn* * Fix `has_and_belongs_to_many` CollectionAssociation size calculations. `has_and_belongs_to_many` should fall back to using the normal CollectionAssociation's size calculation if the collection is not cached or loaded. Fixes #14913, #14914. *Fred Wu* * Return a non zero status when running `rake db:migrate:status` and migration table does not exist. *Paul B.* * Add support for module-level `table_name_suffix` in models. This makes `table_name_suffix` work the same way as `table_name_prefix` when using namespaced models. *Jenner LaFave* * Revert the behaviour of `ActiveRecord::Relation#join` changed through 4.0 => 4.1 to 4.0. In 4.1.0 `Relation#join` is delegated to `Arel#SelectManager`. In 4.0 series it is delegated to `Array#join`. *Bogdan Gusiev* * Log nil binary column values correctly. When an object with a binary column is updated with a nil value in that column, the SQL logger would throw an exception when trying to log that nil value. This only occurs when updating a record that already has a non-nil value in that column since an initial nil value isn't included in the SQL anyway (at least, when dirty checking is enabled.) The column's new value will now be logged as `` to parallel the existing `` for non-nil values. *James Coleman* * Rails will now pass a custom validation context through to autosave associations in order to validate child associations with the same context. Fixes #13854. *Eric Chahin*, *Aaron Nelson*, *Kevin Casey* * Stringify all variables keys of MySQL connection configuration. When `sql_mode` variable for MySQL adapters set in configuration as `String` was ignored and overwritten by strict mode option. Fixes #14895. *Paul Nikitochkin* * Ensure SQLite3 statements are closed on errors. Fixes #13631. *Timur Alperovich* * Give `ActiveRecord::PredicateBuilder` private methods the privacy they deserve. *Hector Satre* * When using a custom `join_table` name on a `habtm`, rails was not saving it on Reflections. This causes a problem when rails loads fixtures, because it uses the reflections to set database with fixtures. Fixes #14845. *Kassio Borges* * Reset the cache when modifying a Relation with cached Arel. Additionally display a warning message to make the user aware. *Yves Senn* * PostgreSQL should internally use `:datetime` consistently for TimeStamp. Assures different spellings of timestamps are treated the same. Example: mytimestamp.simplified_type('timestamp without time zone') # => :datetime mytimestamp.simplified_type('timestamp(6) without time zone') # => also :datetime (previously would be :timestamp) See #14513. *Jefferson Lai* * `ActiveRecord::Base.no_touching` no longer triggers callbacks or start empty transactions. Fixes #14841. *Lucas Mazza* * Fix name collision with `Array#select!` with `Relation#select!`. Fixes #14752. *Earl St Sauver* * Fix unexpected behavior for `has_many :through` associations going through a scoped `has_many`. If a `has_many` association is adjusted using a scope, and another `has_many :through` uses this association, then the scope adjustment is unexpectedly neglected. Fixes #14537. *Jan Habermann* * `@destroyed` should always be set to `false` when an object is duped. *Kuldeep Aggarwal* * Enable `has_many` associations to support irregular inflections. Fixes #8928. *arthurnn*, *Javier Goizueta* * Fix `count` used with a grouping not returning a Hash. Fixes #14721. *Eric Chahin* * `sanitize_sql_like` helper method to escape a string for safe use in an SQL LIKE statement. Example: class Article def self.search(term) where("title LIKE ?", sanitize_sql_like(term)) end end Article.search("20% _reduction_") # => Query looks like "... title LIKE '20\% \_reduction\_' ..." *Rob Gilson*, *Yves Senn* * Do not quote uuid default value on `change_column`. Fixes #14604. *Eric Chahin* * The comparison between `Relation` and `CollectionProxy` should be consistent. Example: author.posts == Post.where(author_id: author.id) # => true Post.where(author_id: author.id) == author.posts # => true Fixes #13506. *Lauro Caetano* * Calling `delete_all` on an unloaded `CollectionProxy` no longer generates an SQL statement containing each id of the collection: Before: DELETE FROM `model` WHERE `model`.`parent_id` = 1 AND `model`.`id` IN (1, 2, 3...) After: DELETE FROM `model` WHERE `model`.`parent_id` = 1 *Eileen M. Uchitelle*, *Aaron Patterson* * Fix invalid SQL when aggregate methods (`empty?`, `any?`, `count`) used with `select`. Fixes #13648. *Simon Woker* * PostgreSQL adapter only warns once for every missing OID per connection. Fixes #14275. *Matthew Draper*, *Yves Senn* * PostgreSQL adapter automatically reloads it's type map when encountering unknown OIDs. Fixes #14678. *Matthew Draper*, *Yves Senn* * Fix insertion of records via `has_many :through` association with scope. Fixes #3548. *Ivan Antropov* * Auto-generate stable fixture UUIDs on PostgreSQL. Fixes #11524. *Roderick van Domburg* * Fix a problem where an enum would overwrite values of another enum with the same name in an unrelated class. Fixes #14607. *Evan Whalen* * PostgreSQL and SQLite string columns no longer have a default limit of 255. Fixes #13435, #9153. *Vladimir Sazhin*, *Toms Mikoss*, *Yves Senn* * Make possible to have an association called `records`. Fixes #11645. *prathamesh-sonpatki* * `to_sql` on an association now matches the query that is actually executed, where it could previously have incorrectly accrued additional conditions (e.g. as a result of a previous query). `CollectionProxy` now always defers to the association scope's `arel` method so the (incorrect) inherited one should be entirely concealed. Fixes #14003. *Jefferson Lai* * Block a few default Class methods as scope name. For instance, this will raise: scope :public, -> { where(status: 1) } *arthurnn* * Fix error when using `with_options` with lambda. Fixes #9805. *Lauro Caetano* * Switch `sqlite3:///` URLs (which were temporarily deprecated in 4.1) from relative to absolute. If you still want the previous interpretation, you should replace `sqlite3:///my/path` with `sqlite3:my/path`. *Matthew Draper* * Treat blank UUID values as `nil`. Example: Sample.new(uuid_field: '') #=> *Dmitry Lavrov* * Enable support for materialized views on PostgreSQL >= 9.3. *Dave Lee* * The PostgreSQL adapter supports custom domains. Fixes #14305. *Yves Senn* * PostgreSQL `Column#type` is now determined through the corresponding OID. The column types stay the same except for enum columns. They no longer have `nil` as type but `enum`. See #7814. *Yves Senn* * Fix error when specifying a non-empty default value on a PostgreSQL array column. Fixes #10613. *Luke Steensen* * Fix error where `.persisted?` throws SystemStackError for an unsaved model with a custom primary key that did not save due to validation error. Fixes #14393. *Chris Finne* * Introduce `validate` as an alias for `valid?`. This is more intuitive when you want to run validations but don't care about the return value. *Henrik Nyh* * Create indexes inline in CREATE TABLE for MySQL. This is important, because adding an index on a temporary table after it has been created would commit the transaction. It also allows creating and dropping indexed tables with fewer queries and fewer permissions required. Example: create_table :temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query" do |t| t.index :zip end # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca* * Use singular table name in generated migrations when `ActiveRecord::Base.pluralize_table_names` is `false`. Fixes #13426. *Kuldeep Aggarwal* * `touch` accepts many attributes to be touched at once. Example: # touches :signed_at, :sealed_at, and :updated_at/on attributes. Photo.last.touch(:signed_at, :sealed_at) *James Pinto* * `rake db:structure:dump` only dumps schema information if the schema migration table exists. Fixes #14217. *Yves Senn* * Reap connections that were checked out by now-dead threads, instead of waiting until they disconnect by themselves. Before this change, a suitably constructed series of short-lived threads could starve the connection pool, without ever having more than a couple alive at the same time. *Matthew Draper* * `pk_and_sequence_for` now ensures that only the pg_depend entries pointing to pg_class, and thus only sequence objects, are considered. *Josh Williams* * `where.not` adds `references` for `includes` like normal `where` calls do. Fixes #14406. *Yves Senn* * Extend fixture `$LABEL` replacement to allow string interpolation. Example: martin: email: $LABEL@email.com users(:martin).email # => martin@email.com *Eric Steele* * Add support for `Relation` be passed as parameter on `QueryCache#select_all`. Fixes #14361. *arthurnn* * Passing an Active Record object to `find` or `exists?` is now deprecated. Call `.id` on the object first. *Aaron Patterson* * Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation. *Ryuta Kamizono* * Support for MySQL 5.6 fractional seconds. *arthurnn*, *Tatsuhiko Miyagawa* * Support for PostgreSQL `citext` data type enabling case-insensitive `where` values without needing to wrap in UPPER/LOWER sql functions. *Troy Kruthoff*, *Lachlan Sylvester* * Only save has_one associations if record has changes. Previously after save related callbacks, such as `#after_commit`, were triggered when the has_one object did not get saved to the db. *Alan Kennedy* * Allow strings to specify the `#order` value. Example: Model.order(id: 'asc').to_sql == Model.order(id: :asc).to_sql *Marcelo Casiraghi*, *Robin Dupret* * Dynamically register PostgreSQL enum OIDs. This prevents "unknown OID" warnings on enum columns. *Dieter Komendera* * `includes` is able to detect the right preloading strategy when string joins are involved. Fixes #14109. *Aaron Patterson*, *Yves Senn* * Fix error with validation with enum fields for records where the value for any enum attribute is always evaluated as 0 during uniqueness validation. Fixes #14172. *Vilius Luneckas* *Ahmed AbouElhamayed* * `before_add` callbacks are fired before the record is saved on `has_and_belongs_to_many` associations *and* on `has_many :through` associations. Before this change, `before_add` callbacks would be fired before the record was saved on `has_and_belongs_to_many` associations, but *not* on `has_many :through` associations. Fixes #14144. * Fix STI classes not defining an attribute method if there is a conflicting private method defined on its ancestors. Fixes #11569. *Godfrey Chan* * Coerce strings when reading attributes. Fixes #10485. Example: book = Book.new(title: 12345) book.save! book.title # => "12345" *Yves Senn* * Deprecate half-baked support for PostgreSQL range values with excluding beginnings. We currently map PostgreSQL ranges to Ruby ranges. This conversion is not fully possible because the Ruby range does not support excluded beginnings. The current solution of incrementing the beginning is not correct and is now deprecated. For subtypes where we don't know how to increment (e.g. `#succ` is not defined) it will raise an `ArgumentException` for ranges with excluding beginnings. *Yves Senn* * Support for user created range types in PostgreSQL. *Yves Senn* Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activerecord/CHANGELOG.md) for previous changes. rails-4.2.6/activerecord/MIT-LICENSE000066400000000000000000000020601266740050600167470ustar00rootroot00000000000000Copyright (c) 2004-2014 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.rails-4.2.6/activerecord/README.rdoc000066400000000000000000000151111266740050600171220ustar00rootroot00000000000000= Active Record -- Object-relational mapping in Rails Active Record connects classes to relational database tables to establish an almost zero-configuration persistence layer for applications. The library provides a base class that, when subclassed, sets up a mapping between the new class and an existing table in the database. In the context of an application, these classes are commonly referred to as *models*. Models can also be connected to other models; this is done by defining *associations*. Active Record relies heavily on naming in that it uses class and association names to establish mappings between respective database tables and foreign key columns. Although these mappings can be defined explicitly, it's recommended to follow naming conventions, especially when getting started with the library. A short rundown of some of the major features: * Automated mapping between classes and tables, attributes and columns. class Product < ActiveRecord::Base end {Learn more}[link:classes/ActiveRecord/Base.html] The Product class is automatically mapped to the table named "products", which might look like this: CREATE TABLE products ( id int(11) NOT NULL auto_increment, name varchar(255), PRIMARY KEY (id) ); This would also define the following accessors: `Product#name` and `Product#name=(new_name)`. * Associations between objects defined by simple class methods. class Firm < ActiveRecord::Base has_many :clients has_one :account belongs_to :conglomerate end {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] * Aggregations of value objects. class Account < ActiveRecord::Base composed_of :balance, class_name: 'Money', mapping: %w(balance amount) composed_of :address, mapping: [%w(address_street street), %w(address_city city)] end {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html] * Validation rules that can differ for new or existing objects. class Account < ActiveRecord::Base validates :subdomain, :name, :email_address, :password, presence: true validates :subdomain, uniqueness: true validates :terms_of_service, acceptance: true, on: :create validates :password, :email_address, confirmation: true, on: :create end {Learn more}[link:classes/ActiveRecord/Validations.html] * Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.). class Person < ActiveRecord::Base before_destroy :invalidate_payment_plan # the `invalidate_payment_plan` method gets called just before Person#destroy end {Learn more}[link:classes/ActiveRecord/Callbacks.html] * Inheritance hierarchies. class Company < ActiveRecord::Base; end class Firm < Company; end class Client < Company; end class PriorityClient < Client; end {Learn more}[link:classes/ActiveRecord/Base.html] * Transactions. # Database transaction Account.transaction do david.withdrawal(100) mary.deposit(100) end {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] * Reflections on columns, associations, and aggregations. reflection = Firm.reflect_on_association(:clients) reflection.klass # => Client (class) Firm.columns # Returns an array of column descriptors for the firms table {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] * Database abstraction through simple adapters. # connect to SQLite3 ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'dbfile.sqlite3') # connect to MySQL with authentication ActiveRecord::Base.establish_connection( adapter: 'mysql2', host: 'localhost', username: 'me', password: 'secret', database: 'activerecord' ) {Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. * Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]. ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) ActiveRecord::Base.logger = Log4r::Logger.new('Application Log') * Database agnostic schema management with Migrations. class AddSystemSettings < ActiveRecord::Migration def up create_table :system_settings do |t| t.string :name t.string :label t.text :value t.string :type t.integer :position end SystemSetting.create name: 'notice', label: 'Use notice?', value: 1 end def down drop_table :system_settings end end {Learn more}[link:classes/ActiveRecord/Migration.html] == Philosophy Active Record is an implementation of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same name described by Martin Fowler: "An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data." Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is object-relational mapping. The prime directive for this mapping has been to minimize the amount of code needed to build a real-world domain model. This is made possible by relying on a number of conventions that make it easy for Active Record to infer complex relations and structures from a minimal amount of explicit direction. Convention over Configuration: * No XML files! * Lots of reflection and run-time extension * Magic is not inherently a bad word Admit the Database: * Lets you drop down to SQL for odd cases and performance * Doesn't attempt to duplicate or replace data definitions == Download and installation The latest version of Active Record can be installed with RubyGems: % [sudo] gem install activerecord Source code can be downloaded as part of the Rails project on GitHub: * https://github.com/rails/rails/tree/4-2-stable/activerecord == License Active Record is released under the MIT license: * http://www.opensource.org/licenses/MIT == Support API documentation is at: * http://api.rubyonrails.org Bug reports can be filed for the Ruby on Rails project here: * https://github.com/rails/rails/issues Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core rails-4.2.6/activerecord/RUNNING_UNIT_TESTS.rdoc000066400000000000000000000024521266740050600211320ustar00rootroot00000000000000== Setup If you don't have an environment for running tests, read http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#setting-up-a-development-environment == Running the Tests To run a specific test: $ ruby -Itest test/cases/base_test.rb -n method_name To run a set of tests: $ ruby -Itest test/cases/base_test.rb You can also run tests that depend upon a specific database backend. For example: $ bundle exec rake test_sqlite3 Simply executing bundle exec rake test is equivalent to the following: $ bundle exec rake test_mysql $ bundle exec rake test_mysql2 $ bundle exec rake test_postgresql $ bundle exec rake test_sqlite3 $ bundle exec rake test_sqlite3_mem There should be tests available for each database backend listed in the {Config File}[rdoc-label:label-Config+File]. (the exact set of available tests is defined in +Rakefile+) == Config File If +test/config.yml+ is present, then its parameters are obeyed; otherwise, the parameters in +test/config.example.yml+ are. You can override the +connections:+ parameter in either file using the +ARCONN+ (Active Record CONNection) environment variable: $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb You can specify a custom location for the config file using the +ARCONFIG+ environment variable. rails-4.2.6/activerecord/Rakefile000066400000000000000000000130111266740050600167560ustar00rootroot00000000000000require 'rake/testtask' require 'rubygems/package_task' require File.expand_path(File.dirname(__FILE__)) + "/test/config" require File.expand_path(File.dirname(__FILE__)) + "/test/support/config" def run_without_aborting(*tasks) errors = [] tasks.each do |task| begin Rake::Task[task].invoke rescue Exception errors << task end end abort "Errors running #{errors.join(', ')}" if errors.any? end desc 'Run mysql, mysql2, sqlite, and postgresql tests by default' task :default => :test desc 'Run mysql, mysql2, sqlite, and postgresql tests' task :test do tasks = defined?(JRUBY_VERSION) ? %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) : %w(test_mysql test_mysql2 test_sqlite3 test_postgresql) run_without_aborting(*tasks) end namespace :test do task :isolated do tasks = defined?(JRUBY_VERSION) ? %w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) : %w(isolated_test_mysql isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql) run_without_aborting(*tasks) end end desc 'Build MySQL and PostgreSQL test databases' namespace :db do task :create => ['db:mysql:build', 'db:postgresql:build'] task :drop => ['db:mysql:drop', 'db:postgresql:drop'] end %w( mysql mysql2 postgresql sqlite3 sqlite3_mem db2 oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| namespace :test do Rake::TestTask.new(adapter => "#{adapter}:env") { |t| adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] t.libs << 'test' t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { |x| x =~ /\/adapters\// } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort t.warning = true t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) } namespace :isolated do task adapter => "#{adapter}:env" do adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] puts [adapter, adapter_short].inspect (Dir["test/cases/**/*_test.rb"].reject { |x| x =~ /\/adapters\// } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| sh(Gem.ruby, '-w' ,"-Itest", file) end or raise "Failures" end end end namespace adapter do task :test => "test_#{adapter}" task :isolated_test => "isolated_test_#{adapter}" # Set the connection environment for the adapter task(:env) { ENV['ARCONN'] = adapter } end # Make sure the adapter test evaluates the env setting task task "test_#{adapter}" => ["#{adapter}:env", "test:#{adapter}"] task "isolated_test_#{adapter}" => ["#{adapter}:env", "test:isolated:#{adapter}"] end rule '.sqlite3' do |t| sh %Q{sqlite3 "#{t.name}" "create table a (a integer); drop table a;"} end task :test_sqlite3 => [ 'test/fixtures/fixture_database.sqlite3', 'test/fixtures/fixture_database_2.sqlite3' ] namespace :db do namespace :mysql do desc 'Build the MySQL test databases' task :build do config = ARTest.config['connections']['mysql'] %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") end desc 'Drop the MySQL test databases' task :drop do config = ARTest.config['connections']['mysql'] %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} ) %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} ) end desc 'Rebuild the MySQL test databases' task :rebuild => [:drop, :build] end namespace :postgresql do desc 'Build the PostgreSQL test databases' task :build do config = ARTest.config['connections']['postgresql'] %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} ) %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} ) # prepare hstore if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0" puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html" end end desc 'Drop the PostgreSQL test databases' task :drop do config = ARTest.config['connections']['postgresql'] %x( dropdb #{config['arunit']['database']} ) %x( dropdb #{config['arunit2']['database']} ) end desc 'Rebuild the PostgreSQL test databases' task :rebuild => [:drop, :build] end end task :build_mysql_databases => 'db:mysql:build' task :drop_mysql_databases => 'db:mysql:drop' task :rebuild_mysql_databases => 'db:mysql:rebuild' task :build_postgresql_databases => 'db:postgresql:build' task :drop_postgresql_databases => 'db:postgresql:drop' task :rebuild_postgresql_databases => 'db:postgresql:rebuild' task :lines do load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics' files = FileList["lib/active_record/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end spec = eval(File.read('activerecord.gemspec')) Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end # Publishing ------------------------------------------------------ desc "Release to rubygems" task :release => :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define Rake::Task['gem:push'].invoke end rails-4.2.6/activerecord/activerecord.gemspec000066400000000000000000000020051266740050600213310ustar00rootroot00000000000000version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'activerecord' s.version = version s.summary = 'Object-relational mapper framework (part of Rails).' s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.' s.required_ruby_version = '>= 1.9.3' s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*'] s.require_path = 'lib' s.extra_rdoc_files = %w(README.rdoc) s.rdoc_options.concat ['--main', 'README.rdoc'] s.add_dependency 'activesupport', version s.add_dependency 'activemodel', version s.add_dependency 'arel', '~> 6.0' end rails-4.2.6/activerecord/examples/000077500000000000000000000000001266740050600171335ustar00rootroot00000000000000rails-4.2.6/activerecord/examples/.gitignore000066400000000000000000000000201266740050600211130ustar00rootroot00000000000000performance.sql rails-4.2.6/activerecord/examples/performance.rb000066400000000000000000000113121266740050600217570ustar00rootroot00000000000000require File.expand_path('../../../load_paths', __FILE__) require "active_record" require 'benchmark/ips' TIME = (ENV['BENCHMARK_TIME'] || 20).to_i RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i conn = { adapter: 'sqlite3', database: ':memory:' } ActiveRecord::Base.establish_connection(conn) class User < ActiveRecord::Base connection.create_table :users, force: true do |t| t.string :name, :email t.timestamps end has_many :exhibits end class Exhibit < ActiveRecord::Base connection.create_table :exhibits, force: true do |t| t.belongs_to :user t.string :name t.text :notes t.timestamps end belongs_to :user def look; attributes end def feel; look; user.name end def self.with_name where("name IS NOT NULL") end def self.with_notes where("notes IS NOT NULL") end def self.look(exhibits) exhibits.each { |e| e.look } end def self.feel(exhibits) exhibits.each { |e| e.feel } end end def progress_bar(int); print "." if (int%100).zero? ; end puts 'Generating data...' module ActiveRecord class Faker LOREM = %Q{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem. Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim, tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae, varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero. Praesent varius tincidunt commodo}.split def self.name LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' ' end def self.email LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join('@') + ".com" end end end # pre-compute the insert statements and fake data compilation, # so the benchmarks below show the actual runtime for the execute # method, minus the setup steps # Using the same paragraph for all exhibits because it is very slow # to generate unique paragraphs for all exhibits. notes = ActiveRecord::Faker::LOREM.join ' ' today = Date.today puts "Inserting #{RECORDS} users and exhibits..." RECORDS.times do |record| user = User.create( created_at: today, name: ActiveRecord::Faker.name, email: ActiveRecord::Faker.email ) Exhibit.create( created_at: today, name: ActiveRecord::Faker.name, user: user, notes: notes ) progress_bar(record) end puts "Done!\n" Benchmark.ips(TIME) do |x| ar_obj = Exhibit.find(1) attrs = { name: 'sam' } attrs_first = { name: 'sam' } attrs_second = { name: 'tom' } exhibit = { name: ActiveRecord::Faker.name, notes: notes, created_at: Date.today } x.report("Model#id") do ar_obj.id end x.report 'Model.new (instantiation)' do Exhibit.new end x.report 'Model.new (setting attributes)' do Exhibit.new(attrs) end x.report 'Model.first' do Exhibit.first.look end x.report 'Model.take' do Exhibit.take end x.report("Model.all limit(100)") do Exhibit.look Exhibit.limit(100) end x.report("Model.all take(100)") do Exhibit.look Exhibit.take(100) end x.report "Model.all limit(100) with relationship" do Exhibit.feel Exhibit.limit(100).includes(:user) end x.report "Model.all limit(10,000)" do Exhibit.look Exhibit.limit(10000) end x.report 'Model.named_scope' do Exhibit.limit(10).with_name.with_notes end x.report 'Model.create' do Exhibit.create(exhibit) end x.report 'Resource#attributes=' do e = Exhibit.new(attrs_first) e.attributes = attrs_second end x.report 'Resource#update' do Exhibit.first.update(name: 'bob') end x.report 'Resource#destroy' do Exhibit.first.destroy end x.report 'Model.transaction' do Exhibit.transaction { Exhibit.new } end x.report 'Model.find(id)' do User.find(1) end x.report 'Model.find_by_sql' do Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first end x.report "Model.log" do Exhibit.connection.send(:log, "hello", "world") {} end x.report "AR.execute(query)" do ActiveRecord::Base.connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}") end end rails-4.2.6/activerecord/examples/simple.rb000066400000000000000000000005531266740050600207540ustar00rootroot00000000000000require File.expand_path('../../../load_paths', __FILE__) require 'active_record' class Person < ActiveRecord::Base establish_connection adapter: 'sqlite3', database: 'foobar.db' connection.create_table table_name, force: true do |t| t.string :name end end bob = Person.create!(name: 'bob') puts Person.all.inspect bob.destroy puts Person.all.inspect rails-4.2.6/activerecord/lib/000077500000000000000000000000001266740050600160635ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record.rb000066400000000000000000000112471266740050600212260ustar00rootroot00000000000000#-- # Copyright (c) 2004-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ require 'active_support' require 'active_support/rails' require 'active_model' require 'arel' require 'active_record/version' require 'active_record/attribute_set' module ActiveRecord extend ActiveSupport::Autoload autoload :Attribute autoload :Base autoload :Callbacks autoload :Core autoload :ConnectionHandling autoload :CounterCache autoload :DynamicMatchers autoload :Enum autoload :Explain autoload :Inheritance autoload :Integration autoload :LegacyYamlAdapter autoload :Migration autoload :Migrator, 'active_record/migration' autoload :ModelSchema autoload :NestedAttributes autoload :NoTouching autoload :Persistence autoload :QueryCache autoload :Querying autoload :ReadonlyAttributes autoload :RecordInvalid, 'active_record/validations' autoload :Reflection autoload :RuntimeRegistry autoload :Sanitization autoload :Schema autoload :SchemaDumper autoload :SchemaMigration autoload :Scoping autoload :Serialization autoload :StatementCache autoload :Store autoload :Timestamp autoload :Transactions autoload :Translation autoload :Validations eager_autoload do autoload :ActiveRecordError, 'active_record/errors' autoload :ConnectionNotEstablished, 'active_record/errors' autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter' autoload :Aggregations autoload :Associations autoload :AttributeAssignment autoload :AttributeMethods autoload :AutosaveAssociation autoload :Relation autoload :AssociationRelation autoload :NullRelation autoload_under 'relation' do autoload :QueryMethods autoload :FinderMethods autoload :Calculations autoload :PredicateBuilder autoload :SpawnMethods autoload :Batches autoload :Delegation end autoload :Result end module Coders autoload :YAMLColumn, 'active_record/coders/yaml_column' autoload :JSON, 'active_record/coders/json' end module AttributeMethods extend ActiveSupport::Autoload eager_autoload do autoload :BeforeTypeCast autoload :Dirty autoload :PrimaryKey autoload :Query autoload :Read autoload :TimeZoneConversion autoload :Write autoload :Serialization end end module Locking extend ActiveSupport::Autoload eager_autoload do autoload :Optimistic autoload :Pessimistic end end module ConnectionAdapters extend ActiveSupport::Autoload eager_autoload do autoload :AbstractAdapter autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool" end end module Scoping extend ActiveSupport::Autoload eager_autoload do autoload :Named autoload :Default end end module Tasks extend ActiveSupport::Autoload autoload :DatabaseTasks autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks' autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks' autoload :PostgreSQLDatabaseTasks, 'active_record/tasks/postgresql_database_tasks' end autoload :TestFixtures, 'active_record/fixtures' def self.eager_load! super ActiveRecord::Locking.eager_load! ActiveRecord::Scoping.eager_load! ActiveRecord::Associations.eager_load! ActiveRecord::AttributeMethods.eager_load! ActiveRecord::ConnectionAdapters.eager_load! end end ActiveSupport.on_load(:active_record) do Arel::Table.engine = self end ActiveSupport.on_load(:i18n) do I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' end rails-4.2.6/activerecord/lib/active_record/000077500000000000000000000000001266740050600206745ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/aggregations.rb000066400000000000000000000326101266740050600236750ustar00rootroot00000000000000module ActiveRecord # = Active Record Aggregations module Aggregations # :nodoc: extend ActiveSupport::Concern def clear_aggregation_cache #:nodoc: @aggregation_cache.clear if persisted? end # Active Record implements aggregation through a macro-like class method called +composed_of+ # for representing attributes as value objects. It expresses relationships like "Account [is] # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call # to the macro adds a description of how the value objects are created from the attributes of # the entity object (when the entity is initialized either as a new object or from finding an # existing object) and how it can be turned back into attributes (when the entity is saved to # the database). # # class Customer < ActiveRecord::Base # composed_of :balance, class_name: "Money", mapping: %w(balance amount) # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] # end # # The customer class now has the following methods to manipulate the value objects: # * Customer#balance, Customer#balance=(money) # * Customer#address, Customer#address=(address) # # These methods will operate with value objects like the ones described below: # # class Money # include Comparable # attr_reader :amount, :currency # EXCHANGE_RATES = { "USD_TO_DKK" => 6 } # # def initialize(amount, currency = "USD") # @amount, @currency = amount, currency # end # # def exchange_to(other_currency) # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor # Money.new(exchanged_amount, other_currency) # end # # def ==(other_money) # amount == other_money.amount && currency == other_money.currency # end # # def <=>(other_money) # if currency == other_money.currency # amount <=> other_money.amount # else # amount <=> other_money.exchange_to(currency).amount # end # end # end # # class Address # attr_reader :street, :city # def initialize(street, city) # @street, @city = street, city # end # # def close_to?(other_address) # city == other_address.city # end # # def ==(other_address) # city == other_address.city && street == other_address.street # end # end # # Now it's possible to access attributes from the database through the value objects instead. If # you choose to name the composition the same as the attribute's name, it will be the only way to # access that attribute. That's the case with our +balance+ attribute. You interact with the value # objects just like you would with any other attribute: # # customer.balance = Money.new(20) # sets the Money value object and the attribute # customer.balance # => Money value object # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK") # customer.balance > Money.new(10) # => true # customer.balance == Money.new(20) # => true # customer.balance < Money.new(5) # => false # # Value objects can also be composed of multiple attributes, such as the case of Address. The order # of the mappings will determine the order of the parameters. # # customer.address_street = "Hyancintvej" # customer.address_city = "Copenhagen" # customer.address # => Address.new("Hyancintvej", "Copenhagen") # # customer.address_street = "Vesterbrogade" # customer.address # => Address.new("Hyancintvej", "Copenhagen") # customer.clear_aggregation_cache # customer.address # => Address.new("Vesterbrogade", "Copenhagen") # # customer.address = Address.new("May Street", "Chicago") # customer.address_street # => "May Street" # customer.address_city # => "Chicago" # # == Writing value objects # # Value objects are immutable and interchangeable objects that represent a given value, such as # a Money object representing $5. Two Money objects both representing $5 should be equal (through # methods such as == and <=> from Comparable if ranking makes sense). This is # unlike entity objects where equality is determined by identity. An entity class such as Customer can # easily have two different objects that both have an address on Hyancintvej. Entity identity is # determined by object or relational unique identifiers (such as primary keys). Normal # ActiveRecord::Base classes are entity objects. # # It's also important to treat the value objects as immutable. Don't allow the Money object to have # its amount changed after creation. Create a new Money object with the new value instead. The # Money#exchange_to method is an example of this. It returns a new value object instead of changing # its own values. Active Record won't persist value objects that have been changed through means # other than the writer method. # # The immutable requirement is enforced by Active Record by freezing any object assigned as a value # object. Attempting to change it afterwards will result in a RuntimeError. # # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable # # == Custom constructors and converters # # By default value objects are initialized by calling the new constructor of the value # class passing each of the mapped attributes, in the order specified by the :mapping # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows # a custom constructor to be specified. # # When a new value is assigned to the value object, the default assumption is that the new value # is an instance of the value class. Specifying a custom converter allows the new value to be automatically # converted to an instance of value class if necessary. # # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be # aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html). # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. # New values can be assigned to the value object using either another NetAddr::CIDR object, a string # or an array. The :constructor and :converter options can be used to meet # these requirements: # # class NetworkResource < ActiveRecord::Base # composed_of :cidr, # class_name: 'NetAddr::CIDR', # mapping: [ %w(network_address network), %w(cidr_range bits) ], # allow_nil: true, # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } # end # # # This calls the :constructor # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24) # # # These assignments will both use the :converter # network_resource.cidr = [ '192.168.2.1', 8 ] # network_resource.cidr = '192.168.0.1/24' # # # This assignment won't use the :converter as the value is already an instance of the value class # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8') # # # Saving and then reloading will use the :constructor on reload # network_resource.save # network_resource.reload # # == Finding records by a value object # # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database # by specifying an instance of the value object in the conditions hash. The following example # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": # # Customer.where(balance: Money.new(20, "USD")) # module ClassMethods # Adds reader and writer methods for manipulating a value object: # composed_of :address adds address and address=(new_address) methods. # # Options are: # * :class_name - Specifies the class name of the association. Use it only if that name # can't be inferred from the part id. So composed_of :address will by default be linked # to the Address class, but if the real class name is CompanyAddress, you'll have to specify it # with this option. # * :mapping - Specifies the mapping of entity attributes to attributes of the value # object. Each mapping is represented as an array where the first item is the name of the # entity attribute and the second item is the name of the attribute in the value object. The # order in which mappings are defined determines the order in which attributes are sent to the # value class constructor. # * :allow_nil - Specifies that the value object will not be instantiated when all mapped # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all # mapped attributes. # This defaults to +false+. # * :constructor - A symbol specifying the name of the constructor method or a Proc that # is called to initialize the value object. The constructor is passed all of the mapped attributes, # in the order that they are defined in the :mapping option, as arguments and uses them # to instantiate a :class_name object. # The default is :new. # * :converter - A symbol specifying the name of a class method of :class_name # or a Proc that is called when a new value is assigned to the value object. The converter is # passed the single value that is used in the assignment and is only called if the new value is # not an instance of :class_name. If :allow_nil is set to true, the converter # can return nil to skip the assignment. # # Option examples: # composed_of :temperature, mapping: %w(reading celsius) # composed_of :balance, class_name: "Money", mapping: %w(balance amount), # converter: Proc.new { |balance| balance.to_money } # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] # composed_of :gps_location # composed_of :gps_location, allow_nil: true # composed_of :ip_address, # class_name: 'IPAddr', # mapping: %w(ip to_i), # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } # def composed_of(part_id, options = {}) options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) name = part_id.id2name class_name = options[:class_name] || name.camelize mapping = options[:mapping] || [ name, name ] mapping = [ mapping ] unless mapping.first.is_a?(Array) allow_nil = options[:allow_nil] || false constructor = options[:constructor] || :new converter = options[:converter] reader_method(name, class_name, mapping, allow_nil, constructor) writer_method(name, class_name, mapping, allow_nil, converter) reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) Reflection.add_aggregate_reflection self, part_id, reflection end private def reader_method(name, class_name, mapping, allow_nil, constructor) define_method(name) do if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? }) attrs = mapping.collect {|key, _| _read_attribute(key)} object = constructor.respond_to?(:call) ? constructor.call(*attrs) : class_name.constantize.send(constructor, *attrs) @aggregation_cache[name] = object end @aggregation_cache[name] end end def writer_method(name, class_name, mapping, allow_nil, converter) define_method("#{name}=") do |part| klass = class_name.constantize if part.is_a?(Hash) part = klass.new(*part.values) end unless part.is_a?(klass) || converter.nil? || part.nil? part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) end if part.nil? && allow_nil mapping.each { |key, _| self[key] = nil } @aggregation_cache[name] = nil else mapping.each { |key, value| self[key] = part.send(value) } @aggregation_cache[name] = part.freeze end end end end end end rails-4.2.6/activerecord/lib/active_record/association_relation.rb000066400000000000000000000012531266740050600254330ustar00rootroot00000000000000module ActiveRecord class AssociationRelation < Relation def initialize(klass, table, association) super(klass, table) @association = association end def proxy_association @association end def ==(other) other == to_a end def build(*args, &block) scoping { @association.build(*args, &block) } end alias new build def create(*args, &block) scoping { @association.create(*args, &block) } end def create!(*args, &block) scoping { @association.create!(*args, &block) } end private def exec_queries super.each { |r| @association.set_inverse_instance r } end end end rails-4.2.6/activerecord/lib/active_record/associations.rb000066400000000000000000002633601266740050600237320ustar00rootroot00000000000000require 'active_support/core_ext/enumerable' require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/module/remove_method' require 'active_record/errors' module ActiveRecord class AssociationNotFoundError < ConfigurationError #:nodoc: def initialize(record, association_name) super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?") end end class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: def initialize(reflection, associated_class = nil) super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})") end end class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection) super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}") end end class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection, source_reflection) super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.") end end class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection) super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") end end class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection, source_reflection) super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.") end end class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection, through_reflection) super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.") end end class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection) super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") end end class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc: def initialize(reflection) through_reflection = reflection.through_reflection source_reflection_names = reflection.source_reflection_names source_associations = reflection.through_reflection.klass._reflections.keys super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => '. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?") end end class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.") end end class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.") end end class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.") end end class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.") end end class EagerLoadPolymorphicError < ActiveRecordError #:nodoc: def initialize(reflection) super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}") end end class ReadOnlyAssociation < ActiveRecordError #:nodoc: def initialize(reflection) super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.") end end # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations # (has_many, has_one) when there is at least 1 child associated instance. # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project class DeleteRestrictionError < ActiveRecordError #:nodoc: def initialize(name) super("Cannot delete record because of dependent #{name}") end end # See ActiveRecord::Associations::ClassMethods for documentation. module Associations # :nodoc: extend ActiveSupport::Autoload extend ActiveSupport::Concern # These classes will be loaded when associations are created. # So there is no need to eager load them. autoload :Association, 'active_record/associations/association' autoload :SingularAssociation, 'active_record/associations/singular_association' autoload :CollectionAssociation, 'active_record/associations/collection_association' autoload :ForeignAssociation, 'active_record/associations/foreign_association' autoload :CollectionProxy, 'active_record/associations/collection_proxy' autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association' autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association' autoload :HasManyAssociation, 'active_record/associations/has_many_association' autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association' autoload :HasOneAssociation, 'active_record/associations/has_one_association' autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association' autoload :ThroughAssociation, 'active_record/associations/through_association' module Builder #:nodoc: autoload :Association, 'active_record/associations/builder/association' autoload :SingularAssociation, 'active_record/associations/builder/singular_association' autoload :CollectionAssociation, 'active_record/associations/builder/collection_association' autoload :BelongsTo, 'active_record/associations/builder/belongs_to' autoload :HasOne, 'active_record/associations/builder/has_one' autoload :HasMany, 'active_record/associations/builder/has_many' autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many' end eager_autoload do autoload :Preloader, 'active_record/associations/preloader' autoload :JoinDependency, 'active_record/associations/join_dependency' autoload :AssociationScope, 'active_record/associations/association_scope' autoload :AliasTracker, 'active_record/associations/alias_tracker' end # Clears out the association cache. def clear_association_cache #:nodoc: @association_cache.clear if persisted? end # :nodoc: attr_reader :association_cache # Returns the association instance for the given name, instantiating it if it doesn't already exist def association(name) #:nodoc: association = association_instance_get(name) if association.nil? raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name) association = reflection.association_class.new(self, reflection) association_instance_set(name, association) end association end private # Returns the specified association instance if it responds to :loaded?, nil otherwise. def association_instance_get(name) @association_cache[name] end # Set the specified association instance. def association_instance_set(name, association) @association_cache[name] = association end # \Associations are a set of macro-like class methods for tying objects together through # foreign keys. They express relationships like "Project has one Project Manager" # or "Project belongs to a Portfolio". Each macro adds a number of methods to the # class which are specialized according to the collection or association symbol and the # options hash. It works much the same way as Ruby's own attr* # methods. # # class Project < ActiveRecord::Base # belongs_to :portfolio # has_one :project_manager # has_many :milestones # has_and_belongs_to_many :categories # end # # The project class now has the following methods (and more) to ease the traversal and # manipulation of its relationships: # * Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil? # * Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?, # * Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone), # Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id), # Project#milestones.build, Project#milestones.create # * Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1), # Project#categories.delete(category1), Project#categories.destroy(category1) # # === A word of warning # # Don't create associations that have the same name as instance methods of # ActiveRecord::Base. Since the association adds a method with that name to # its model, it will override the inherited method and break things. # For instance, +attributes+ and +connection+ would be bad choices for association names. # # == Auto-generated methods # See also Instance Public methods below for more details. # # === Singular associations (one-to-one) # | | belongs_to | # generated methods | belongs_to | :polymorphic | has_one # ----------------------------------+------------+--------------+--------- # other(force_reload=false) | X | X | X # other=(other) | X | X | X # build_other(attributes={}) | X | | X # create_other(attributes={}) | X | | X # create_other!(attributes={}) | X | | X # # ===Collection associations (one-to-many / many-to-many) # | | | has_many # generated methods | habtm | has_many | :through # ----------------------------------+-------+----------+---------- # others(force_reload=false) | X | X | X # others=(other,other,...) | X | X | X # other_ids | X | X | X # other_ids=(id,id,...) | X | X | X # others<< | X | X | X # others.push | X | X | X # others.concat | X | X | X # others.build(attributes={}) | X | X | X # others.create(attributes={}) | X | X | X # others.create!(attributes={}) | X | X | X # others.size | X | X | X # others.length | X | X | X # others.count | X | X | X # others.sum(*args) | X | X | X # others.empty? | X | X | X # others.clear | X | X | X # others.delete(other,other,...) | X | X | X # others.delete_all | X | X | X # others.destroy(other,other,...) | X | X | X # others.destroy_all | X | X | X # others.find(*args) | X | X | X # others.exists? | X | X | X # others.distinct | X | X | X # others.uniq | X | X | X # others.reset | X | X | X # # === Overriding generated methods # # Association methods are generated in a module that is included into the model class, # which allows you to easily override with your own methods and call the original # generated method with +super+. For example: # # class Car < ActiveRecord::Base # belongs_to :owner # belongs_to :old_owner # def owner=(new_owner) # self.old_owner = self.owner # super # end # end # # If your model class is Project, the module is # named Project::GeneratedFeatureMethods. The GeneratedFeatureMethods module is # included in the model class immediately after the (anonymous) generated attributes methods # module, meaning an association will override the methods for an attribute with the same name. # # == Cardinality and associations # # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many # relationships between models. Each model uses an association to describe its role in # the relation. The +belongs_to+ association is always used in the model that has # the foreign key. # # === One-to-one # # Use +has_one+ in the base, and +belongs_to+ in the associated model. # # class Employee < ActiveRecord::Base # has_one :office # end # class Office < ActiveRecord::Base # belongs_to :employee # foreign key - employee_id # end # # === One-to-many # # Use +has_many+ in the base, and +belongs_to+ in the associated model. # # class Manager < ActiveRecord::Base # has_many :employees # end # class Employee < ActiveRecord::Base # belongs_to :manager # foreign key - manager_id # end # # === Many-to-many # # There are two ways to build a many-to-many relationship. # # The first way uses a +has_many+ association with the :through option and a join model, so # there are two stages of associations. # # class Assignment < ActiveRecord::Base # belongs_to :programmer # foreign key - programmer_id # belongs_to :project # foreign key - project_id # end # class Programmer < ActiveRecord::Base # has_many :assignments # has_many :projects, through: :assignments # end # class Project < ActiveRecord::Base # has_many :assignments # has_many :programmers, through: :assignments # end # # For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table # that has no corresponding model or primary key. # # class Programmer < ActiveRecord::Base # has_and_belongs_to_many :projects # foreign keys in the join table # end # class Project < ActiveRecord::Base # has_and_belongs_to_many :programmers # foreign keys in the join table # end # # Choosing which way to build a many-to-many relationship is not always simple. # If you need to work with the relationship model as its own entity, # use has_many :through. Use +has_and_belongs_to_many+ when working with legacy schemas or when # you never work directly with the relationship itself. # # == Is it a +belongs_to+ or +has_one+ association? # # Both express a 1-1 relationship. The difference is mostly where to place the foreign # key, which goes on the table for the class declaring the +belongs_to+ relationship. # # class User < ActiveRecord::Base # # I reference an account. # belongs_to :account # end # # class Account < ActiveRecord::Base # # One user references me. # has_one :user # end # # The tables for these classes could look something like: # # CREATE TABLE users ( # id int(11) NOT NULL auto_increment, # account_id int(11) default NULL, # name varchar default NULL, # PRIMARY KEY (id) # ) # # CREATE TABLE accounts ( # id int(11) NOT NULL auto_increment, # name varchar default NULL, # PRIMARY KEY (id) # ) # # == Unsaved objects and associations # # You can manipulate objects and associations before they are saved to the database, but # there is some special behavior you should be aware of, mostly involving the saving of # associated objects. # # You can set the :autosave option on a has_one, belongs_to, # has_many, or has_and_belongs_to_many association. Setting it # to +true+ will _always_ save the members, whereas setting it to +false+ will # _never_ save the members. More details about :autosave option is available at # AutosaveAssociation. # # === One-to-one associations # # * Assigning an object to a +has_one+ association automatically saves that object and # the object being replaced (if there is one), in order to update their foreign # keys - except if the parent object is unsaved (new_record? == true). # * If either of these saves fail (due to one of the objects being invalid), an # ActiveRecord::RecordNotSaved exception is raised and the assignment is # cancelled. # * If you wish to assign an object to a +has_one+ association without saving it, # use the build_association method (documented below). The object being # replaced will still be saved to update its foreign key. # * Assigning an object to a +belongs_to+ association does not save the object, since # the foreign key field belongs on the parent. It does not save the parent either. # # === Collections # # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically # saves that object, except if the parent object (the owner of the collection) is not yet # stored in the database. # * If saving any of the objects being added to a collection (via push or similar) # fails, then push returns +false+. # * If saving fails while replacing the collection (via association=), an # ActiveRecord::RecordNotSaved exception is raised and the assignment is # cancelled. # * You can add an object to a collection without automatically saving it by using the # collection.build method (documented below). # * All unsaved (new_record? == true) members of the collection are automatically # saved when the parent is saved. # # == Customizing the query # # \Associations are built from Relations, and you can use the Relation syntax # to customize them. For example, to add a condition: # # class Blog < ActiveRecord::Base # has_many :published_posts, -> { where published: true }, class_name: 'Post' # end # # Inside the -> { ... } block you can use all of the usual Relation methods. # # === Accessing the owner object # # Sometimes it is useful to have access to the owner object when building the query. The owner # is passed as a parameter to the block. For example, the following association would find all # events that occur on the user's birthday: # # class User < ActiveRecord::Base # has_many :birthday_events, ->(user) { where starts_on: user.birthday }, class_name: 'Event' # end # # Note: Joining, eager loading and preloading of these associations is not fully possible. # These operations happen before instance creation and the scope will be called with a +nil+ argument. # This can lead to unexpected behavior and is deprecated. # # == Association callbacks # # Similar to the normal callbacks that hook into the life cycle of an Active Record object, # you can also define callbacks that get triggered when you add an object to or remove an # object from an association collection. # # class Project # has_and_belongs_to_many :developers, after_add: :evaluate_velocity # # def evaluate_velocity(developer) # ... # end # end # # It's possible to stack callbacks by passing them as an array. Example: # # class Project # has_and_belongs_to_many :developers, # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] # end # # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+. # # If any of the +before_add+ callbacks throw an exception, the object will not be # added to the collection. # # Similarly, if any of the +before_remove+ callbacks throw an exception, the object # will not be removed from the collection. # # == Association extensions # # The proxy objects that control the access to associations can be extended through anonymous # modules. This is especially beneficial for adding new finders, creators, and other # factory-type methods that are only used as part of this association. # # class Account < ActiveRecord::Base # has_many :people do # def find_or_create_by_name(name) # first_name, last_name = name.split(" ", 2) # find_or_create_by(first_name: first_name, last_name: last_name) # end # end # end # # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") # person.first_name # => "David" # person.last_name # => "Heinemeier Hansson" # # If you need to share the same extensions between many associations, you can use a named # extension module. # # module FindOrCreateByNameExtension # def find_or_create_by_name(name) # first_name, last_name = name.split(" ", 2) # find_or_create_by(first_name: first_name, last_name: last_name) # end # end # # class Account < ActiveRecord::Base # has_many :people, -> { extending FindOrCreateByNameExtension } # end # # class Company < ActiveRecord::Base # has_many :people, -> { extending FindOrCreateByNameExtension } # end # # Some extensions can only be made to work with knowledge of the association's internals. # Extensions can access relevant state using the following methods (where +items+ is the # name of the association): # # * record.association(:items).owner - Returns the object the association is part of. # * record.association(:items).reflection - Returns the reflection object that describes the association. # * record.association(:items).target - Returns the associated object for +belongs_to+ and +has_one+, or # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+. # # However, inside the actual extension code, you will not have access to the record as # above. In this case, you can access proxy_association. For example, # record.association(:items) and record.items.proxy_association will return # the same object, allowing you to make calls like proxy_association.owner inside # association extensions. # # == Association Join Models # # Has Many associations can be configured with the :through option to use an # explicit join model to retrieve the data. This operates similarly to a # +has_and_belongs_to_many+ association. The advantage is that you're able to add validations, # callbacks, and extra attributes on the join model. Consider the following schema: # # class Author < ActiveRecord::Base # has_many :authorships # has_many :books, through: :authorships # end # # class Authorship < ActiveRecord::Base # belongs_to :author # belongs_to :book # end # # @author = Author.first # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to # @author.books # selects all books by using the Authorship join model # # You can also go through a +has_many+ association on the join model: # # class Firm < ActiveRecord::Base # has_many :clients # has_many :invoices, through: :clients # end # # class Client < ActiveRecord::Base # belongs_to :firm # has_many :invoices # end # # class Invoice < ActiveRecord::Base # belongs_to :client # end # # @firm = Firm.first # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm # @firm.invoices # selects all invoices by going through the Client join model # # Similarly you can go through a +has_one+ association on the join model: # # class Group < ActiveRecord::Base # has_many :users # has_many :avatars, through: :users # end # # class User < ActiveRecord::Base # belongs_to :group # has_one :avatar # end # # class Avatar < ActiveRecord::Base # belongs_to :user # end # # @group = Group.first # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group # @group.avatars # selects all avatars by going through the User join model. # # An important caveat with going through +has_one+ or +has_many+ associations on the # join model is that these associations are *read-only*. For example, the following # would not work following the previous example: # # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around # @group.avatars.delete(@group.avatars.last) # so would this # # == Setting Inverses # # If you are using a +belongs_to+ on the join model, it is a good idea to set the # :inverse_of option on the +belongs_to+, which will mean that the following example # works correctly (where tags is a +has_many+ :through association): # # @post = Post.first # @tag = @post.tags.build name: "ruby" # @tag.save # # The last line ought to save the through record (a Taggable). This will only work if the # :inverse_of is set: # # class Taggable < ActiveRecord::Base # belongs_to :post # belongs_to :tag, inverse_of: :taggings # end # # If you do not set the :inverse_of record, the association will # do its best to match itself up with the correct inverse. Automatic # inverse detection only works on has_many, has_one, and # belongs_to associations. # # Extra options on the associations, as defined in the # AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS constant, will # also prevent the association's inverse from being found automatically. # # The automatic guessing of the inverse association uses a heuristic based # on the name of the class, so it may not work for all associations, # especially the ones with non-standard names. # # You can turn off the automatic detection of inverse associations by setting # the :inverse_of option to false like so: # # class Taggable < ActiveRecord::Base # belongs_to :tag, inverse_of: false # end # # == Nested \Associations # # You can actually specify *any* association with the :through option, including an # association which has a :through option itself. For example: # # class Author < ActiveRecord::Base # has_many :posts # has_many :comments, through: :posts # has_many :commenters, through: :comments # end # # class Post < ActiveRecord::Base # has_many :comments # end # # class Comment < ActiveRecord::Base # belongs_to :commenter # end # # @author = Author.first # @author.commenters # => People who commented on posts written by the author # # An equivalent way of setting up this association this would be: # # class Author < ActiveRecord::Base # has_many :posts # has_many :commenters, through: :posts # end # # class Post < ActiveRecord::Base # has_many :comments # has_many :commenters, through: :comments # end # # class Comment < ActiveRecord::Base # belongs_to :commenter # end # # When using a nested association, you will not be able to modify the association because there # is not enough information to know what modification to make. For example, if you tried to # add a Commenter in the example above, there would be no way to tell how to set up the # intermediate Post and Comment objects. # # == Polymorphic \Associations # # Polymorphic associations on models are not restricted on what types of models they # can be associated with. Rather, they specify an interface that a +has_many+ association # must adhere to. # # class Asset < ActiveRecord::Base # belongs_to :attachable, polymorphic: true # end # # class Post < ActiveRecord::Base # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. # end # # @asset.attachable = @post # # This works by using a type column in addition to a foreign key to specify the associated # record. In the Asset example, you'd need an +attachable_id+ integer column and an # +attachable_type+ string column. # # Using polymorphic associations in combination with single table inheritance (STI) is # a little tricky. In order for the associations to work as expected, ensure that you # store the base model for the STI models in the type column of the polymorphic # association. To continue with the asset example above, suppose there are guest posts # and member posts that use the posts table for STI. In this case, there must be a +type+ # column in the posts table. # # Note: The attachable_type= method is being called when assigning an +attachable+. # The +class_name+ of the +attachable+ is passed as a String. # # class Asset < ActiveRecord::Base # belongs_to :attachable, polymorphic: true # # def attachable_type=(class_name) # super(class_name.constantize.base_class.to_s) # end # end # # class Post < ActiveRecord::Base # # because we store "Post" in attachable_type now dependent: :destroy will work # has_many :assets, as: :attachable, dependent: :destroy # end # # class GuestPost < Post # end # # class MemberPost < Post # end # # == Caching # # All of the methods are built on a simple caching principle that will keep the result # of the last query around unless specifically instructed not to. The cache is even # shared across methods to make it even cheaper to use the macro-added methods without # worrying too much about performance at the first go. # # project.milestones # fetches milestones from the database # project.milestones.size # uses the milestone cache # project.milestones.empty? # uses the milestone cache # project.milestones(true).size # fetches milestones from the database # project.milestones # uses the milestone cache # # == Eager loading of associations # # Eager loading is a way to find objects of a certain class and a number of named associations. # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100 # posts that each need to display their author triggers 101 database queries. Through the # use of eager loading, the number of queries will be reduced from 101 to 2. # # class Post < ActiveRecord::Base # belongs_to :author # has_many :comments # end # # Consider the following loop using the class above: # # Post.all.each do |post| # puts "Post: " + post.title # puts "Written by: " + post.author.name # puts "Last comment on: " + post.comments.first.created_on # end # # To iterate over these one hundred posts, we'll generate 201 database queries. Let's # first just optimize it for retrieving the author: # # Post.includes(:author).each do |post| # # This references the name of the +belongs_to+ association that also used the :author # symbol. After loading the posts, find will collect the +author_id+ from each one and load # all the referenced authors with one query. Doing so will cut down the number of queries # from 201 to 102. # # We can improve upon the situation further by referencing both associations in the finder with: # # Post.includes(:author, :comments).each do |post| # # This will load all comments with a single query. This reduces the total number of queries # to 3. In general, the number of queries will be 1 plus the number of associations # named (except if some of the associations are polymorphic +belongs_to+ - see below). # # To include a deep hierarchy of associations, use a hash: # # Post.includes(:author, { comments: { author: :gravatar } }).each do |post| # # The above code will load all the comments and all of their associated # authors and gravatars. You can mix and match any combination of symbols, # arrays, and hashes to retrieve the associations you want to load. # # All of this power shouldn't fool you into thinking that you can pull out huge amounts # of data with no performance penalty just because you've reduced the number of queries. # The database still needs to send all the data to Active Record and it still needs to # be processed. So it's no catch-all for performance problems, but it's a great way to # cut down on the number of queries in a situation as the one described above. # # Since only one table is loaded at a time, conditions or orders cannot reference tables # other than the main one. If this is the case, Active Record falls back to the previously # used LEFT OUTER JOIN based strategy. For example: # # Post.includes([:author, :comments]).where(['comments.approved = ?', true]) # # This will result in a single SQL query with joins along the lines of: # LEFT OUTER JOIN comments ON comments.post_id = posts.id and # LEFT OUTER JOIN authors ON authors.id = posts.author_id. Note that using conditions # like this can have unintended consequences. # In the above example posts with no approved comments are not returned at all, because # the conditions apply to the SQL statement as a whole and not just to the association. # # You must disambiguate column references for this fallback to happen, for example # order: "author.name DESC" will work but order: "name DESC" will not. # # If you want to load all posts (including posts with no approved comments) then write # your own LEFT OUTER JOIN query using ON # # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'") # # In this case it is usually more natural to include an association which has conditions defined on it: # # class Post < ActiveRecord::Base # has_many :approved_comments, -> { where approved: true }, class_name: 'Comment' # end # # Post.includes(:approved_comments) # # This will load posts and eager load the +approved_comments+ association, which contains # only those comments that have been approved. # # If you eager load an association with a specified :limit option, it will be ignored, # returning all the associated objects: # # class Picture < ActiveRecord::Base # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment' # end # # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. # # Eager loading is supported with polymorphic associations. # # class Address < ActiveRecord::Base # belongs_to :addressable, polymorphic: true # end # # A call that tries to eager load the addressable model # # Address.includes(:addressable) # # This will execute one query to load the addresses and load the addressables with one # query per addressable type. # For example if all the addressables are either of class Person or Company then a total # of 3 queries will be executed. The list of addressable types to load is determined on # the back of the addresses loaded. This is not supported if Active Record has to fallback # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. # The reason is that the parent model's type is a column value so its corresponding table # name cannot be put in the +FROM+/+JOIN+ clauses of that query. # # == Table Aliasing # # Active Record uses table aliasing in the case that a table is referenced multiple times # in a join. If a table is referenced only once, the standard table name is used. The # second time, the table is aliased as #{reflection_name}_#{parent_table_name}. # Indexes are appended for any more successive uses of the table name. # # Post.joins(:comments) # # => SELECT ... FROM posts INNER JOIN comments ON ... # Post.joins(:special_comments) # STI # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts # # Acts as tree example: # # TreeMixin.joins(:children) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # TreeMixin.joins(children: :parent) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # INNER JOIN parents_mixins ... # TreeMixin.joins(children: {parent: :children}) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # INNER JOIN parents_mixins ... # INNER JOIN mixins childrens_mixins_2 # # Has and Belongs to Many join tables use the same idea, but add a _join suffix: # # Post.joins(:categories) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # Post.joins(categories: :posts) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories # Post.joins(categories: {posts: :categories}) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 # # If you wish to specify your own custom joins using joins method, those table # names will take precedence over the eager associations: # # Post.joins(:comments).joins("inner join comments ...") # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... # Post.joins(:comments, :special_comments).joins("inner join comments ...") # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ... # INNER JOIN comments special_comments_posts ... # INNER JOIN comments ... # # Table aliases are automatically truncated according to the maximum length of table identifiers # according to the specific database. # # == Modules # # By default, associations will look for objects within the current module scope. Consider: # # module MyApplication # module Business # class Firm < ActiveRecord::Base # has_many :clients # end # # class Client < ActiveRecord::Base; end # end # end # # When Firm#clients is called, it will in turn call # MyApplication::Business::Client.find_all_by_firm_id(firm.id). # If you want to associate with a class in another module scope, this can be done by # specifying the complete class name. # # module MyApplication # module Business # class Firm < ActiveRecord::Base; end # end # # module Billing # class Account < ActiveRecord::Base # belongs_to :firm, class_name: "MyApplication::Business::Firm" # end # end # end # # == Bi-directional associations # # When you specify an association there is usually an association on the associated model # that specifies the same relationship in reverse. For example, with the following models: # # class Dungeon < ActiveRecord::Base # has_many :traps # has_one :evil_wizard # end # # class Trap < ActiveRecord::Base # belongs_to :dungeon # end # # class EvilWizard < ActiveRecord::Base # belongs_to :dungeon # end # # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+ # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, # Active Record doesn't know anything about these inverse relationships and so no object # loading optimization is possible. For example: # # d = Dungeon.first # t = d.traps.first # d.level == t.dungeon.level # => true # d.level = 10 # d.level == t.dungeon.level # => false # # The +Dungeon+ instances +d+ and t.dungeon in the above example refer to # the same object data from the database, but are actually different in-memory copies # of that data. Specifying the :inverse_of option on associations lets you tell # Active Record about inverse relationships and it will optimise object loading. For # example, if we changed our model definitions to: # # class Dungeon < ActiveRecord::Base # has_many :traps, inverse_of: :dungeon # has_one :evil_wizard, inverse_of: :dungeon # end # # class Trap < ActiveRecord::Base # belongs_to :dungeon, inverse_of: :traps # end # # class EvilWizard < ActiveRecord::Base # belongs_to :dungeon, inverse_of: :evil_wizard # end # # Then, from our code snippet above, +d+ and t.dungeon are actually the same # in-memory instance and our final d.level == t.dungeon.level will return +true+. # # There are limitations to :inverse_of support: # # * does not work with :through associations. # * does not work with :polymorphic associations. # * for +belongs_to+ associations +has_many+ inverse associations are ignored. # # == Deleting from associations # # === Dependent associations # # +has_many+, +has_one+ and +belongs_to+ associations support the :dependent option. # This allows you to specify that associated records should be deleted when the owner is # deleted. # # For example: # # class Author # has_many :posts, dependent: :destroy # end # Author.find(1).destroy # => Will destroy all of the author's posts, too # # The :dependent option can have different values which specify how the deletion # is done. For more information, see the documentation for this option on the different # specific association types. When no option is given, the behavior is to do nothing # with the associated records when destroying a record. # # Note that :dependent is implemented using Rails' callback # system, which works by processing callbacks in order. Therefore, other # callbacks declared either before or after the :dependent option # can affect what it does. # # === Delete or destroy? # # +has_many+ and +has_and_belongs_to_many+ associations have the methods destroy, # delete, destroy_all and delete_all. # # For +has_and_belongs_to_many+, delete and destroy are the same: they # cause the records in the join table to be removed. # # For +has_many+, destroy and destroy_all will always call the destroy method of the # record(s) being removed so that callbacks are run. However delete and delete_all will either # do the deletion according to the strategy specified by the :dependent option, or # if no :dependent option is given, then it will follow the default strategy. # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for # +has_many+ :through, where the default strategy is delete_all (delete # the join records, without running their callbacks). # # There is also a clear method which is the same as delete_all, except that # it returns the association rather than the records which have been deleted. # # === What gets deleted? # # There is a potential pitfall here: +has_and_belongs_to_many+ and +has_many+ :through # associations have records in join tables, as well as the associated records. So when we # call one of these deletion methods, what exactly should be deleted? # # The answer is that it is assumed that deletion on an association is about removing the # link between the owner and the associated object(s), rather than necessarily the # associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+ # :through, the join records will be deleted, but the associated records won't. # # This makes sense if you think about it: if you were to call post.tags.delete(Tag.find_by(name: 'food')) # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself # to be removed from the database. # # However, there are examples where this strategy doesn't make sense. For example, suppose # a person has many projects, and each project has many tasks. If we deleted one of a person's # tasks, we would probably not want the project to be deleted. In this scenario, the delete method # won't actually work: it can only be used if the association on the join model is a # +belongs_to+. In other situations you are expected to perform operations directly on # either the associated records or the :through association. # # With a regular +has_many+ there is no distinction between the "associated records" # and the "link", so there is only one choice for what gets deleted. # # With +has_and_belongs_to_many+ and +has_many+ :through, if you want to delete the # associated records themselves, you can always do something along the lines of # person.tasks.each(&:destroy). # # == Type safety with ActiveRecord::AssociationTypeMismatch # # If you attempt to assign an object to an association that doesn't match the inferred # or specified :class_name, you'll get an ActiveRecord::AssociationTypeMismatch. # # == Options # # All of the association macros can be specialized through options. This makes cases # more complex than the simple and guessable ones possible. module ClassMethods # Specifies a one-to-many association. The following methods for retrieval and query of # collections of associated objects will be added: # # +collection+ is a placeholder for the symbol passed as the +name+ argument, so # has_many :clients would add among others clients.empty?. # # [collection(force_reload = false)] # Returns an array of all the associated objects. # An empty array is returned if none are found. # [collection<<(object, ...)] # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. # Note that this operation instantly fires update SQL without waiting for the save or update call on the # parent object, unless the parent object is a new record. # [collection.delete(object, ...)] # Removes one or more objects from the collection by setting their foreign keys to +NULL+. # Objects will be in addition destroyed if they're associated with dependent: :destroy, # and deleted if they're associated with dependent: :delete_all. # # If the :through option is used, then the join records are deleted (rather than # nullified) by default, but you can specify dependent: :destroy or # dependent: :nullify to override this. # [collection.destroy(object, ...)] # Removes one or more objects from the collection by running destroy on # each record, regardless of any dependent option, ensuring callbacks are run. # # If the :through option is used, then the join records are destroyed # instead, not the objects themselves. # [collection=objects] # Replaces the collections content by deleting and adding objects as appropriate. If the :through # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is # direct. # [collection_singular_ids] # Returns an array of the associated objects' ids # [collection_singular_ids=ids] # Replace the collection with the objects identified by the primary keys in +ids+. This # method loads the models and calls collection=. See above. # [collection.clear] # Removes every object from the collection. This destroys the associated objects if they # are associated with dependent: :destroy, deletes them directly from the # database if dependent: :delete_all, otherwise sets their foreign keys to +NULL+. # If the :through option is true no destroy callbacks are invoked on the join models. # Join models are directly deleted. # [collection.empty?] # Returns +true+ if there are no associated objects. # [collection.size] # Returns the number of associated objects. # [collection.find(...)] # Finds an associated object according to the same rules as ActiveRecord::Base.find. # [collection.exists?(...)] # Checks whether an associated object with the given conditions exists. # Uses the same rules as ActiveRecord::Base.exists?. # [collection.build(attributes = {}, ...)] # Returns one or more new objects of the collection type that have been instantiated # with +attributes+ and linked to this object through a foreign key, but have not yet # been saved. # [collection.create(attributes = {})] # Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already # been saved (if it passed the validation). *Note*: This only works if the base model # already exists in the DB, not if it is a new (unsaved) record! # [collection.create!(attributes = {})] # Does the same as collection.create, but raises ActiveRecord::RecordInvalid # if the record is invalid. # # === Example # # A Firm class declares has_many :clients, which will add: # * Firm#clients (similar to Client.where(firm_id: id)) # * Firm#clients<< # * Firm#clients.delete # * Firm#clients.destroy # * Firm#clients= # * Firm#client_ids # * Firm#client_ids= # * Firm#clients.clear # * Firm#clients.empty? (similar to firm.clients.size == 0) # * Firm#clients.size (similar to Client.count "firm_id = #{id}") # * Firm#clients.find (similar to Client.where(firm_id: id).find(id)) # * Firm#clients.exists?(name: 'ACME') (similar to Client.exists?(name: 'ACME', firm_id: firm.id)) # * Firm#clients.build (similar to Client.new("firm_id" => id)) # * Firm#clients.create (similar to c = Client.new("firm_id" => id); c.save; c) # * Firm#clients.create! (similar to c = Client.new("firm_id" => id); c.save!) # The declaration can also include an +options+ hash to specialize the behavior of the association. # # === Scopes # # You can pass a second argument +scope+ as a callable (i.e. proc or # lambda) to retrieve a specific set of records or customize the generated # query when you access the associated collection. # # Scope examples: # has_many :comments, -> { where(author_id: 1) } # has_many :employees, -> { joins(:address) } # has_many :posts, ->(post) { where("max_post_length > ?", post.length) } # # === Extensions # # The +extension+ argument allows you to pass a block into a has_many # association. This is useful for adding new finders, creators and other # factory-type methods to be used as part of the association. # # Extension examples: # has_many :employees do # def find_or_create_by_name(name) # first_name, last_name = name.split(" ", 2) # find_or_create_by(first_name: first_name, last_name: last_name) # end # end # # === Options # [:class_name] # Specify the class name of the association. Use it only if that name can't be inferred # from the association name. So has_many :products will by default be linked # to the Product class, but if the real class name is SpecialProduct, you'll have to # specify it with this option. # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ # association will use "person_id" as the default :foreign_key. # [:foreign_type] # Specify the column used to store the associated object's type, if this is a polymorphic # association. By default this is guessed to be the name of the polymorphic association # specified on "as" option with a "_type" suffix. So a class that defines a # has_many :tags, as: :taggable association will use "taggable_type" as the # default :foreign_type. # [:primary_key] # Specify the name of the column to use as the primary key for the association. By default this is +id+. # [:dependent] # Controls what happens to the associated objects when # their owner is destroyed. Note that these are implemented as # callbacks, and Rails executes callbacks in order. Therefore, other # similar callbacks may affect the :dependent behavior, and the # :dependent behavior may affect other callbacks. # # * :destroy causes all the associated objects to also be destroyed. # * :delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not be executed). # * :nullify causes the foreign keys to be set to +NULL+. Callbacks are not executed. # * :restrict_with_exception causes an exception to be raised if there are any associated records. # * :restrict_with_error causes an error to be added to the owner if there are any associated objects. # # If using with the :through option, the association on the join model must be # a +belongs_to+, and the records which get deleted are the join records, rather than # the associated records. # [:counter_cache] # This option can be used to configure a custom named :counter_cache. You only need this option, # when you customized the name of your :counter_cache on the belongs_to association. # [:as] # Specifies a polymorphic interface (See belongs_to). # [:through] # Specifies an association through which to perform the query. This can be any other type # of association, including other :through associations. Options for :class_name, # :primary_key and :foreign_key are ignored, as the association uses the # source reflection. # # If the association on the join model is a +belongs_to+, the collection can be modified # and the records on the :through model will be automatically created and removed # as appropriate. Otherwise, the collection is read-only, so you should manipulate the # :through association directly. # # If you are going to modify the association (rather than just read from it), then it is # a good idea to set the :inverse_of option on the source association on the # join model. This allows associated records to be built which will automatically create # the appropriate join model records when they are saved. (See the 'Association Join Models' # section above.) # [:source] # Specifies the source association name used by has_many :through queries. # Only use it if the name cannot be inferred from the association. # has_many :subscribers, through: :subscriptions will look for either :subscribers or # :subscriber on Subscription, unless a :source is given. # [:source_type] # Specifies type of the source association used by has_many :through queries where the source # association is a polymorphic +belongs_to+. # [:validate] # If +false+, don't validate the associated objects when saving the parent object. true by default. # [:autosave] # If true, always save the associated objects or destroy them if marked for destruction, # when saving the parent object. If false, never save or destroy the associated objects. # By default, only save associated objects that are new records. This option is implemented as a # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects # may need to be explicitly saved in any user-defined +before_save+ callbacks. # # Note that accepts_nested_attributes_for sets :autosave to true. # [:inverse_of] # Specifies the name of the belongs_to association on the associated object # that is the inverse of this has_many association. Does not work in combination # with :through or :as options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # # Option examples: # has_many :comments, -> { order "posted_on" } # has_many :comments, -> { includes :author } # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person" # has_many :tracks, -> { order "position" }, dependent: :destroy # has_many :comments, dependent: :nullify # has_many :tags, as: :taggable # has_many :reports, -> { readonly } # has_many :subscribers, through: :subscriptions, source: :user def has_many(name, scope = nil, options = {}, &extension) reflection = Builder::HasMany.build(self, name, scope, options, &extension) Reflection.add_reflection self, name, reflection end # Specifies a one-to-one association with another class. This method should only be used # if the other class contains the foreign key. If the current class contains the foreign key, # then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview # on when to use +has_one+ and when to use +belongs_to+. # # The following methods for retrieval and query of a single associated object will be added: # # +association+ is a placeholder for the symbol passed as the +name+ argument, so # has_one :manager would add among others manager.nil?. # # [association(force_reload = false)] # Returns the associated object. +nil+ is returned if none is found. # [association=(associate)] # Assigns the associate object, extracts the primary key, sets it as the foreign key, # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing # associated object when assigning a new one, even if the new one isn't saved to database. # [build_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not # yet been saved. # [create_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that # has already been saved (if it passed the validation). # [create_association!(attributes = {})] # Does the same as create_association, but raises ActiveRecord::RecordInvalid # if the record is invalid. # # === Example # # An Account class declares has_one :beneficiary, which will add: # * Account#beneficiary (similar to Beneficiary.where(account_id: id).first) # * Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save) # * Account#build_beneficiary (similar to Beneficiary.new("account_id" => id)) # * Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b) # * Account#create_beneficiary! (similar to b = Beneficiary.new("account_id" => id); b.save!; b) # # === Scopes # # You can pass a second argument +scope+ as a callable (i.e. proc or # lambda) to retrieve a specific record or customize the generated query # when you access the associated object. # # Scope examples: # has_one :author, -> { where(comment_id: 1) } # has_one :employer, -> { joins(:company) } # has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) } # # === Options # # The declaration can also include an +options+ hash to specialize the behavior of the association. # # Options are: # [:class_name] # Specify the class name of the association. Use it only if that name can't be inferred # from the association name. So has_one :manager will by default be linked to the Manager class, but # if the real class name is Person, you'll have to specify it with this option. # [:dependent] # Controls what happens to the associated object when # its owner is destroyed: # # * :destroy causes the associated object to also be destroyed # * :delete causes the associated object to be deleted directly from the database (so callbacks will not execute) # * :nullify causes the foreign key to be set to +NULL+. Callbacks are not executed. # * :restrict_with_exception causes an exception to be raised if there is an associated record # * :restrict_with_error causes an error to be added to the owner if there is an associated object # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association # will use "person_id" as the default :foreign_key. # [:foreign_type] # Specify the column used to store the associated object's type, if this is a polymorphic # association. By default this is guessed to be the name of the polymorphic association # specified on "as" option with a "_type" suffix. So a class that defines a # has_one :tag, as: :taggable association will use "taggable_type" as the # default :foreign_type. # [:primary_key] # Specify the method that returns the primary key used for the association. By default this is +id+. # [:as] # Specifies a polymorphic interface (See belongs_to). # [:through] # Specifies a Join Model through which to perform the query. Options for :class_name, # :primary_key, and :foreign_key are ignored, as the association uses the # source reflection. You can only use a :through query through a has_one # or belongs_to association on the join model. # [:source] # Specifies the source association name used by has_one :through queries. # Only use it if the name cannot be inferred from the association. # has_one :favorite, through: :favorites will look for a # :favorite on Favorite, unless a :source is given. # [:source_type] # Specifies type of the source association used by has_one :through queries where the source # association is a polymorphic +belongs_to+. # [:validate] # If +false+, don't validate the associated object when saving the parent object. +false+ by default. # [:autosave] # If true, always save the associated object or destroy it if marked for destruction, # when saving the parent object. If false, never save or destroy the associated object. # By default, only save the associated object if it's a new record. # # Note that accepts_nested_attributes_for sets :autosave to true. # [:inverse_of] # Specifies the name of the belongs_to association on the associated object # that is the inverse of this has_one association. Does not work in combination # with :through or :as options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # [:required] # When set to +true+, the association will also have its presence validated. # This will validate the association itself, not the id. You can use # +:inverse_of+ to avoid an extra query during validation. # # Option examples: # has_one :credit_card, dependent: :destroy # destroys the associated credit card # has_one :credit_card, dependent: :nullify # updates the associated records foreign # # key value to NULL rather than destroying it # has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment" # has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person" # has_one :attachment, as: :attachable # has_one :boss, -> { readonly } # has_one :club, through: :membership # has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable # has_one :credit_card, required: true def has_one(name, scope = nil, options = {}) reflection = Builder::HasOne.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end # Specifies a one-to-one association with another class. This method should only be used # if this class contains the foreign key. If the other class contains the foreign key, # then you should use +has_one+ instead. See also ActiveRecord::Associations::ClassMethods's overview # on when to use +has_one+ and when to use +belongs_to+. # # Methods will be added for retrieval and query for a single associated object, for which # this object holds an id: # # +association+ is a placeholder for the symbol passed as the +name+ argument, so # belongs_to :author would add among others author.nil?. # # [association(force_reload = false)] # Returns the associated object. +nil+ is returned if none is found. # [association=(associate)] # Assigns the associate object, extracts the primary key, and sets it as the foreign key. # [build_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. # [create_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that # has already been saved (if it passed the validation). # [create_association!(attributes = {})] # Does the same as create_association, but raises ActiveRecord::RecordInvalid # if the record is invalid. # # === Example # # A Post class declares belongs_to :author, which will add: # * Post#author (similar to Author.find(author_id)) # * Post#author=(author) (similar to post.author_id = author.id) # * Post#build_author (similar to post.author = Author.new) # * Post#create_author (similar to post.author = Author.new; post.author.save; post.author) # * Post#create_author! (similar to post.author = Author.new; post.author.save!; post.author) # The declaration can also include an +options+ hash to specialize the behavior of the association. # # === Scopes # # You can pass a second argument +scope+ as a callable (i.e. proc or # lambda) to retrieve a specific record or customize the generated query # when you access the associated object. # # Scope examples: # belongs_to :user, -> { where(id: 2) } # belongs_to :user, -> { joins(:friends) } # belongs_to :level, ->(level) { where("game_level > ?", level.current) } # # === Options # # [:class_name] # Specify the class name of the association. Use it only if that name can't be inferred # from the association name. So belongs_to :author will by default be linked to the Author class, but # if the real class name is Person, you'll have to specify it with this option. # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name # of the association with an "_id" suffix. So a class that defines a belongs_to :person # association will use "person_id" as the default :foreign_key. Similarly, # belongs_to :favorite_person, class_name: "Person" will use a foreign key # of "favorite_person_id". # [:foreign_type] # Specify the column used to store the associated object's type, if this is a polymorphic # association. By default this is guessed to be the name of the association with a "_type" # suffix. So a class that defines a belongs_to :taggable, polymorphic: true # association will use "taggable_type" as the default :foreign_type. # [:primary_key] # Specify the method that returns the primary key of associated object used for the association. # By default this is id. # [:dependent] # If set to :destroy, the associated object is destroyed when this object is. If set to # :delete, the associated object is deleted *without* calling its destroy method. # This option should not be specified when belongs_to is used in conjunction with # a has_many relationship on another class because of the potential to leave # orphaned records behind. # [:counter_cache] # Caches the number of belonging objects on the associate class through the use of +increment_counter+ # and +decrement_counter+. The counter cache is incremented when an object of this # class is created and decremented when it's destroyed. This requires that a column # named #{table_name}_count (such as +comments_count+ for a belonging Comment class) # is used on the associate class (such as a Post class) - that is the migration for # #{table_name}_count is created on the associate class (such that Post.comments_count will # return the count cached, see note below). You can also specify a custom counter # cache column by providing a column name instead of a +true+/+false+ value to this # option (e.g., counter_cache: :my_custom_counter.) # Note: Specifying a counter cache will add it to that model's list of readonly attributes # using +attr_readonly+. # [:polymorphic] # Specify this association is a polymorphic association by passing +true+. # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute # to the +attr_readonly+ list in the associated classes (e.g. class Post; attr_readonly :comments_count; end). # [:validate] # If +false+, don't validate the associated objects when saving the parent object. +false+ by default. # [:autosave] # If true, always save the associated object or destroy it if marked for destruction, when # saving the parent object. # If false, never save or destroy the associated object. # By default, only save the associated object if it's a new record. # # Note that accepts_nested_attributes_for sets :autosave to true. # [:touch] # If true, the associated object will be touched (the updated_at/on attributes set to current time) # when this record is either saved or destroyed. If you specify a symbol, that attribute # will be updated with the current time in addition to the updated_at/on attribute. # [:inverse_of] # Specifies the name of the has_one or has_many association on the associated # object that is the inverse of this belongs_to association. Does not work in # combination with the :polymorphic options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # [:required] # When set to +true+, the association will also have its presence validated. # This will validate the association itself, not the id. You can use # +:inverse_of+ to avoid an extra query during validation. # # Option examples: # belongs_to :firm, foreign_key: "client_of" # belongs_to :person, primary_key: "name", foreign_key: "person_name" # belongs_to :author, class_name: "Person", foreign_key: "author_id" # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }, # class_name: "Coupon", foreign_key: "coupon_id" # belongs_to :attachable, polymorphic: true # belongs_to :project, -> { readonly } # belongs_to :post, counter_cache: true # belongs_to :company, touch: true # belongs_to :company, touch: :employees_last_updated_at # belongs_to :company, required: true def belongs_to(name, scope = nil, options = {}) reflection = Builder::BelongsTo.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end # Specifies a many-to-many relationship with another class. This associates two classes via an # intermediate join table. Unless the join table is explicitly specified as an option, it is # guessed using the lexical order of the class names. So a join between Developer and Project # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically. # Note that this precedence is calculated using the < operator for String. This # means that if the strings are of different lengths, and the strings are equal when compared # up to the shortest length, then the longer string is considered of higher # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the # custom :join_table option if you need to. # If your tables share a common prefix, it will only appear once at the beginning. For example, # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products". # # The join table should not have a primary key or a model associated with it. You must manually generate the # join table with a migration such as this: # # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration # def change # create_table :developers_projects, id: false do |t| # t.integer :developer_id # t.integer :project_id # end # end # end # # It's also a good idea to add indexes to each of those columns to speed up the joins process. # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only # uses one index per table during the lookup. # # Adds the following methods for retrieval and query: # # +collection+ is a placeholder for the symbol passed as the +name+ argument, so # has_and_belongs_to_many :categories would add among others categories.empty?. # # [collection(force_reload = false)] # Returns an array of all the associated objects. # An empty array is returned if none are found. # [collection<<(object, ...)] # Adds one or more objects to the collection by creating associations in the join table # (collection.push and collection.concat are aliases to this method). # Note that this operation instantly fires update SQL without waiting for the save or update call on the # parent object, unless the parent object is a new record. # [collection.delete(object, ...)] # Removes one or more objects from the collection by removing their associations from the join table. # This does not destroy the objects. # [collection.destroy(object, ...)] # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option. # This does not destroy the objects. # [collection=objects] # Replaces the collection's content by deleting and adding objects as appropriate. # [collection_singular_ids] # Returns an array of the associated objects' ids. # [collection_singular_ids=ids] # Replace the collection by the objects identified by the primary keys in +ids+. # [collection.clear] # Removes every object from the collection. This does not destroy the objects. # [collection.empty?] # Returns +true+ if there are no associated objects. # [collection.size] # Returns the number of associated objects. # [collection.find(id)] # Finds an associated object responding to the +id+ and that # meets the condition that it has to be associated with this object. # Uses the same rules as ActiveRecord::Base.find. # [collection.exists?(...)] # Checks whether an associated object with the given conditions exists. # Uses the same rules as ActiveRecord::Base.exists?. # [collection.build(attributes = {})] # Returns a new object of the collection type that has been instantiated # with +attributes+ and linked to this object through the join table, but has not yet been saved. # [collection.create(attributes = {})] # Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through the join table, and that has already been # saved (if it passed the validation). # # === Example # # A Developer class declares has_and_belongs_to_many :projects, which will add: # * Developer#projects # * Developer#projects<< # * Developer#projects.delete # * Developer#projects.destroy # * Developer#projects= # * Developer#project_ids # * Developer#project_ids= # * Developer#projects.clear # * Developer#projects.empty? # * Developer#projects.size # * Developer#projects.find(id) # * Developer#projects.exists?(...) # * Developer#projects.build (similar to Project.new("developer_id" => id)) # * Developer#projects.create (similar to c = Project.new("developer_id" => id); c.save; c) # The declaration may include an +options+ hash to specialize the behavior of the association. # # === Scopes # # You can pass a second argument +scope+ as a callable (i.e. proc or # lambda) to retrieve a specific set of records or customize the generated # query when you access the associated collection. # # Scope examples: # has_and_belongs_to_many :projects, -> { includes :milestones, :manager } # has_and_belongs_to_many :categories, ->(category) { # where("default_category = ?", category.name) # } # # === Extensions # # The +extension+ argument allows you to pass a block into a # has_and_belongs_to_many association. This is useful for adding new # finders, creators and other factory-type methods to be used as part of # the association. # # Extension examples: # has_and_belongs_to_many :contractors do # def find_or_create_by_name(name) # first_name, last_name = name.split(" ", 2) # find_or_create_by(first_name: first_name, last_name: last_name) # end # end # # === Options # # [:class_name] # Specify the class name of the association. Use it only if that name can't be inferred # from the association name. So has_and_belongs_to_many :projects will by default be linked to the # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. # [:join_table] # Specify the name of the join table if the default based on lexical order isn't what you want. # WARNING: If you're overwriting the table name of either class, the +table_name+ method # MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work. # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes # a +has_and_belongs_to_many+ association to Project will use "person_id" as the # default :foreign_key. # [:association_foreign_key] # Specify the foreign key used for the association on the receiving side of the association. # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. # So if a Person class makes a +has_and_belongs_to_many+ association to Project, # the association will use "project_id" as the default :association_foreign_key. # [:readonly] # If true, all the associated objects are readonly through the association. # [:validate] # If +false+, don't validate the associated objects when saving the parent object. +true+ by default. # [:autosave] # If true, always save the associated objects or destroy them if marked for destruction, when # saving the parent object. # If false, never save or destroy the associated objects. # By default, only save associated objects that are new records. # # Note that accepts_nested_attributes_for sets :autosave to true. # # Option examples: # has_and_belongs_to_many :projects # has_and_belongs_to_many :projects, -> { includes :milestones, :manager } # has_and_belongs_to_many :nations, class_name: "Country" # has_and_belongs_to_many :categories, join_table: "prods_cats" # has_and_belongs_to_many :categories, -> { readonly } def has_and_belongs_to_many(name, scope = nil, options = {}, &extension) if scope.is_a?(Hash) options = scope scope = nil end habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) builder = Builder::HasAndBelongsToMany.new name, self, options join_model = builder.through_model # FIXME: we should move this to the internal constants. Also people # should never directly access this constant so I'm not happy about # setting it. const_set join_model.name, join_model middle_reflection = builder.middle_reflection join_model Builder::HasMany.define_callbacks self, middle_reflection Reflection.add_reflection self, middle_reflection.name, middle_reflection middle_reflection.parent_reflection = [name.to_s, habtm_reflection] include Module.new { class_eval <<-RUBY, __FILE__, __LINE__ + 1 def destroy_associations association(:#{middle_reflection.name}).delete_all(:delete_all) association(:#{name}).reset super end RUBY } hm_options = {} hm_options[:through] = middle_reflection.name hm_options[:source] = join_model.right_reflection.name [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k| hm_options[k] = options[k] if options.key? k end has_many name, scope, hm_options, &extension self._reflections[name.to_s].parent_reflection = [name.to_s, habtm_reflection] end end end end rails-4.2.6/activerecord/lib/active_record/associations/000077500000000000000000000000001266740050600233735ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/associations/alias_tracker.rb000066400000000000000000000051641266740050600265320ustar00rootroot00000000000000require 'active_support/core_ext/string/conversions' module ActiveRecord module Associations # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and # ActiveRecord::Associations::ThroughAssociationScope class AliasTracker # :nodoc: attr_reader :aliases, :connection def self.empty(connection) new connection, Hash.new(0) end def self.create(connection, table_joins) if table_joins.empty? empty connection else aliases = Hash.new { |h,k| h[k] = initial_count_for(connection, k, table_joins) } new connection, aliases end end def self.initial_count_for(connection, name, table_joins) # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase quoted_name = connection.quote_table_name(name).downcase counts = table_joins.map do |join| if join.is_a?(Arel::Nodes::StringJoin) # Table names + table aliases join.left.downcase.scan( /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ ).size elsif join.respond_to? :left join.left.table_name == name ? 1 : 0 else # this branch is reached by two tests: # # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37 # with :posts # # activerecord/test/cases/associations/eager_test.rb:1133 # with :comments # 0 end end counts.sum end # table_joins is an array of arel joins which might conflict with the aliases we assign here def initialize(connection, aliases) @aliases = aliases @connection = connection end def aliased_table_for(table_name, aliased_name) if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 Arel::Table.new(table_name) else # Otherwise, we need to use an alias aliased_name = connection.table_alias_for(aliased_name) # Update the count aliases[aliased_name] += 1 table_alias = if aliases[aliased_name] > 1 "#{truncate(aliased_name)}_#{aliases[aliased_name]}" else aliased_name end Arel::Table.new(table_name).alias(table_alias) end end private def truncate(name) name.slice(0, connection.table_alias_length - 2) end end end end rails-4.2.6/activerecord/lib/active_record/associations/association.rb000066400000000000000000000215661266740050600262460ustar00rootroot00000000000000require 'active_support/core_ext/array/wrap' module ActiveRecord module Associations # = Active Record Associations # # This is the root class of all associations ('+ Foo' signifies an included module Foo): # # Association # SingularAssociation # HasOneAssociation # HasOneThroughAssociation + ThroughAssociation # BelongsToAssociation # BelongsToPolymorphicAssociation # CollectionAssociation # HasManyAssociation # HasManyThroughAssociation + ThroughAssociation class Association #:nodoc: attr_reader :owner, :target, :reflection attr_accessor :inversed delegate :options, :to => :reflection def initialize(owner, reflection) reflection.check_validity! @owner, @reflection = owner, reflection reset reset_scope end # Returns the name of the table of the associated class: # # post.comments.aliased_table_name # => "comments" # def aliased_table_name klass.table_name end # Resets the \loaded flag to +false+ and sets the \target to +nil+. def reset @loaded = false @target = nil @stale_state = nil @inversed = false end # Reloads the \target and returns +self+ on success. def reload reset reset_scope load_target self unless target.nil? end # Has the \target been already \loaded? def loaded? @loaded end # Asserts the \target has been loaded setting the \loaded flag to +true+. def loaded! @loaded = true @stale_state = stale_state @inversed = false end # The target is stale if the target no longer points to the record(s) that the # relevant foreign_key(s) refers to. If stale, the association accessor method # on the owner will reload the target. It's up to subclasses to implement the # stale_state method if relevant. # # Note that if the target has not been loaded, it is not considered stale. def stale_target? !inversed && loaded? && @stale_state != stale_state end # Sets the target of this association to \target, and the \loaded flag to +true+. def target=(target) @target = target loaded! end def scope target_scope.merge(association_scope) end # The scope for this association. # # Note that the association_scope is merged into the target_scope only when the # scope method is called. This is because at that point the call may be surrounded # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which # actually gets built. def association_scope if klass @association_scope ||= AssociationScope.scope(self, klass.connection) end end def reset_scope @association_scope = nil end # Set the inverse association, if possible def set_inverse_instance(record) if invertible_for?(record) inverse = record.association(inverse_reflection_for(record).name) inverse.target = owner inverse.inversed = true end record end # Returns the class of the target. belongs_to polymorphic overrides this to look at the # polymorphic_type field on the owner. def klass reflection.klass end # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the # through association's scope) def target_scope AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all) end # Loads the \target if needed and returns it. # # This method is abstract in the sense that it relies on +find_target+, # which is expected to be provided by descendants. # # If the \target is already \loaded it is just returned. Thus, you can call # +load_target+ unconditionally to get the \target. # # ActiveRecord::RecordNotFound is rescued within the method, and it is # not reraised. The proxy is \reset and +nil+ is the return value. def load_target @target = find_target if (@stale_state && stale_target?) || find_target? loaded! unless loaded? target rescue ActiveRecord::RecordNotFound reset end def interpolate(sql, record = nil) if sql.respond_to?(:to_proc) owner.instance_exec(record, &sql) else sql end end # We can't dump @reflection since it contains the scope proc def marshal_dump ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] } [@reflection.name, ivars] end def marshal_load(data) reflection_name, ivars = data ivars.each { |name, val| instance_variable_set(name, val) } @reflection = @owner.class._reflect_on_association(reflection_name) end def initialize_attributes(record) #:nodoc: skip_assign = [reflection.foreign_key, reflection.type].compact attributes = create_scope.except(*(record.changed - skip_assign)) record.assign_attributes(attributes) set_inverse_instance(record) end private def find_target? !loaded? && (!owner.new_record? || foreign_key_present?) && klass end def creation_attributes attributes = {} if (reflection.has_one? || reflection.collection?) && !options[:through] attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key] if reflection.options[:as] attributes[reflection.type] = owner.class.base_class.name end end attributes end # Sets the owner attributes on the given record def set_owner_attributes(record) creation_attributes.each { |key, value| record[key] = value } end # Returns true if there is a foreign key present on the owner which # references the target. This is used to determine whether we can load # the target if the owner is currently a new record (and therefore # without a key). If the owner is a new record then foreign_key must # be present in order to load target. # # Currently implemented by belongs_to (vanilla and polymorphic) and # has_one/has_many :through associations which go through a belongs_to. def foreign_key_present? false end # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of # the kind of the class of the associated objects. Meant to be used as # a sanity check when you are about to assign an associated record. def raise_on_type_mismatch!(record) unless record.is_a?(reflection.klass) fresh_class = reflection.class_name.safe_constantize unless fresh_class && record.is_a?(fresh_class) message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" raise ActiveRecord::AssociationTypeMismatch, message end end end # Can be redefined by subclasses, notably polymorphic belongs_to # The record parameter is necessary to support polymorphic inverses as we must check for # the association in the specific class of the record. def inverse_reflection_for(record) reflection.inverse_of end # Returns true if inverse association on the given record needs to be set. # This method is redefined by subclasses. def invertible_for?(record) foreign_key_for?(record) && inverse_reflection_for(record) end # Returns true if record contains the foreign_key def foreign_key_for?(record) record.has_attribute?(reflection.foreign_key) end # This should be implemented to return the values of the relevant key(s) on the owner, # so that when stale_state is different from the value stored on the last find_target, # the target is stale. # # This is only relevant to certain associations, which is why it returns nil by default. def stale_state end def build_record(attributes) reflection.build_association(attributes) do |record| initialize_attributes(record) end end # Returns true if statement cache should be skipped on the association reader. def skip_statement_cache? reflection.scope_chain.any?(&:any?) || scope.eager_loading? || klass.current_scope || klass.default_scopes.any? || reflection.source_reflection.active_record.default_scopes.any? end end end end rails-4.2.6/activerecord/lib/active_record/associations/association_scope.rb000066400000000000000000000137501266740050600274330ustar00rootroot00000000000000module ActiveRecord module Associations class AssociationScope #:nodoc: def self.scope(association, connection) INSTANCE.scope association, connection end class BindSubstitution def initialize(block) @block = block end def bind_value(scope, column, value, alias_tracker) substitute = alias_tracker.connection.substitute_at(column) scope.bind_values += [[column, @block.call(value)]] substitute end end def self.create(&block) block = block ? block : lambda { |val| val } new BindSubstitution.new(block) end def initialize(bind_substitution) @bind_substitution = bind_substitution end INSTANCE = create def scope(association, connection) klass = association.klass reflection = association.reflection scope = klass.unscoped owner = association.owner alias_tracker = AliasTracker.empty connection scope.extending! Array(reflection.options[:extend]) add_constraints(scope, owner, klass, reflection, alias_tracker) end def join_type Arel::Nodes::InnerJoin end def self.get_bind_values(owner, chain) binds = [] last_reflection = chain.last binds << last_reflection.join_id_for(owner) if last_reflection.type binds << owner.class.base_class.name end chain.each_cons(2).each do |reflection, next_reflection| if reflection.type binds << next_reflection.klass.base_class.name end end binds end private def construct_tables(chain, klass, refl, alias_tracker) chain.map do |reflection| alias_tracker.aliased_table_for( table_name_for(reflection, klass, refl), table_alias_for(reflection, refl, reflection != refl) ) end end def table_alias_for(reflection, refl, join = false) name = "#{reflection.plural_name}_#{alias_suffix(refl)}" name << "_join" if join name end def join(table, constraint) table.create_join(table, table.create_on(constraint), join_type) end def column_for(table_name, column_name, alias_tracker) columns = alias_tracker.connection.schema_cache.columns_hash(table_name) columns[column_name] end def bind_value(scope, column, value, alias_tracker) @bind_substitution.bind_value scope, column, value, alias_tracker end def bind(scope, table_name, column_name, value, tracker) column = column_for table_name, column_name, tracker bind_value scope, column, value, tracker end def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass) join_keys = reflection.join_keys(assoc_klass) key = join_keys.key foreign_key = join_keys.foreign_key bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker scope = scope.where(table[key].eq(bind_val)) if reflection.type value = owner.class.base_class.name bind_val = bind scope, table.table_name, reflection.type, value, tracker scope = scope.where(table[reflection.type].eq(bind_val)) else scope end end def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection) join_keys = reflection.join_keys(assoc_klass) key = join_keys.key foreign_key = join_keys.foreign_key constraint = table[key].eq(foreign_table[foreign_key]) if reflection.type value = next_reflection.klass.base_class.name bind_val = bind scope, table.table_name, reflection.type, value, tracker scope = scope.where(table[reflection.type].eq(bind_val)) end scope = scope.joins(join(foreign_table, constraint)) end def add_constraints(scope, owner, assoc_klass, refl, tracker) chain = refl.chain scope_chain = refl.scope_chain tables = construct_tables(chain, assoc_klass, refl, tracker) owner_reflection = chain.last table = tables.last scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass) chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first unless reflection == chain.last next_reflection = chain[i + 1] scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection) end is_first_chain = i == 0 klass = is_first_chain ? assoc_klass : reflection.klass # Exclude the scope of the association itself, because that # was already merged in the #scope method. scope_chain[i].each do |scope_chain_item| item = eval_scope(klass, scope_chain_item, owner) if scope_chain_item == refl.scope scope.merge! item.except(:where, :includes, :bind) end if is_first_chain scope.includes! item.includes_values end scope.unscope!(*item.unscope_values) scope.where_values += item.where_values scope.bind_values += item.bind_values scope.order_values |= item.order_values end end scope end def alias_suffix(refl) refl.name end def table_name_for(reflection, klass, refl) if reflection == refl # If this is a polymorphic belongs_to, we want to get the klass from the # association because it depends on the polymorphic_type attribute of # the owner klass.table_name else reflection.table_name end end def eval_scope(klass, scope, owner) klass.unscoped.instance_exec(owner, &scope) end end end end rails-4.2.6/activerecord/lib/active_record/associations/belongs_to_association.rb000066400000000000000000000063151266740050600304540ustar00rootroot00000000000000module ActiveRecord # = Active Record Belongs To Association module Associations class BelongsToAssociation < SingularAssociation #:nodoc: def handle_dependency target.send(options[:dependent]) if load_target end def replace(record) if record raise_on_type_mismatch!(record) update_counters(record) replace_keys(record) set_inverse_instance(record) @updated = true else decrement_counters remove_keys end self.target = record end def reset super @updated = false end def updated? @updated end def decrement_counters # :nodoc: with_cache_name { |name| decrement_counter name } end def increment_counters # :nodoc: with_cache_name { |name| increment_counter name } end private def find_target? !loaded? && foreign_key_present? && klass end def with_cache_name counter_cache_name = reflection.counter_cache_column return unless counter_cache_name && owner.persisted? yield counter_cache_name end def update_counters(record) with_cache_name do |name| return unless different_target? record record.class.increment_counter(name, record.id) decrement_counter name end end def decrement_counter(counter_cache_name) if foreign_key_present? klass.decrement_counter(counter_cache_name, target_id) end end def increment_counter(counter_cache_name) if foreign_key_present? klass.increment_counter(counter_cache_name, target_id) if target && !stale_target? && counter_cache_available_in_memory?(counter_cache_name) target.increment(counter_cache_name) end end end # Checks whether record is different to the current target, without loading it def different_target?(record) record.id != owner._read_attribute(reflection.foreign_key) end def replace_keys(record) owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class)) end def remove_keys owner[reflection.foreign_key] = nil end def foreign_key_present? owner._read_attribute(reflection.foreign_key) end # NOTE - for now, we're only supporting inverse setting from belongs_to back onto # has_one associations. def invertible_for?(record) inverse = inverse_reflection_for(record) inverse && inverse.has_one? end def target_id if options[:primary_key] owner.send(reflection.name).try(:id) else owner._read_attribute(reflection.foreign_key) end end def stale_state result = owner._read_attribute(reflection.foreign_key) result && result.to_s end def counter_cache_available_in_memory?(counter_cache_name) target.respond_to?(counter_cache_name) end end end end rails-4.2.6/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb000066400000000000000000000020241266740050600330720ustar00rootroot00000000000000module ActiveRecord # = Active Record Belongs To Polymorphic Association module Associations class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc: def klass type = owner[reflection.foreign_type] type.presence && type.constantize end private def replace_keys(record) super owner[reflection.foreign_type] = record.class.base_class.name end def remove_keys super owner[reflection.foreign_type] = nil end def different_target?(record) super || record.class != klass end def inverse_reflection_for(record) reflection.polymorphic_inverse_of(record.class) end def raise_on_type_mismatch!(record) # A polymorphic association cannot have a type mismatch, by definition end def stale_state foreign_key = super foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s] end end end end rails-4.2.6/activerecord/lib/active_record/associations/builder/000077500000000000000000000000001266740050600250215ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/associations/builder/association.rb000066400000000000000000000104601266740050600276630ustar00rootroot00000000000000require 'active_support/core_ext/module/attribute_accessors' # This is the parent Association class which defines the variables # used by all associations. # # The hierarchy is defined as follows: # Association # - SingularAssociation # - BelongsToAssociation # - HasOneAssociation # - CollectionAssociation # - HasManyAssociation module ActiveRecord::Associations::Builder class Association #:nodoc: class << self attr_accessor :extensions # TODO: This class accessor is needed to make activerecord-deprecated_finders work. # We can move it to a constant in 5.0. attr_accessor :valid_options end self.extensions = [] self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate] attr_reader :name, :scope, :options def self.build(model, name, scope, options, &block) if model.dangerous_attribute_method?(name) raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \ "this will conflict with a method #{name} already defined by Active Record. " \ "Please choose a different association name." end builder = create_builder model, name, scope, options, &block reflection = builder.build(model) define_accessors model, reflection define_callbacks model, reflection define_validations model, reflection builder.define_extensions model reflection end def self.create_builder(model, name, scope, options, &block) raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) new(model, name, scope, options, &block) end def initialize(model, name, scope, options) # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders. if scope.is_a?(Hash) options = scope scope = nil end # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders. @name = name @scope = scope @options = options validate_options if scope && scope.arity == 0 @scope = proc { instance_exec(&scope) } end end def build(model) ActiveRecord::Reflection.create(macro, name, scope, options, model) end def macro raise NotImplementedError end def valid_options Association.valid_options + Association.extensions.flat_map(&:valid_options) end def validate_options options.assert_valid_keys(valid_options) end def define_extensions(model) end def self.define_callbacks(model, reflection) if dependent = reflection.options[:dependent] check_dependent_options(dependent) add_destroy_callbacks(model, reflection) end Association.extensions.each do |extension| extension.build model, reflection end end # Defines the setter and getter methods for the association # class Post < ActiveRecord::Base # has_many :comments # end # # Post.first.comments and Post.first.comments= methods are defined by this method... def self.define_accessors(model, reflection) mixin = model.generated_association_methods name = reflection.name define_readers(mixin, name) define_writers(mixin, name) end def self.define_readers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}(*args) association(:#{name}).reader(*args) end CODE end def self.define_writers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(value) association(:#{name}).writer(value) end CODE end def self.define_validations(model, reflection) # noop end def self.valid_dependent_options raise NotImplementedError end private def self.check_dependent_options(dependent) unless valid_dependent_options.include? dependent raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}" end end def self.add_destroy_callbacks(model, reflection) name = reflection.name model.before_destroy lambda { |o| o.association(name).handle_dependency } end end end rails-4.2.6/activerecord/lib/active_record/associations/builder/belongs_to.rb000066400000000000000000000066461266740050600275150ustar00rootroot00000000000000module ActiveRecord::Associations::Builder class BelongsTo < SingularAssociation #:nodoc: def macro :belongs_to end def valid_options super + [:foreign_type, :polymorphic, :touch, :counter_cache] end def self.valid_dependent_options [:destroy, :delete] end def self.define_callbacks(model, reflection) super add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache] add_touch_callbacks(model, reflection) if reflection.options[:touch] end def self.define_accessors(mixin, reflection) super add_counter_cache_methods mixin end private def self.add_counter_cache_methods(mixin) return if mixin.method_defined? :belongs_to_counter_cache_after_update mixin.class_eval do def belongs_to_counter_cache_after_update(reflection) foreign_key = reflection.foreign_key cache_column = reflection.counter_cache_column if (@_after_create_counter_called ||= false) @_after_create_counter_called = false elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable? model = reflection.klass foreign_key_was = attribute_was foreign_key foreign_key = attribute foreign_key if foreign_key && model.respond_to?(:increment_counter) model.increment_counter(cache_column, foreign_key) end if foreign_key_was && model.respond_to?(:decrement_counter) model.decrement_counter(cache_column, foreign_key_was) end end end end end def self.add_counter_cache_callbacks(model, reflection) cache_column = reflection.counter_cache_column model.after_update lambda { |record| record.belongs_to_counter_cache_after_update(reflection) } klass = reflection.class_name.safe_constantize klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly) end def self.touch_record(o, foreign_key, name, touch) # :nodoc: old_foreign_id = o.changed_attributes[foreign_key] if old_foreign_id association = o.association(name) reflection = association.reflection if reflection.polymorphic? klass = o.public_send("#{reflection.foreign_type}_was").constantize else klass = association.klass end old_record = klass.find_by(klass.primary_key => old_foreign_id) if old_record if touch != true old_record.touch touch else old_record.touch end end end record = o.send name if record && record.persisted? if touch != true record.touch touch else record.touch end end end def self.add_touch_callbacks(model, reflection) foreign_key = reflection.foreign_key n = reflection.name touch = reflection.options[:touch] callback = lambda { |record| BelongsTo.touch_record(record, foreign_key, n, touch) } model.after_save callback, if: :changed? model.after_touch callback model.after_destroy callback end def self.add_destroy_callbacks(model, reflection) name = reflection.name model.after_destroy lambda { |o| o.association(name).handle_dependency } end end end rails-4.2.6/activerecord/lib/active_record/associations/builder/collection_association.rb000066400000000000000000000052741266740050600321050ustar00rootroot00000000000000# This class is inherited by the has_many and has_many_and_belongs_to_many association classes require 'active_record/associations' module ActiveRecord::Associations::Builder class CollectionAssociation < Association #:nodoc: CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] def valid_options super + [:table_name, :before_add, :after_add, :before_remove, :after_remove, :extend] end attr_reader :block_extension def initialize(model, name, scope, options) super @mod = nil if block_given? @mod = Module.new(&Proc.new) @scope = wrap_scope @scope, @mod end end def self.define_callbacks(model, reflection) super name = reflection.name options = reflection.options CALLBACKS.each { |callback_name| define_callback(model, callback_name, name, options) } end def define_extensions(model) if @mod extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension" model.parent.const_set(extension_module_name, @mod) end end def self.define_callback(model, callback_name, name, options) full_callback_name = "#{callback_name}_for_#{name}" # TODO : why do i need method_defined? I think its because of the inheritance chain model.class_attribute full_callback_name unless model.method_defined?(full_callback_name) callbacks = Array(options[callback_name.to_sym]).map do |callback| case callback when Symbol ->(method, owner, record) { owner.send(callback, record) } when Proc ->(method, owner, record) { callback.call(owner, record) } else ->(method, owner, record) { callback.send(method, owner, record) } end end model.send "#{full_callback_name}=", callbacks end # Defines the setter and getter methods for the collection_singular_ids. def self.define_readers(mixin, name) super mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name.to_s.singularize}_ids association(:#{name}).ids_reader end CODE end def self.define_writers(mixin, name) super mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name.to_s.singularize}_ids=(ids) association(:#{name}).ids_writer(ids) end CODE end private def wrap_scope(scope, mod) if scope if scope.arity > 0 proc { |owner| instance_exec(owner, &scope).extending(mod) } else proc { instance_exec(&scope).extending(mod) } end else proc { extending(mod) } end end end end rails-4.2.6/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb000066400000000000000000000073561266740050600322150ustar00rootroot00000000000000module ActiveRecord::Associations::Builder class HasAndBelongsToMany # :nodoc: class JoinTableResolver KnownTable = Struct.new :join_table class KnownClass def initialize(lhs_class, rhs_class_name) @lhs_class = lhs_class @rhs_class_name = rhs_class_name @join_table = nil end def join_table @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_") end private def klass @lhs_class.send(:compute_type, @rhs_class_name) end end def self.build(lhs_class, name, options) if options[:join_table] KnownTable.new options[:join_table].to_s else class_name = options.fetch(:class_name) { name.to_s.camelize.singularize } KnownClass.new lhs_class, class_name end end end attr_reader :lhs_model, :association_name, :options def initialize(association_name, lhs_model, options) @association_name = association_name @lhs_model = lhs_model @options = options end def through_model habtm = JoinTableResolver.build lhs_model, association_name, options join_model = Class.new(ActiveRecord::Base) { class << self; attr_accessor :left_model attr_accessor :name attr_accessor :table_name_resolver attr_accessor :left_reflection attr_accessor :right_reflection end def self.table_name table_name_resolver.join_table end def self.compute_type(class_name) left_model.compute_type class_name end def self.add_left_association(name, options) belongs_to name, options self.left_reflection = _reflect_on_association(name) end def self.add_right_association(name, options) rhs_name = name.to_s.singularize.to_sym belongs_to rhs_name, options self.right_reflection = _reflect_on_association(rhs_name) end def self.retrieve_connection left_model.retrieve_connection end } join_model.name = "HABTM_#{association_name.to_s.camelize}" join_model.table_name_resolver = habtm join_model.left_model = lhs_model join_model.add_left_association :left_side, anonymous_class: lhs_model join_model.add_right_association association_name, belongs_to_options(options) join_model end def middle_reflection(join_model) middle_name = [lhs_model.name.downcase.pluralize, association_name].join('_').gsub(/::/, '_').to_sym middle_options = middle_options join_model hm_builder = HasMany.create_builder(lhs_model, middle_name, nil, middle_options) hm_builder.build lhs_model end private def middle_options(join_model) middle_options = {} middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}" middle_options[:source] = join_model.left_reflection.name if options.key? :foreign_key middle_options[:foreign_key] = options[:foreign_key] end middle_options end def belongs_to_options(options) rhs_options = {} if options.key? :class_name rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key rhs_options[:class_name] = options[:class_name] end if options.key? :association_foreign_key rhs_options[:foreign_key] = options[:association_foreign_key] end rhs_options end end end rails-4.2.6/activerecord/lib/active_record/associations/builder/has_many.rb000066400000000000000000000006711266740050600271510ustar00rootroot00000000000000module ActiveRecord::Associations::Builder class HasMany < CollectionAssociation #:nodoc: def macro :has_many end def valid_options super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type] end def self.valid_dependent_options [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception] end end end rails-4.2.6/activerecord/lib/active_record/associations/builder/has_one.rb000066400000000000000000000010421266740050600267570ustar00rootroot00000000000000module ActiveRecord::Associations::Builder class HasOne < SingularAssociation #:nodoc: def macro :has_one end def valid_options valid = super + [:as, :foreign_type] valid += [:through, :source, :source_type] if options[:through] valid end def self.valid_dependent_options [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception] end private def self.add_destroy_callbacks(model, reflection) super unless reflection.options[:through] end end end rails-4.2.6/activerecord/lib/active_record/associations/builder/singular_association.rb000066400000000000000000000022161266740050600315670ustar00rootroot00000000000000# This class is inherited by the has_one and belongs_to association classes module ActiveRecord::Associations::Builder class SingularAssociation < Association #:nodoc: def valid_options super + [:dependent, :primary_key, :inverse_of, :required] end def self.define_accessors(model, reflection) super define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable? end # Defines the (build|create)_association methods for belongs_to or has_one association def self.define_constructors(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def build_#{name}(*args, &block) association(:#{name}).build(*args, &block) end def create_#{name}(*args, &block) association(:#{name}).create(*args, &block) end def create_#{name}!(*args, &block) association(:#{name}).create!(*args, &block) end CODE end def self.define_validations(model, reflection) super if reflection.options[:required] model.validates_presence_of reflection.name end end end end rails-4.2.6/activerecord/lib/active_record/associations/collection_association.rb000066400000000000000000000516541266740050600304620ustar00rootroot00000000000000module ActiveRecord module Associations # = Active Record Association Collection # # CollectionAssociation is an abstract class that provides common stuff to # ease the implementation of association proxies that represent # collections. See the class hierarchy in Association. # # CollectionAssociation: # HasManyAssociation => has_many # HasManyThroughAssociation + ThroughAssociation => has_many :through # # CollectionAssociation class provides common methods to the collections # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with # +:through association+ option. # # You need to be careful with assumptions regarding the target: The proxy # does not fetch records from the database until it needs them, but new # ones created with +build+ are added to the target. So, the target may be # non-empty and still lack children waiting to be read from the database. # If you look directly to the database you cannot assume that's the entire # collection because new records may have been added to the target, etc. # # If you need to work on all current children, new and existing records, # +load_target+ and the +loaded+ flag are your friends. class CollectionAssociation < Association #:nodoc: # Implements the reader method, e.g. foo.items for Foo.has_many :items def reader(force_reload = false) if force_reload klass.uncached { reload } elsif stale_target? reload end if owner.new_record? # Cache the proxy separately before the owner has an id # or else a post-save proxy will still lack the id @new_record_proxy ||= CollectionProxy.create(klass, self) else @proxy ||= CollectionProxy.create(klass, self) end end # Implements the writer method, e.g. foo.items= for Foo.has_many :items def writer(records) replace(records) end # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items def ids_reader if loaded? load_target.map do |record| record.send(reflection.association_primary_key) end else column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" scope.pluck(column) end end # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items def ids_writer(ids) pk_type = reflection.primary_key_type ids = Array(ids).reject { |id| id.blank? } ids.map! { |i| pk_type.type_cast_from_user(i) } replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids)) end def reset super @target = [] end def select(*fields) if block_given? load_target.select.each { |e| yield e } else scope.select(*fields) end end def find(*args) if block_given? load_target.find(*args) { |*block_args| yield(*block_args) } else if options[:inverse_of] && loaded? args_flatten = args.flatten raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank? result = find_by_scan(*args) result_size = Array(result).size if !result || result_size != args_flatten.size scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size) else result end else scope.find(*args) end end end def first(*args) first_nth_or_last(:first, *args) end def second(*args) first_nth_or_last(:second, *args) end def third(*args) first_nth_or_last(:third, *args) end def fourth(*args) first_nth_or_last(:fourth, *args) end def fifth(*args) first_nth_or_last(:fifth, *args) end def forty_two(*args) first_nth_or_last(:forty_two, *args) end def last(*args) first_nth_or_last(:last, *args) end def take(n = nil) if loaded? n ? target.take(n) : target.first else scope.take(n).tap do |record| set_inverse_instance record if record.is_a? ActiveRecord::Base end end end def build(attributes = {}, &block) if attributes.is_a?(Array) attributes.collect { |attr| build(attr, &block) } else add_to_target(build_record(attributes)) do |record| yield(record) if block_given? end end end def create(attributes = {}, &block) _create_record(attributes, &block) end def create!(attributes = {}, &block) _create_record(attributes, true, &block) end # Add +records+ to this association. Returns +self+ so method calls may # be chained. Since << flattens its argument list and inserts each record, # +push+ and +concat+ behave identically. def concat(*records) if owner.new_record? load_target concat_records(records) else transaction { concat_records(records) } end end # Starts a transaction in the association class's database connection. # # class Author < ActiveRecord::Base # has_many :books # end # # Author.first.books.transaction do # # same effect as calling Book.transaction # end def transaction(*args) reflection.klass.transaction(*args) do yield end end # Removes all records from the association without calling callbacks # on the associated records. It honors the +:dependent+ option. However # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+ # deletion strategy for the association is applied. # # You can force a particular deletion strategy by passing a parameter. # # Example: # # @author.books.delete_all(:nullify) # @author.books.delete_all(:delete_all) # # See delete for more info. def delete_all(dependent = nil) if dependent && ![:nullify, :delete_all].include?(dependent) raise ArgumentError, "Valid values are :nullify or :delete_all" end dependent = if dependent dependent elsif options[:dependent] == :destroy :delete_all else options[:dependent] end delete_or_nullify_all_records(dependent).tap do reset loaded! end end # Destroy all the records from this association. # # See destroy for more info. def destroy_all destroy(load_target).tap do reset loaded! end end # Count all records using SQL. Construct options and pass them with # scope to the target class's +count+. def count(column_name = nil, count_options = {}) # TODO: Remove count_options argument as soon we remove support to # activerecord-deprecated_finders. column_name, count_options = nil, column_name if column_name.is_a?(Hash) relation = scope if association_scope.distinct_value # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. column_name ||= reflection.klass.primary_key relation = relation.distinct end value = relation.count(column_name) limit = options[:limit] offset = options[:offset] if limit || offset [ [value - offset.to_i, 0].max, limit.to_i ].min else value end end # Removes +records+ from this association calling +before_remove+ and # +after_remove+ callbacks. # # This method is abstract in the sense that +delete_records+ has to be # provided by descendants. Note this method does not imply the records # are actually removed from the database, that depends precisely on # +delete_records+. They are in any case removed from the collection. def delete(*records) return if records.empty? _options = records.extract_options! dependent = _options[:dependent] || options[:dependent] records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) } delete_or_destroy(records, dependent) end # Deletes the +records+ and removes them from this association calling # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks. # # Note that this method removes records from the database ignoring the # +:dependent+ option. def destroy(*records) return if records.empty? records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) } delete_or_destroy(records, :destroy) end # Returns the size of the collection by executing a SELECT COUNT(*) # query if the collection hasn't been loaded, and calling # collection.size if it has. # # If the collection has been already loaded +size+ and +length+ are # equivalent. If not and you are going to need the records anyway # +length+ will take one less query. Otherwise +size+ is more efficient. # # This method is abstract in the sense that it relies on # +count_records+, which is a method descendants have to provide. def size if !find_target? || loaded? if association_scope.distinct_value target.uniq.size else target.size end elsif !loaded? && !association_scope.group_values.empty? load_target.size elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array) unsaved_records = target.select { |r| r.new_record? } unsaved_records.size + count_records else count_records end end # Returns the size of the collection calling +size+ on the target. # # If the collection has been already loaded +length+ and +size+ are # equivalent. If not and you are going to need the records anyway this # method will take one less query. Otherwise +size+ is more efficient. def length load_target.size end # Returns true if the collection is empty. # # If the collection has been loaded # it is equivalent to collection.size.zero?. If the # collection has not been loaded, it is equivalent to # collection.exists?. If the collection has not already been # loaded and you are going to fetch the records anyway it is better to # check collection.length.zero?. def empty? if loaded? size.zero? else @target.blank? && !scope.exists? end end # Returns true if the collections is not empty. # Equivalent to +!collection.empty?+. def any? if block_given? load_target.any? { |*block_args| yield(*block_args) } else !empty? end end # Returns true if the collection has more than 1 record. # Equivalent to +collection.size > 1+. def many? if block_given? load_target.many? { |*block_args| yield(*block_args) } else size > 1 end end def distinct seen = {} load_target.find_all do |record| seen[record.id] = true unless seen.key?(record.id) end end alias uniq distinct # Replace this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. def replace(other_array) other_array.each { |val| raise_on_type_mismatch!(val) } original_target = load_target.dup if owner.new_record? replace_records(other_array, original_target) else replace_common_records_in_memory(other_array, original_target) if other_array != original_target transaction { replace_records(other_array, original_target) } end end end def include?(record) if record.is_a?(reflection.klass) if record.new_record? include_in_memory?(record) else loaded? ? target.include?(record) : scope.exists?(record.id) end else false end end def load_target if find_target? @target = merge_target_lists(find_target, target) end loaded! target end def add_to_target(record, skip_callbacks = false, &block) if association_scope.distinct_value index = @target.index(record) end replace_on_target(record, index, skip_callbacks, &block) end def replace_on_target(record, index, skip_callbacks) callback(:before_add, record) unless skip_callbacks yield(record) if block_given? if index @target[index] = record else @target << record end callback(:after_add, record) unless skip_callbacks set_inverse_instance(record) record end def scope(opts = {}) scope = super() scope.none! if opts.fetch(:nullify, true) && null_scope? scope end def null_scope? owner.new_record? && !foreign_key_present? end private def get_records return scope.to_a if skip_statement_cache? conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do StatementCache.create(conn) { |params| as = AssociationScope.create { params.bind } target_scope.merge as.scope(self, conn) } end binds = AssociationScope.get_bind_values(owner, reflection.chain) sc.execute binds, klass, klass.connection end def find_target records = get_records records.each { |record| set_inverse_instance(record) } records end # We have some records loaded from the database (persisted) and some that are # in-memory (memory). The same record may be represented in the persisted array # and in the memory array. # # So the task of this method is to merge them according to the following rules: # # * The final array must not have duplicates # * The order of the persisted array is to be preserved # * Any changes made to attributes on objects in the memory array are to be preserved # * Otherwise, attributes should have the value found in the database def merge_target_lists(persisted, memory) return persisted if memory.empty? return memory if persisted.empty? persisted.map! do |record| if mem_record = memory.delete(record) ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name| mem_record[name] = record[name] end mem_record else record end end persisted + memory end def _create_record(attributes, raise = false, &block) unless owner.persisted? raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end if attributes.is_a?(Array) attributes.collect { |attr| _create_record(attr, raise, &block) } else transaction do add_to_target(build_record(attributes)) do |record| yield(record) if block_given? insert_record(record, true, raise) end end end end # Do the relevant stuff to insert the given record into the association collection. def insert_record(record, validate = true, raise = false) raise NotImplementedError end def create_scope scope.scope_for_create.stringify_keys end def delete_or_destroy(records, method) records = records.flatten records.each { |record| raise_on_type_mismatch!(record) } existing_records = records.reject { |r| r.new_record? } if existing_records.empty? remove_records(existing_records, records, method) else transaction { remove_records(existing_records, records, method) } end end def remove_records(existing_records, records, method) records.each { |record| callback(:before_remove, record) } delete_records(existing_records, method) if existing_records.any? records.each { |record| target.delete(record) } records.each { |record| callback(:after_remove, record) } end # Delete the given records from the association, using one of the methods :destroy, # :delete_all or :nullify (or nil, in which case a default is used). def delete_records(records, method) raise NotImplementedError end def replace_records(new_target, original_target) delete(target - new_target) unless concat(new_target - target) @target = original_target raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ "new records could not be saved." end target end def replace_common_records_in_memory(new_target, original_target) common_records = new_target & original_target common_records.each do |record| skip_callbacks = true replace_on_target(record, @target.index(record), skip_callbacks) end end def concat_records(records, should_raise = false) result = true records.flatten.each do |record| raise_on_type_mismatch!(record) add_to_target(record) do |rec| result &&= insert_record(rec, true, should_raise) unless owner.new_record? end end result && records end def callback(method, record) callbacks_for(method).each do |callback| callback.call(method, owner, record) end end def callbacks_for(callback_name) full_callback_name = "#{callback_name}_for_#{reflection.name}" owner.class.send(full_callback_name) end # Should we deal with assoc.first or assoc.last by issuing an independent query to # the database, or by getting the target, and then taking the first/last item from that? # # If the args is just a non-empty options hash, go to the database. # # Otherwise, go to the database only if none of the following are true: # * target already loaded # * owner is new record # * target contains new or changed record(s) def fetch_first_nth_or_last_using_find?(args) if args.first.is_a?(Hash) true else !(loaded? || owner.new_record? || target.any? { |record| record.new_record? || record.changed? }) end end def include_in_memory?(record) if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) assoc = owner.association(reflection.through_reflection.name) assoc.reader.any? { |source| target_association = source.send(reflection.source_reflection.name) if target_association.respond_to?(:include?) target_association.include?(record) else target_association == record end } || target.include?(record) else target.include?(record) end end # If the :inverse_of option has been # specified, then #find scans the entire collection. def find_by_scan(*args) expects_array = args.first.kind_of?(Array) ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq if ids.size == 1 id = ids.first record = load_target.detect { |r| id == r.id.to_s } expects_array ? [ record ] : record else load_target.select { |r| ids.include?(r.id.to_s) } end end # Fetches the first/last using SQL if possible, otherwise from the target array. def first_nth_or_last(type, *args) args.shift if args.first.is_a?(Hash) && args.first.empty? collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target collection.send(type, *args).tap do |record| set_inverse_instance record if record.is_a? ActiveRecord::Base end end end end end rails-4.2.6/activerecord/lib/active_record/associations/collection_proxy.rb000066400000000000000000001053731266740050600273250ustar00rootroot00000000000000module ActiveRecord module Associations # Association proxies in Active Record are middlemen between the object that # holds the association, known as the @owner, and the actual associated # object, known as the @target. The kind of association any proxy is # about is available in @reflection. That's an instance of the class # ActiveRecord::Reflection::AssociationReflection. # # For example, given # # class Blog < ActiveRecord::Base # has_many :posts # end # # blog = Blog.first # # the association proxy in blog.posts has the object in +blog+ as # @owner, the collection of its posts as @target, and # the @reflection object represents a :has_many macro. # # This class delegates unknown methods to @target via # method_missing. # # The @target object is not \loaded until needed. For example, # # blog.posts.count # # is computed directly through SQL and does not trigger by itself the # instantiation of the actual post records. class CollectionProxy < Relation delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope) delegate :find_nth, to: :scope def initialize(klass, association) #:nodoc: @association = association super klass, klass.arel_table merge! association.scope(nullify: false) end def target @association.target end def load_target @association.load_target end # Returns +true+ if the association has been loaded, otherwise +false+. # # person.pets.loaded? # => false # person.pets # person.pets.loaded? # => true def loaded? @association.loaded? end # Works in two ways. # # *First:* Specify a subset of fields to be selected from the result set. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.select(:name) # # => [ # # #, # # #, # # # # # ] # # person.pets.select(:id, :name ) # # => [ # # #, # # #, # # # # # ] # # Be careful because this also means you're initializing a model # object with only the fields that you've selected. If you attempt # to access a field except +id+ that is not in the initialized record you'll # receive: # # person.pets.select(:name).first.person_id # # => ActiveModel::MissingAttributeError: missing attribute: person_id # # *Second:* You can pass a block so it can be used just like Array#select. # This builds an array of objects from the database for the scope, # converting them into an array and iterating through them using # Array#select. # # person.pets.select { |pet| pet.name =~ /oo/ } # # => [ # # #, # # # # # ] # # person.pets.select(:name) { |pet| pet.name =~ /oo/ } # # => [ # # #, # # # # # ] def select(*fields, &block) @association.select(*fields, &block) end # Finds an object in the collection responding to the +id+. Uses the same # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound # error if the object cannot be found. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.find(1) # => # # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4 # # person.pets.find(2) { |pet| pet.name.downcase! } # # => # # # person.pets.find(2, 3) # # => [ # # #, # # # # # ] def find(*args, &block) @association.find(*args, &block) end # Returns the first record, or the first +n+ records, from the collection. # If the collection is empty, the first form returns +nil+, and the second # form returns an empty array. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.first # => # # # person.pets.first(2) # # => [ # # #, # # # # # ] # # another_person_without.pets # => [] # another_person_without.pets.first # => nil # another_person_without.pets.first(3) # => [] def first(*args) @association.first(*args) end # Same as +first+ except returns only the second record. def second(*args) @association.second(*args) end # Same as +first+ except returns only the third record. def third(*args) @association.third(*args) end # Same as +first+ except returns only the fourth record. def fourth(*args) @association.fourth(*args) end # Same as +first+ except returns only the fifth record. def fifth(*args) @association.fifth(*args) end # Same as +first+ except returns only the forty second record. # Also known as accessing "the reddit". def forty_two(*args) @association.forty_two(*args) end # Returns the last record, or the last +n+ records, from the collection. # If the collection is empty, the first form returns +nil+, and the second # form returns an empty array. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.last # => # # # person.pets.last(2) # # => [ # # #, # # # # # ] # # another_person_without.pets # => [] # another_person_without.pets.last # => nil # another_person_without.pets.last(3) # => [] def last(*args) @association.last(*args) end def take(n = nil) @association.take(n) end # Returns a new object of the collection type that has been instantiated # with +attributes+ and linked to this object, but have not yet been saved. # You can pass an array of attributes hashes, this will return an array # with the new objects. # # class Person # has_many :pets # end # # person.pets.build # # => # # # person.pets.build(name: 'Fancy-Fancy') # # => # # # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}]) # # => [ # # #, # # #, # # # # # ] # # person.pets.size # => 5 # size of the collection # person.pets.count # => 0 # count from database def build(attributes = {}, &block) @association.build(attributes, &block) end alias_method :new, :build # Returns a new object of the collection type that has been instantiated with # attributes, linked to this object and that has already been saved (if it # passes the validations). # # class Person # has_many :pets # end # # person.pets.create(name: 'Fancy-Fancy') # # => # # # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}]) # # => [ # # #, # # # # # ] # # person.pets.size # => 3 # person.pets.count # => 3 # # person.pets.find(1, 2, 3) # # => [ # # #, # # #, # # # # # ] def create(attributes = {}, &block) @association.create(attributes, &block) end # Like +create+, except that if the record is invalid, raises an exception. # # class Person # has_many :pets # end # # class Pet # validates :name, presence: true # end # # person.pets.create!(name: nil) # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank def create!(attributes = {}, &block) @association.create!(attributes, &block) end # Add one or more records to the collection by setting their foreign keys # to the association's primary key. Since << flattens its argument list and # inserts each record, +push+ and +concat+ behave identically. Returns +self+ # so method calls may be chained. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.size # => 0 # person.pets.concat(Pet.new(name: 'Fancy-Fancy')) # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')) # person.pets.size # => 3 # # person.id # => 1 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')]) # person.pets.size # => 5 def concat(*records) @association.concat(*records) end # Replaces this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # # => [#] # # other_pets = [Pet.new(name: 'Puff', group: 'celebrities'] # # person.pets.replace(other_pets) # # person.pets # # => [#] # # If the supplied array has an incorrect association type, it raises # an ActiveRecord::AssociationTypeMismatch error: # # person.pets.replace(["doo", "ggie", "gaga"]) # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String def replace(other_array) @association.replace(other_array) end # Deletes all the records from the collection according to the strategy # specified by the +:dependent+ option. If no +:dependent+ option is given, # then it will follow the default strategy. # # For +has_many :through+ associations, the default deletion strategy is # +:delete_all+. # # For +has_many+ associations, the default deletion strategy is +:nullify+. # This sets the foreign keys to +NULL+. # # class Person < ActiveRecord::Base # has_many :pets # dependent: :nullify option by default # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.delete_all # # => [ # # #, # # #, # # # # # ] # # person.pets.size # => 0 # person.pets # => [] # # Pet.find(1, 2, 3) # # => [ # # #, # # #, # # # # # ] # # Both +has_many+ and +has_many :through+ dependencies default to the # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+. # Records are not instantiated and callbacks will not be fired. # # class Person < ActiveRecord::Base # has_many :pets, dependent: :destroy # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.delete_all # # Pet.find(1, 2, 3) # # => ActiveRecord::RecordNotFound # # If it is set to :delete_all, all the objects are deleted # *without* calling their +destroy+ method. # # class Person < ActiveRecord::Base # has_many :pets, dependent: :delete_all # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.delete_all # # Pet.find(1, 2, 3) # # => ActiveRecord::RecordNotFound def delete_all(dependent = nil) @association.delete_all(dependent) end # Deletes the records of the collection directly from the database # ignoring the +:dependent+ option. Records are instantiated and it # invokes +before_remove+, +after_remove+ , +before_destroy+ and # +after_destroy+ callbacks. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.destroy_all # # person.pets.size # => 0 # person.pets # => [] # # Pet.find(1) # => Couldn't find Pet with id=1 def destroy_all @association.destroy_all end # Deletes the +records+ supplied from the collection according to the strategy # specified by the +:dependent+ option. If no +:dependent+ option is given, # then it will follow the default strategy. Returns an array with the # deleted records. # # For +has_many :through+ associations, the default deletion strategy is # +:delete_all+. # # For +has_many+ associations, the default deletion strategy is +:nullify+. # This sets the foreign keys to +NULL+. # # class Person < ActiveRecord::Base # has_many :pets # dependent: :nullify option by default # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.delete(Pet.find(1)) # # => [#] # # person.pets.size # => 2 # person.pets # # => [ # # #, # # # # # ] # # Pet.find(1) # # => # # # If it is set to :destroy all the +records+ are removed by calling # their +destroy+ method. See +destroy+ for more information. # # class Person < ActiveRecord::Base # has_many :pets, dependent: :destroy # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.delete(Pet.find(1), Pet.find(3)) # # => [ # # #, # # # # # ] # # person.pets.size # => 1 # person.pets # # => [#] # # Pet.find(1, 3) # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3) # # If it is set to :delete_all, all the +records+ are deleted # *without* calling their +destroy+ method. # # class Person < ActiveRecord::Base # has_many :pets, dependent: :delete_all # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.delete(Pet.find(1)) # # => [#] # # person.pets.size # => 2 # person.pets # # => [ # # #, # # # # # ] # # Pet.find(1) # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1 # # You can pass +Fixnum+ or +String+ values, it finds the records # responding to the +id+ and executes delete on them. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.delete("1") # # => [#] # # person.pets.delete(2, 3) # # => [ # # #, # # # # # ] def delete(*records) @association.delete(*records) end # Destroys the +records+ supplied and removes them from the collection. # This method will _always_ remove record from the database ignoring # the +:dependent+ option. Returns an array with the removed records. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.destroy(Pet.find(1)) # # => [#] # # person.pets.size # => 2 # person.pets # # => [ # # #, # # # # # ] # # person.pets.destroy(Pet.find(2), Pet.find(3)) # # => [ # # #, # # # # # ] # # person.pets.size # => 0 # person.pets # => [] # # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3) # # You can pass +Fixnum+ or +String+ values, it finds the records # responding to the +id+ and then deletes them from the database. # # person.pets.size # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.destroy("4") # # => # # # person.pets.size # => 2 # person.pets # # => [ # # #, # # # # # ] # # person.pets.destroy(5, 6) # # => [ # # #, # # # # # ] # # person.pets.size # => 0 # person.pets # => [] # # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6) def destroy(*records) @association.destroy(*records) end # Specifies whether the records should be unique or not. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.select(:name) # # => [ # # #, # # # # # ] # # person.pets.select(:name).distinct # # => [#] def distinct @association.distinct end alias uniq distinct # Count all records using SQL. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.count # => 3 # person.pets # # => [ # # #, # # #, # # # # # ] def count(column_name = nil, options = {}) # TODO: Remove options argument as soon we remove support to # activerecord-deprecated_finders. @association.count(column_name, options) end # Returns the size of the collection. If the collection hasn't been loaded, # it executes a SELECT COUNT(*) query. Else it calls collection.size. # # If the collection has been already loaded +size+ and +length+ are # equivalent. If not and you are going to need the records anyway # +length+ will take one less query. Otherwise +size+ is more efficient. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.size # => 3 # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1 # # person.pets # This will execute a SELECT * FROM query # # => [ # # #, # # #, # # # # # ] # # person.pets.size # => 3 # # Because the collection is already loaded, this will behave like # # collection.size and no SQL count query is executed. def size @association.size end # Returns the size of the collection calling +size+ on the target. # If the collection has been already loaded, +length+ and +size+ are # equivalent. If not and you are going to need the records anyway this # method will take one less query. Otherwise +size+ is more efficient. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.length # => 3 # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1 # # # Because the collection is loaded, you can # # call the collection with no additional queries: # person.pets # # => [ # # #, # # #, # # # # # ] def length @association.length end # Returns +true+ if the collection is empty. If the collection has been # loaded it is equivalent # to collection.size.zero?. If the collection has not been loaded, # it is equivalent to collection.exists?. If the collection has # not already been loaded and you are going to fetch the records anyway it # is better to check collection.length.zero?. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.count # => 1 # person.pets.empty? # => false # # person.pets.delete_all # # person.pets.count # => 0 # person.pets.empty? # => true def empty? @association.empty? end # Returns +true+ if the collection is not empty. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.count # => 0 # person.pets.any? # => false # # person.pets << Pet.new(name: 'Snoop') # person.pets.count # => 0 # person.pets.any? # => true # # You can also pass a +block+ to define criteria. The behavior # is the same, it returns true if the collection based on the # criteria is not empty. # # person.pets # # => [#] # # person.pets.any? do |pet| # pet.group == 'cats' # end # # => false # # person.pets.any? do |pet| # pet.group == 'dogs' # end # # => true def any?(&block) @association.any?(&block) end # Returns true if the collection has more than one record. # Equivalent to collection.size > 1. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.count # => 1 # person.pets.many? # => false # # person.pets << Pet.new(name: 'Snoopy') # person.pets.count # => 2 # person.pets.many? # => true # # You can also pass a +block+ to define criteria. The # behavior is the same, it returns true if the collection # based on the criteria has more than one record. # # person.pets # # => [ # # #, # # #, # # # # # ] # # person.pets.many? do |pet| # pet.group == 'dogs' # end # # => false # # person.pets.many? do |pet| # pet.group == 'cats' # end # # => true def many?(&block) @association.many?(&block) end # Returns +true+ if the given +record+ is present in the collection. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # => [#] # # person.pets.include?(Pet.find(20)) # => true # person.pets.include?(Pet.find(21)) # => false def include?(record) !!@association.include?(record) end def arel scope.arel end def proxy_association @association end # We don't want this object to be put on the scoping stack, because # that could create an infinite loop where we call an @association # method, which gets the current scope, which is this object, which # delegates to @association, and so on. def scoping @association.scope.scoping { yield } end # Returns a Relation object for the records in this association def scope @association.scope end alias spawn scope # Equivalent to Array#==. Returns +true+ if the two arrays # contain the same number of elements and if each element is equal # to the corresponding element in the +other+ array, otherwise returns # +false+. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # # => [ # # #, # # # # # ] # # other = person.pets.to_ary # # person.pets == other # # => true # # other = [Pet.new(id: 1), Pet.new(id: 2)] # # person.pets == other # # => false def ==(other) load_target == other end # Returns a new array of objects from the collection. If the collection # hasn't been loaded, it fetches the records from the database. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # # => [ # # #, # # #, # # # # # ] # # other_pets = person.pets.to_ary # # => [ # # #, # # #, # # # # # ] # # other_pets.replace([Pet.new(name: 'BooGoo')]) # # other_pets # # => [#] # # person.pets # # This is not affected by replace # # => [ # # #, # # #, # # # # # ] def to_ary load_target.dup end alias_method :to_a, :to_ary # Adds one or more +records+ to the collection by setting their foreign keys # to the association's primary key. Returns +self+, so several appends may be # chained together. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.size # => 0 # person.pets << Pet.new(name: 'Fancy-Fancy') # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')] # person.pets.size # => 3 # # person.id # => 1 # person.pets # # => [ # # #, # # #, # # # # # ] def <<(*records) proxy_association.concat(records) && self end alias_method :push, :<< alias_method :append, :<< def prepend(*args) raise NoMethodError, "prepend on association is not defined. Please use << or append" end # Equivalent to +delete_all+. The difference is that returns +self+, instead # of an array with the deleted objects, so methods can be chained. See # +delete_all+ for more information. def clear delete_all self end # Reloads the collection from the database. Returns +self+. # Equivalent to collection(true). # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # fetches pets from the database # # => [#] # # person.pets # uses the pets cache # # => [#] # # person.pets.reload # fetches pets from the database # # => [#] # # person.pets(true) # fetches pets from the database # # => [#] def reload proxy_association.reload self end # Unloads the association. Returns +self+. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets # fetches pets from the database # # => [#] # # person.pets # uses the pets cache # # => [#] # # person.pets.reset # clears the pets cache # # person.pets # fetches pets from the database # # => [#] def reset proxy_association.reset proxy_association.reset_scope self end end end end rails-4.2.6/activerecord/lib/active_record/associations/foreign_association.rb000066400000000000000000000003751266740050600277520ustar00rootroot00000000000000module ActiveRecord::Associations module ForeignAssociation def foreign_key_present? if reflection.klass.primary_key owner.attribute_present?(reflection.active_record_primary_key) else false end end end end rails-4.2.6/activerecord/lib/active_record/associations/has_many_association.rb000066400000000000000000000163251266740050600301220ustar00rootroot00000000000000module ActiveRecord # = Active Record Has Many Association module Associations # This is the proxy that handles a has many association. # # If the association has a :through option further specialization # is provided by its child HasManyThroughAssociation. class HasManyAssociation < CollectionAssociation #:nodoc: include ForeignAssociation def handle_dependency case options[:dependent] when :restrict_with_exception raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty? when :restrict_with_error unless empty? record = klass.human_attribute_name(reflection.name).downcase owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record) false end else if options[:dependent] == :destroy # No point in executing the counter update since we're going to destroy the parent anyway load_target.each { |t| t.destroyed_by_association = reflection } destroy_all else delete_all end end end def insert_record(record, validate = true, raise = false) set_owner_attributes(record) set_inverse_instance(record) if raise record.save!(:validate => validate) else record.save(:validate => validate) end end def empty? if has_cached_counter? size.zero? else super end end private # Returns the number of records in this collection. # # If the association has a counter cache it gets that value. Otherwise # it will attempt to do a count via SQL, bounded to :limit if # there's one. Some configuration options like :group make it impossible # to do an SQL count, in those cases the array count will be used. # # That does not depend on whether the collection has already been loaded # or not. The +size+ method is the one that takes the loaded flag into # account and delegates to +count_records+ if needed. # # If the collection is empty the target is set to an empty array and # the loaded flag is set to true as well. def count_records count = if has_cached_counter? owner._read_attribute cached_counter_attribute_name else scope.count end # If there's nothing in the database and @target has no new records # we are certain the current target is an empty array. This is a # documented side-effect of the method that may avoid an extra SELECT. @target ||= [] and loaded! if count == 0 [association_scope.limit_value, count].compact.min end # Returns whether a counter cache should be used for this association. # # The counter_cache option must be given on either the owner or inverse # association, and the column must be present on the owner. def has_cached_counter?(reflection = reflection()) if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache] owner.attribute_present?(cached_counter_attribute_name(reflection)) end end def cached_counter_attribute_name(reflection = reflection()) if reflection.options[:counter_cache] reflection.options[:counter_cache].to_s else "#{reflection.name}_count" end end def update_counter(difference, reflection = reflection()) update_counter_in_database(difference, reflection) update_counter_in_memory(difference, reflection) end def update_counter_in_database(difference, reflection = reflection()) if has_cached_counter?(reflection) counter = cached_counter_attribute_name(reflection) owner.class.update_counters(owner.id, counter => difference) end end def update_counter_in_memory(difference, reflection = reflection()) if counter_must_be_updated_by_has_many?(reflection) counter = cached_counter_attribute_name(reflection) owner[counter] += difference owner.send(:clear_attribute_changes, counter) # eww end end # This shit is nasty. We need to avoid the following situation: # # * An associated record is deleted via record.destroy # * Hence the callbacks run, and they find a belongs_to on the record with a # :counter_cache options which points back at our owner. So they update the # counter cache. # * In which case, we must make sure to *not* update the counter cache, or else # it will be decremented twice. # # Hence this method. def inverse_which_updates_counter_cache(reflection = reflection()) counter_name = cached_counter_attribute_name(reflection) inverse_which_updates_counter_named(counter_name, reflection) end alias inverse_updates_counter_cache? inverse_which_updates_counter_cache def inverse_which_updates_counter_named(counter_name, reflection) reflection.klass._reflections.values.find { |inverse_reflection| inverse_reflection.belongs_to? && inverse_reflection.counter_cache_column == counter_name } end alias inverse_updates_counter_named? inverse_which_updates_counter_named def inverse_updates_counter_in_memory?(reflection) inverse = inverse_which_updates_counter_cache(reflection) inverse && inverse == reflection.inverse_of end def counter_must_be_updated_by_has_many?(reflection) !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection) end def delete_count(method, scope) if method == :delete_all scope.delete_all else scope.update_all(reflection.foreign_key => nil) end end def delete_or_nullify_all_records(method) count = delete_count(method, self.scope) update_counter(-count) end # Deletes the records according to the :dependent option. def delete_records(records, method) if method == :destroy records.each(&:destroy!) update_counter(-records.length) unless inverse_updates_counter_cache? else scope = self.scope.where(reflection.klass.primary_key => records) update_counter(-delete_count(method, scope)) end end def concat_records(records, *) update_counter_if_success(super, records.length) end def _create_record(attributes, *) if attributes.is_a?(Array) super else update_counter_if_success(super, 1) end end def update_counter_if_success(saved_successfully, difference) if saved_successfully update_counter_in_memory(difference) end saved_successfully end end end end rails-4.2.6/activerecord/lib/active_record/associations/has_many_through_association.rb000066400000000000000000000166201266740050600316600ustar00rootroot00000000000000require 'active_support/core_ext/string/filters' module ActiveRecord # = Active Record Has Many Through Association module Associations class HasManyThroughAssociation < HasManyAssociation #:nodoc: include ThroughAssociation def initialize(owner, reflection) super @through_records = {} @through_association = nil end # Returns the size of the collection by executing a SELECT COUNT(*) query # if the collection hasn't been loaded, and by calling collection.size if # it has. If the collection will likely have a size greater than zero, # and if fetching the collection will be needed afterwards, one less # SELECT query will be generated by using #length instead. def size if has_cached_counter? owner._read_attribute cached_counter_attribute_name(reflection) elsif loaded? target.size else super end end def concat(*records) unless owner.new_record? records.flatten.each do |record| raise_on_type_mismatch!(record) end end super end def concat_records(records) ensure_not_nested records = super(records, true) if owner.new_record? && records records.flatten.each do |record| build_through_record(record) end end records end def insert_record(record, validate = true, raise = false) ensure_not_nested if record.new_record? if raise record.save!(:validate => validate) else return unless record.save(:validate => validate) end end save_through_record(record) if has_cached_counter? && !through_reflection_updates_counter_cache? ActiveSupport::Deprecation.warn(<<-MSG.squish) Automatic updating of counter caches on through associations has been deprecated, and will be removed in Rails 5. Instead, please set the appropriate `counter_cache` options on the `has_many` and `belongs_to` for your associations to #{through_reflection.name}. MSG update_counter_in_database(1) end record end private def through_association @through_association ||= owner.association(through_reflection.name) end # The through record (built with build_record) is temporarily cached # so that it may be reused if insert_record is subsequently called. # # However, after insert_record has been called, the cache is cleared in # order to allow multiple instances of the same record in an association. def build_through_record(record) @through_records[record.object_id] ||= begin ensure_mutable through_record = through_association.build(*options_for_through_record) through_record.send("#{source_reflection.name}=", record) if options[:source_type] through_record.send("#{source_reflection.foreign_type}=", options[:source_type]) end through_record end end def options_for_through_record [through_scope_attributes] end def through_scope_attributes scope.where_values_hash(through_association.reflection.name.to_s). except!(through_association.reflection.foreign_key, through_association.reflection.klass.inheritance_column) end def save_through_record(record) build_through_record(record).save! ensure @through_records.delete(record.object_id) end def build_record(attributes) ensure_not_nested record = super(attributes) inverse = source_reflection.inverse_of if inverse if inverse.collection? record.send(inverse.name) << build_through_record(record) elsif inverse.has_one? record.send("#{inverse.name}=", build_through_record(record)) end end record end def target_reflection_has_associated_record? !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?) end def update_through_counter?(method) case method when :destroy !inverse_updates_counter_cache?(through_reflection) when :nullify false else true end end def delete_or_nullify_all_records(method) delete_records(load_target, method) end def delete_records(records, method) ensure_not_nested scope = through_association.scope scope.where! construct_join_attributes(*records) case method when :destroy if scope.klass.primary_key count = scope.destroy_all.length else scope.each do |record| record._run_destroy_callbacks end arel = scope.arel stmt = Arel::DeleteManager.new arel.engine stmt.from scope.klass.arel_table stmt.wheres = arel.constraints count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values) end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) else count = scope.delete_all end delete_through_records(records) if source_reflection.options[:counter_cache] && method != :destroy counter = source_reflection.counter_cache_column klass.decrement_counter counter, records.map(&:id) end if through_reflection.collection? && update_through_counter?(method) update_counter(-count, through_reflection) else update_counter(-count) end end def through_records_for(record) attributes = construct_join_attributes(record) candidates = Array.wrap(through_association.target) candidates.find_all do |c| attributes.all? do |key, value| c.public_send(key) == value end end end def delete_through_records(records) records.each do |record| through_records = through_records_for(record) if through_reflection.collection? through_records.each { |r| through_association.target.delete(r) } else if through_records.include?(through_association.target) through_association.target = nil end end @through_records.delete(record.object_id) end end def find_target return [] unless target_reflection_has_associated_record? get_records end # NOTE - not sure that we can actually cope with inverses here def invertible_for?(record) false end def has_cached_counter?(reflection = reflection()) owner.attribute_present?(cached_counter_attribute_name(reflection)) end def through_reflection_updates_counter_cache? counter_name = cached_counter_attribute_name inverse_updates_counter_named?(counter_name, through_reflection) end end end end rails-4.2.6/activerecord/lib/active_record/associations/has_one_association.rb000066400000000000000000000062021266740050600277300ustar00rootroot00000000000000module ActiveRecord # = Active Record Belongs To Has One Association module Associations class HasOneAssociation < SingularAssociation #:nodoc: include ForeignAssociation def handle_dependency case options[:dependent] when :restrict_with_exception raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target when :restrict_with_error if load_target record = klass.human_attribute_name(reflection.name).downcase owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record) false end else delete end end def replace(record, save = true) raise_on_type_mismatch!(record) if record load_target return self.target if !(target || record) assigning_another_record = target != record if assigning_another_record || record.changed? save &&= owner.persisted? transaction_if(save) do remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record if record set_owner_attributes(record) set_inverse_instance(record) if save && !record.save nullify_owner_attributes(record) set_owner_attributes(target) if target raise RecordNotSaved, "Failed to save the new associated #{reflection.name}." end end end end self.target = record end def delete(method = options[:dependent]) if load_target case method when :delete target.delete when :destroy target.destroy when :nullify target.update_columns(reflection.foreign_key => nil) end end end private # The reason that the save param for replace is false, if for create (not just build), # is because the setting of the foreign keys is actually handled by the scoping when # the record is instantiated, and so they are set straight away and do not need to be # updated within replace. def set_new_record(record) replace(record, false) end def remove_target!(method) case method when :delete target.delete when :destroy target.destroy else nullify_owner_attributes(target) if target.persisted? && owner.persisted? && !target.save set_owner_attributes(target) raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " + "The record failed to save after its foreign key was set to nil." end end end def nullify_owner_attributes(record) record[reflection.foreign_key] = nil end def transaction_if(value) if value reflection.klass.transaction { yield } else yield end end end end end rails-4.2.6/activerecord/lib/active_record/associations/has_one_through_association.rb000066400000000000000000000016661266740050600315010ustar00rootroot00000000000000module ActiveRecord # = Active Record Has One Through Association module Associations class HasOneThroughAssociation < HasOneAssociation #:nodoc: include ThroughAssociation def replace(record) create_through_record(record) self.target = record end private def create_through_record(record) ensure_not_nested through_proxy = owner.association(through_reflection.name) through_record = through_proxy.send(:load_target) if through_record && !record through_record.destroy elsif record attributes = construct_join_attributes(record) if through_record through_record.update(attributes) elsif owner.new_record? through_proxy.build(attributes) else through_proxy.create(attributes) end end end end end end rails-4.2.6/activerecord/lib/active_record/associations/join_dependency.rb000066400000000000000000000225451266740050600270650ustar00rootroot00000000000000module ActiveRecord module Associations class JoinDependency # :nodoc: autoload :JoinBase, 'active_record/associations/join_dependency/join_base' autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association' class Aliases # :nodoc: def initialize(tables) @tables = tables @alias_cache = tables.each_with_object({}) { |table,h| h[table.node] = table.columns.each_with_object({}) { |column,i| i[column.name] = column.alias } } @name_and_alias_cache = tables.each_with_object({}) { |table,h| h[table.node] = table.columns.map { |column| [column.name, column.alias] } } end def columns @tables.flat_map { |t| t.column_aliases } end # An array of [column_name, alias] pairs for the table def column_aliases(node) @name_and_alias_cache[node] end def column_alias(node, column) @alias_cache[node][column] end class Table < Struct.new(:node, :columns) def table Arel::Nodes::TableAlias.new node.table, node.aliased_table_name end def column_aliases t = table columns.map { |column| t[column.name].as Arel.sql column.alias } end end Column = Struct.new(:name, :alias) end attr_reader :alias_tracker, :base_klass, :join_root def self.make_tree(associations) hash = {} walk_tree associations, hash hash end def self.walk_tree(associations, hash) case associations when Symbol, String hash[associations.to_sym] ||= {} when Array associations.each do |assoc| walk_tree assoc, hash end when Hash associations.each do |k,v| cache = hash[k] ||= {} walk_tree v, cache end else raise ConfigurationError, associations.inspect end end # base is the base class on which operation is taking place. # associations is the list of associations which are joined using hash, symbol or array. # joins is the list of all string join commands and arel nodes. # # Example : # # class Physician < ActiveRecord::Base # has_many :appointments # has_many :patients, through: :appointments # end # # If I execute `@physician.patients.to_a` then # base # => Physician # associations # => [] # joins # => [# Physician # associations # => [:appointments] # joins # => [] # def initialize(base, associations, joins) @alias_tracker = AliasTracker.create(base.connection, joins) @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1 tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) @join_root.children.each { |child| construct_tables! @join_root, child } end def reflections join_root.drop(1).map!(&:reflection) end def join_constraints(outer_joins) joins = join_root.children.flat_map { |child| make_inner_joins join_root, child } joins.concat outer_joins.flat_map { |oj| if join_root.match? oj.join_root walk join_root, oj.join_root else oj.join_root.children.flat_map { |child| make_outer_joins oj.join_root, child } end } end def aliases Aliases.new join_root.each_with_index.map { |join_part,i| columns = join_part.column_names.each_with_index.map { |column_name,j| Aliases::Column.new column_name, "t#{i}_r#{j}" } Aliases::Table.new(join_part, columns) } end def instantiate(result_set, aliases) primary_key = aliases.column_alias(join_root, join_root.primary_key) seen = Hash.new { |h,parent_klass| h[parent_klass] = Hash.new { |i,parent_id| i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} } } } model_cache = Hash.new { |h,klass| h[klass] = {} } parents = model_cache[join_root] column_aliases = aliases.column_aliases join_root message_bus = ActiveSupport::Notifications.instrumenter payload = { record_count: result_set.length, class_name: join_root.base_klass.name } message_bus.instrument('instantiation.active_record', payload) do result_set.each { |row_hash| parent_key = primary_key ? row_hash[primary_key] : row_hash parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases) construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases) } end parents.values end private def make_constraints(parent, child, tables, join_type) chain = child.reflection.chain foreign_table = parent.table foreign_klass = parent.base_klass child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain) end def make_outer_joins(parent, child) tables = table_aliases_for(parent, child) join_type = Arel::Nodes::OuterJoin info = make_constraints parent, child, tables, join_type [info] + child.children.flat_map { |c| make_outer_joins(child, c) } end def make_inner_joins(parent, child) tables = child.tables join_type = Arel::Nodes::InnerJoin info = make_constraints parent, child, tables, join_type [info] + child.children.flat_map { |c| make_inner_joins(child, c) } end def table_aliases_for(parent, node) node.reflection.chain.map { |reflection| alias_tracker.aliased_table_for( reflection.table_name, table_alias_for(reflection, parent, reflection != node.reflection) ) } end def construct_tables!(parent, node) node.tables = table_aliases_for(parent, node) node.children.each { |child| construct_tables! node, child } end def table_alias_for(reflection, parent, join) name = "#{reflection.plural_name}_#{parent.table_name}" name << "_join" if join name end def walk(left, right) intersection, missing = right.children.map { |node1| [left.children.find { |node2| node1.match? node2 }, node1] }.partition(&:first) ojs = missing.flat_map { |_,n| make_outer_joins left, n } intersection.flat_map { |l,r| walk l, r }.concat ojs end def find_reflection(klass, name) klass._reflect_on_association(name) or raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?" end def build(associations, base_klass) associations.map do |name, right| reflection = find_reflection base_klass, name reflection.check_validity! reflection.check_eager_loadable! if reflection.polymorphic? raise EagerLoadPolymorphicError.new(reflection) end JoinAssociation.new reflection, build(right, reflection.klass) end end def construct(ar_parent, parent, row, rs, seen, model_cache, aliases) return if ar_parent.nil? primary_id = ar_parent.id parent.children.each do |node| if node.reflection.collection? other = ar_parent.association(node.reflection.name) other.loaded! else if ar_parent.association_cache.key?(node.reflection.name) model = ar_parent.association(node.reflection.name).target construct(model, node, row, rs, seen, model_cache, aliases) next end end key = aliases.column_alias(node, node.primary_key) id = row[key] if id.nil? nil_association = ar_parent.association(node.reflection.name) nil_association.loaded! next end model = seen[parent.base_klass][primary_id][node.base_klass][id] if model construct(model, node, row, rs, seen, model_cache, aliases) else model = construct_model(ar_parent, node, row, model_cache, id, aliases) seen[parent.base_klass][primary_id][node.base_klass][id] = model construct(model, node, row, rs, seen, model_cache, aliases) end end end def construct_model(record, node, row, model_cache, id, aliases) model = model_cache[node][id] ||= node.instantiate(row, aliases.column_aliases(node)) other = record.association(node.reflection.name) if node.reflection.collection? other.target.push(model) else other.target = model end other.set_inverse_instance(model) model end end end end rails-4.2.6/activerecord/lib/active_record/associations/join_dependency/000077500000000000000000000000001266740050600265305ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/associations/join_dependency/join_association.rb000066400000000000000000000075301266740050600324150ustar00rootroot00000000000000require 'active_record/associations/join_dependency/join_part' module ActiveRecord module Associations class JoinDependency # :nodoc: class JoinAssociation < JoinPart # :nodoc: # The reflection of the association represented attr_reader :reflection attr_accessor :tables def initialize(reflection, children) super(reflection.klass, children) @reflection = reflection @tables = nil end def match?(other) return true if self == other super && reflection == other.reflection end JoinInformation = Struct.new :joins, :binds def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain) joins = [] bind_values = [] tables = tables.reverse scope_chain_index = 0 scope_chain = scope_chain.reverse # The chain starts with the target table, but we want to end with it here (makes # more sense in this context), so we reverse chain.reverse_each do |reflection| table = tables.shift klass = reflection.klass join_keys = reflection.join_keys(klass) key = join_keys.key foreign_key = join_keys.foreign_key constraint = build_constraint(klass, table, key, foreign_table, foreign_key) scope_chain_items = scope_chain[scope_chain_index].map do |item| if item.is_a?(Relation) item else ActiveRecord::Relation.create(klass, table).instance_exec(node, &item) end end scope_chain_index += 1 scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right| left.merge right end if rel && !rel.arel.constraints.empty? bind_values.concat rel.bind_values constraint = constraint.and rel.arel.constraints end if reflection.type value = foreign_klass.base_class.name column = klass.columns_hash[reflection.type.to_s] substitute = klass.connection.substitute_at(column) bind_values.push [column, value] constraint = constraint.and table[reflection.type].eq substitute end joins << table.create_join(table, table.create_on(constraint), join_type) # The current table in this iteration becomes the foreign table in the next foreign_table, foreign_klass = table, klass end JoinInformation.new joins, bind_values end # Builds equality condition. # # Example: # # class Physician < ActiveRecord::Base # has_many :appointments # end # # If I execute `Physician.joins(:appointments).to_a` then # klass # => Physician # table # => # # key # => physician_id # foreign_table # => # # foreign_key # => id # def build_constraint(klass, table, key, foreign_table, foreign_key) constraint = table[key].eq(foreign_table[foreign_key]) if klass.finder_needs_type_condition? constraint = table.create_and([ constraint, klass.send(:type_condition, table) ]) end constraint end def table tables.first end def aliased_table_name table.table_alias || table.name end end end end end rails-4.2.6/activerecord/lib/active_record/associations/join_dependency/join_base.rb000066400000000000000000000007351266740050600310130ustar00rootroot00000000000000require 'active_record/associations/join_dependency/join_part' module ActiveRecord module Associations class JoinDependency # :nodoc: class JoinBase < JoinPart # :nodoc: def match?(other) return true if self == other super && base_klass == other.base_klass end def table base_klass.arel_table end def aliased_table_name base_klass.table_name end end end end end rails-4.2.6/activerecord/lib/active_record/associations/join_dependency/join_part.rb000066400000000000000000000042551266740050600310500ustar00rootroot00000000000000module ActiveRecord module Associations class JoinDependency # :nodoc: # A JoinPart represents a part of a JoinDependency. It is inherited # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which # everything else is being joined onto. A JoinAssociation represents an association which # is joining to the base. A JoinAssociation may result in more than one actual join # operations (for example a has_and_belongs_to_many JoinAssociation would result in # two; one for the join table and one for the target table). class JoinPart # :nodoc: include Enumerable # The Active Record class which this join part is associated 'about'; for a JoinBase # this is the actual base model, for a JoinAssociation this is the target model of the # association. attr_reader :base_klass, :children delegate :table_name, :column_names, :primary_key, :to => :base_klass def initialize(base_klass, children) @base_klass = base_klass @children = children end def name reflection.name end def match?(other) self.class == other.class end def each(&block) yield self children.each { |child| child.each(&block) } end # An Arel::Table for the active_record def table raise NotImplementedError end # The alias for the active_record's table def aliased_table_name raise NotImplementedError end def extract_record(row, column_names_with_alias) # This code is performance critical as it is called per row. # see: https://github.com/rails/rails/pull/12185 hash = {} index = 0 length = column_names_with_alias.length while index < length column_name, alias_name = column_names_with_alias[index] hash[column_name] = row[alias_name] index += 1 end hash end def instantiate(row, aliases) base_klass.instantiate(extract_record(row, aliases)) end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader.rb000066400000000000000000000174501266740050600257040ustar00rootroot00000000000000module ActiveRecord module Associations # Implements the details of eager loading of Active Record associations. # # Suppose that you have the following two Active Record models: # # class Author < ActiveRecord::Base # # columns: name, age # has_many :books # end # # class Book < ActiveRecord::Base # # columns: title, sales, author_id # end # # When you load an author with all associated books Active Record will make # multiple queries like this: # # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a # # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer') # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5) # # Active Record saves the ids of the records from the first query to use in # the second. Depending on the number of associations involved there can be # arbitrarily many SQL queries made. # # However, if there is a WHERE clause that spans across tables Active # Record will fall back to a slightly more resource-intensive single query: # # Author.includes(:books).where(books: {title: 'Illiad'}).to_a # => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2, # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2 # FROM `authors` # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id` # WHERE `books`.`title` = 'Illiad' # # This could result in many rows that contain redundant data and it performs poorly at scale # and is therefore only used when necessary. # class Preloader #:nodoc: extend ActiveSupport::Autoload eager_autoload do autoload :Association, 'active_record/associations/preloader/association' autoload :SingularAssociation, 'active_record/associations/preloader/singular_association' autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association' autoload :ThroughAssociation, 'active_record/associations/preloader/through_association' autoload :HasMany, 'active_record/associations/preloader/has_many' autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through' autoload :HasOne, 'active_record/associations/preloader/has_one' autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through' autoload :BelongsTo, 'active_record/associations/preloader/belongs_to' end # Eager loads the named associations for the given Active Record record(s). # # In this description, 'association name' shall refer to the name passed # to an association creation method. For example, a model that specifies # belongs_to :author, has_many :buyers has association # names +:author+ and +:buyers+. # # == Parameters # +records+ is an array of ActiveRecord::Base. This array needs not be flat, # i.e. +records+ itself may also contain arrays of records. In any case, # +preload_associations+ will preload the all associations records by # flattening +records+. # # +associations+ specifies one or more associations that you want to # preload. It may be: # - a Symbol or a String which specifies a single association name. For # example, specifying +:books+ allows this method to preload all books # for an Author. # - an Array which specifies multiple association names. This array # is processed recursively. For example, specifying [:avatar, :books] # allows this method to preload an author's avatar as well as all of his # books. # - a Hash which specifies multiple association names, as well as # association names for the to-be-preloaded association objects. For # example, specifying { author: :avatar } will preload a # book's author, as well as that author's avatar. # # +:associations+ has the same format as the +:include+ option for # ActiveRecord::Base.find. So +associations+ could look like this: # # :books # [ :books, :author ] # { author: :avatar } # [ :books, { author: :avatar } ] NULL_RELATION = Struct.new(:values, :bind_values).new({}, []) def preload(records, associations, preload_scope = nil) records = Array.wrap(records).compact.uniq associations = Array.wrap(associations) preload_scope = preload_scope || NULL_RELATION if records.empty? [] else associations.flat_map { |association| preloaders_on association, records, preload_scope } end end private def preloaders_on(association, records, scope) case association when Hash preloaders_for_hash(association, records, scope) when Symbol preloaders_for_one(association, records, scope) when String preloaders_for_one(association.to_sym, records, scope) else raise ArgumentError, "#{association.inspect} was not recognised for preload" end end def preloaders_for_hash(association, records, scope) association.flat_map { |parent, child| loaders = preloaders_for_one parent, records, scope recs = loaders.flat_map(&:preloaded_records).uniq loaders.concat Array.wrap(child).flat_map { |assoc| preloaders_on assoc, recs, scope } loaders } end # Not all records have the same class, so group then preload group on the reflection # itself so that if various subclass share the same association then we do not split # them unnecessarily # # Additionally, polymorphic belongs_to associations can have multiple associated # classes, depending on the polymorphic_type field. So we group by the classes as # well. def preloaders_for_one(association, records, scope) grouped_records(association, records).flat_map do |reflection, klasses| klasses.map do |rhs_klass, rs| loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope) loader.run self loader end end end def grouped_records(association, records) h = {} records.each do |record| next unless record assoc = record.association(association) klasses = h[assoc.reflection] ||= {} (klasses[assoc.klass] ||= []) << record end h end class AlreadyLoaded # :nodoc: attr_reader :owners, :reflection def initialize(klass, owners, reflection, preload_scope) @owners = owners @reflection = reflection end def run(preloader); end def preloaded_records owners.flat_map { |owner| owner.association(reflection.name).target } end end class NullPreloader # :nodoc: def self.new(klass, owners, reflection, preload_scope); self; end def self.run(preloader); end def self.preloaded_records; []; end end def preloader_for(reflection, owners, rhs_klass) return NullPreloader unless rhs_klass if owners.first.association(reflection.name).loaded? return AlreadyLoaded end reflection.check_preloadable! case reflection.macro when :has_many reflection.options[:through] ? HasManyThrough : HasMany when :has_one reflection.options[:through] ? HasOneThrough : HasOne when :belongs_to BelongsTo end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/000077500000000000000000000000001266740050600253505ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/associations/preloader/association.rb000066400000000000000000000120221266740050600302060ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader class Association #:nodoc: attr_reader :owners, :reflection, :preload_scope, :model, :klass attr_reader :preloaded_records def initialize(klass, owners, reflection, preload_scope) @klass = klass @owners = owners @reflection = reflection @preload_scope = preload_scope @model = owners.first && owners.first.class @scope = nil @owners_by_key = nil @preloaded_records = [] end def run(preloader) preload(preloader) end def preload(preloader) raise NotImplementedError end def scope @scope ||= build_scope end def records_for(ids) query_scope(ids) end def query_scope(ids) scope.where(association_key.in(ids)) end def table klass.arel_table end # The name of the key on the associated records def association_key_name raise NotImplementedError end # This is overridden by HABTM as the condition should be on the foreign_key column in # the join table def association_key table[association_key_name] end # The name of the key on the model which declares the association def owner_key_name raise NotImplementedError end def owners_by_key @owners_by_key ||= if key_conversion_required? owners.group_by do |owner| owner[owner_key_name].to_s end else owners.group_by do |owner| owner[owner_key_name] end end end def options reflection.options end private def associated_records_by_owner(preloader) owners_map = owners_by_key owner_keys = owners_map.keys.compact # Each record may have multiple owners, and vice-versa records_by_owner = owners.each_with_object({}) do |owner,h| h[owner] = [] end if owner_keys.any? # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) # Make several smaller queries if necessary or make one query if the adapter supports it sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) records = load_slices sliced records.each do |record, owner_key| owners_map[owner_key].each do |owner| records_by_owner[owner] << record end end end records_by_owner end def key_conversion_required? association_key_type != owner_key_type end def association_key_type @klass.type_for_attribute(association_key_name.to_s).type end def owner_key_type @model.type_for_attribute(owner_key_name.to_s).type end def load_slices(slices) @preloaded_records = slices.flat_map { |slice| records_for(slice) } @preloaded_records.map { |record| key = record[association_key_name] key = key.to_s if key_conversion_required? [record, key] } end def reflection_scope @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped end def build_scope scope = klass.unscoped values = reflection_scope.values reflection_binds = reflection_scope.bind_values preload_values = preload_scope.values preload_binds = preload_scope.bind_values scope.where_values = Array(values[:where]) + Array(preload_values[:where]) scope.references_values = Array(values[:references]) + Array(preload_values[:references]) scope.bind_values = (reflection_binds + preload_binds) scope._select! preload_values[:select] || values[:select] || table[Arel.star] scope.includes! preload_values[:includes] || values[:includes] scope.joins! preload_values[:joins] || values[:joins] scope.order! preload_values[:order] || values[:order] if preload_values[:reordering] || values[:reordering] scope.reordering_value = true end if preload_values[:readonly] || values[:readonly] scope.readonly! end if options[:as] scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) end scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope]) klass.default_scoped.merge(scope) end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/belongs_to.rb000066400000000000000000000005201266740050600300250ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader class BelongsTo < SingularAssociation #:nodoc: def association_key_name reflection.options[:primary_key] || klass && klass.primary_key end def owner_key_name reflection.foreign_key end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/collection_association.rb000066400000000000000000000012001266740050600324150ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader class CollectionAssociation < Association #:nodoc: private def build_scope super.order(preload_scope.values[:order] || reflection_scope.values[:order]) end def preload(preloader) associated_records_by_owner(preloader).each do |owner, records| association = owner.association(reflection.name) association.loaded! association.target.concat(records) records.each { |record| association.set_inverse_instance(record) } end end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/has_many.rb000066400000000000000000000004661266740050600275020ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader class HasMany < CollectionAssociation #:nodoc: def association_key_name reflection.foreign_key end def owner_key_name reflection.active_record_primary_key end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/has_many_through.rb000066400000000000000000000006701266740050600312370ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader class HasManyThrough < CollectionAssociation #:nodoc: include ThroughAssociation def associated_records_by_owner(preloader) records_by_owner = super if reflection_scope.distinct_value records_by_owner.each_value { |records| records.uniq! } end records_by_owner end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/has_one.rb000066400000000000000000000007001266740050600273060ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader class HasOne < SingularAssociation #:nodoc: def association_key_name reflection.foreign_key end def owner_key_name reflection.active_record_primary_key end private def build_scope super.order(preload_scope.values[:order] || reflection_scope.values[:order]) end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/has_one_through.rb000066400000000000000000000002661266740050600310550ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader class HasOneThrough < SingularAssociation #:nodoc: include ThroughAssociation end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/singular_association.rb000066400000000000000000000010051266740050600321110ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader class SingularAssociation < Association #:nodoc: private def preload(preloader) associated_records_by_owner(preloader).each do |owner, associated_records| record = associated_records.first association = owner.association(reflection.name) association.target = record association.set_inverse_instance(record) if record end end end end end end rails-4.2.6/activerecord/lib/active_record/associations/preloader/through_association.rb000066400000000000000000000057541266740050600317640ustar00rootroot00000000000000module ActiveRecord module Associations class Preloader module ThroughAssociation #:nodoc: def through_reflection reflection.through_reflection end def source_reflection reflection.source_reflection end def associated_records_by_owner(preloader) preloader.preload(owners, through_reflection.name, through_scope) through_records = owners.map do |owner| association = owner.association through_reflection.name [owner, Array(association.reader)] end reset_association owners, through_reflection.name middle_records = through_records.flat_map { |(_,rec)| rec } preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope) @preloaded_records = preloaders.flat_map(&:preloaded_records) middle_to_pl = preloaders.each_with_object({}) do |pl,h| pl.owners.each { |middle| h[middle] = pl } end record_offset = {} @preloaded_records.each_with_index do |record,i| record_offset[record] = i end through_records.each_with_object({}) { |(lhs,center),records_by_owner| pl_to_middle = center.group_by { |record| middle_to_pl[record] } records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles| rhs_records = middles.flat_map { |r| association = r.association source_reflection.name association.reader }.compact rhs_records.sort_by { |rhs| record_offset[rhs] } end } end private def reset_association(owners, association_name) should_reset = (through_scope != through_reflection.klass.unscoped) || (reflection.options[:source_type] && through_reflection.collection?) # Don't cache the association - we would only be caching a subset if should_reset owners.each { |owner| owner.association(association_name).reset } end end def through_scope scope = through_reflection.klass.unscoped if options[:source_type] scope.where! reflection.foreign_type => options[:source_type] else unless reflection_scope.where_values.empty? scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) scope.where_values = reflection_scope.values[:where] scope.bind_values = reflection_scope.bind_values end scope.references! reflection_scope.values[:references] scope = scope.order reflection_scope.values[:order] if scope.eager_loading? end scope end end end end end rails-4.2.6/activerecord/lib/active_record/associations/singular_association.rb000066400000000000000000000042171266740050600301440ustar00rootroot00000000000000module ActiveRecord module Associations class SingularAssociation < Association #:nodoc: # Implements the reader method, e.g. foo.bar for Foo.has_one :bar def reader(force_reload = false) if force_reload && klass klass.uncached { reload } elsif !loaded? || stale_target? reload end target end # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar def writer(record) replace(record) end def create(attributes = {}, &block) _create_record(attributes, &block) end def create!(attributes = {}, &block) _create_record(attributes, true, &block) end def build(attributes = {}) record = build_record(attributes) yield(record) if block_given? set_new_record(record) record end private def create_scope scope.scope_for_create.stringify_keys.except(klass.primary_key) end def get_records return scope.limit(1).to_a if skip_statement_cache? conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do StatementCache.create(conn) { |params| as = AssociationScope.create { params.bind } target_scope.merge(as.scope(self, conn)).limit(1) } end binds = AssociationScope.get_bind_values(owner, reflection.chain) sc.execute binds, klass, klass.connection end def find_target if record = get_records.first set_inverse_instance record end end def replace(record) raise NotImplementedError, "Subclasses must implement a replace(record) method" end def set_new_record(record) replace(record) end def _create_record(attributes, raise_error = false) record = build_record(attributes) yield(record) if block_given? saved = record.save set_new_record(record) raise RecordInvalid.new(record) if !saved && raise_error record end end end end rails-4.2.6/activerecord/lib/active_record/associations/through_association.rb000066400000000000000000000071351266740050600300020ustar00rootroot00000000000000module ActiveRecord # = Active Record Through Association module Associations module ThroughAssociation #:nodoc: delegate :source_reflection, :through_reflection, :to => :reflection protected # We merge in these scopes for two reasons: # # 1. To get the default_scope conditions for any of the other reflections in the chain # 2. To get the type conditions for any STI models in the chain def target_scope scope = super reflection.chain.drop(1).each do |reflection| relation = reflection.klass.all scope.merge!( relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load) ) end scope end private # Construct attributes for :through pointing to owner and associate. This is used by the # methods which create and delete records on the association. # # We only support indirectly modifying through associations which has a belongs_to source. # This is the "has_many :tags, through: :taggings" situation, where the join model # typically has a belongs_to on both side. In other words, associations which could also # be represented as has_and_belongs_to_many associations. # # We do not support creating/deleting records on the association where the source has # some other type, because this opens up a whole can of worms, and in basically any # situation it is more natural for the user to just create or modify their join records # directly as required. def construct_join_attributes(*records) ensure_mutable if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key join_attributes = { source_reflection.name => records } else join_attributes = { source_reflection.foreign_key => records.map { |record| record.send(source_reflection.association_primary_key(reflection.klass)) } } end if options[:source_type] join_attributes[source_reflection.foreign_type] = records.map { |record| record.class.base_class.name } end if records.count == 1 Hash[join_attributes.map { |k, v| [k, v.first] }] else join_attributes end end # Note: this does not capture all cases, for example it would be crazy to try to # properly support stale-checking for nested associations. def stale_state if through_reflection.belongs_to? owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s end end def foreign_key_present? through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil? end def ensure_mutable unless source_reflection.belongs_to? raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) end end def ensure_not_nested if reflection.nested? raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection) end end def build_record(attributes) inverse = source_reflection.inverse_of target = through_association.target if inverse && target && !target.is_a?(Array) attributes[inverse.foreign_key] = target.id end super(attributes) end end end end rails-4.2.6/activerecord/lib/active_record/attribute.rb000066400000000000000000000065071266740050600232340ustar00rootroot00000000000000module ActiveRecord class Attribute # :nodoc: class << self def from_database(name, value, type) FromDatabase.new(name, value, type) end def from_user(name, value, type) FromUser.new(name, value, type) end def with_cast_value(name, value, type) WithCastValue.new(name, value, type) end def null(name) Null.new(name) end def uninitialized(name, type) Uninitialized.new(name, type) end end attr_reader :name, :value_before_type_cast, :type # This method should not be called directly. # Use #from_database or #from_user def initialize(name, value_before_type_cast, type) @name = name @value_before_type_cast = value_before_type_cast @type = type end def value # `defined?` is cheaper than `||=` when we get back falsy values @value = original_value unless defined?(@value) @value end def original_value type_cast(value_before_type_cast) end def value_for_database type.type_cast_for_database(value) end def changed_from?(old_value) type.changed?(old_value, value, value_before_type_cast) end def changed_in_place_from?(old_value) has_been_read? && type.changed_in_place?(old_value, value) end def with_value_from_user(value) self.class.from_user(name, value, type) end def with_value_from_database(value) self.class.from_database(name, value, type) end def with_cast_value(value) self.class.with_cast_value(name, value, type) end def type_cast(*) raise NotImplementedError end def initialized? true end def came_from_user? false end def ==(other) self.class == other.class && name == other.name && value_before_type_cast == other.value_before_type_cast && type == other.type end protected def initialize_dup(other) if defined?(@value) && @value.duplicable? @value = @value.dup end end private def has_been_read? defined?(@value) end class FromDatabase < Attribute # :nodoc: def type_cast(value) type.type_cast_from_database(value) end end class FromUser < Attribute # :nodoc: def type_cast(value) type.type_cast_from_user(value) end def came_from_user? true end end class WithCastValue < Attribute # :nodoc: def type_cast(value) value end def changed_in_place_from?(old_value) false end end class Null < Attribute # :nodoc: def initialize(name) super(name, nil, Type::Value.new) end def value nil end def with_value_from_database(value) raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" end alias_method :with_value_from_user, :with_value_from_database end class Uninitialized < Attribute # :nodoc: def initialize(name, type) super(name, nil, type) end def value if block_given? yield name end end def value_for_database end def initialized? false end end private_constant :FromDatabase, :FromUser, :Null, :Uninitialized end end rails-4.2.6/activerecord/lib/active_record/attribute_assignment.rb000066400000000000000000000176121266740050600254630ustar00rootroot00000000000000require 'active_model/forbidden_attributes_protection' module ActiveRecord module AttributeAssignment extend ActiveSupport::Concern include ActiveModel::ForbiddenAttributesProtection # Allows you to set all the attributes by passing in a hash of attributes with # keys matching the attribute names (which again matches the column names). # # If the passed hash responds to permitted? method and the return value # of this method is +false+ an ActiveModel::ForbiddenAttributesError # exception is raised. # # cat = Cat.new(name: "Gorby", status: "yawning") # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil} # cat.assign_attributes(status: "sleeping") # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil } # # New attributes will be persisted in the database when the object is saved. # # Aliased to attributes=. def assign_attributes(new_attributes) if !new_attributes.respond_to?(:stringify_keys) raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." end return if new_attributes.blank? attributes = new_attributes.stringify_keys multi_parameter_attributes = [] nested_parameter_attributes = [] attributes = sanitize_for_mass_assignment(attributes) attributes.each do |k, v| if k.include?("(") multi_parameter_attributes << [ k, v ] elsif v.is_a?(Hash) nested_parameter_attributes << [ k, v ] else _assign_attribute(k, v) end end assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? end alias attributes= assign_attributes private def _assign_attribute(k, v) public_send("#{k}=", v) rescue NoMethodError, NameError if respond_to?("#{k}=") raise else raise UnknownAttributeError.new(self, k) end end # Assign any deferred nested attributes after the base attributes have been set. def assign_nested_parameter_attributes(pairs) pairs.each { |k, v| _assign_attribute(k, v) } end # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done # by calling new on the column type or aggregation type (through composed_of) object with these parameters. # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+. def assign_multiparameter_attributes(pairs) execute_callstack_for_multiparameter_attributes( extract_callstack_for_multiparameter_attributes(pairs) ) end def execute_callstack_for_multiparameter_attributes(callstack) errors = [] callstack.each do |name, values_with_empty_parameters| begin send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value) rescue => ex errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) end end unless errors.empty? error_descriptions = errors.map { |ex| ex.message }.join(",") raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" end end def extract_callstack_for_multiparameter_attributes(pairs) attributes = {} pairs.each do |(multiparameter_name, value)| attribute_name = multiparameter_name.split("(").first attributes[attribute_name] ||= {} parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value end attributes end def type_cast_attribute_value(multiparameter_name, value) multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value end def find_parameter_position(multiparameter_name) multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i end class MultiparameterAttribute #:nodoc: attr_reader :object, :name, :values, :cast_type def initialize(object, name, values) @object = object @name = name @values = values end def read_value return if values.values.compact.empty? @cast_type = object.type_for_attribute(name) klass = cast_type.klass if klass == Time read_time elsif klass == Date read_date else read_other end end private def instantiate_time_object(set_values) if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type) Time.zone.local(*set_values) else Time.send(object.class.default_timezone, *set_values) end end def read_time # If column is a :time (and not :date or :datetime) there is no need to validate if # there are year/month/day fields if cast_type.type == :time # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value| values[key] ||= value end else # else column is a timestamp, so if Date bits were not provided, error validate_required_parameters!([1,2,3]) # If Date bits were provided but blank, then return nil return if blank_date_parameter? end max_position = extract_max_param(6) set_values = values.values_at(*(1..max_position)) # If Time bits are not there, then default to 0 (3..5).each { |i| set_values[i] = set_values[i].presence || 0 } instantiate_time_object(set_values) end def read_date return if blank_date_parameter? set_values = values.values_at(1,2,3) begin Date.new(*set_values) rescue ArgumentError # if Date.new raises an exception on an invalid date instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates end end def read_other max_position = extract_max_param positions = (1..max_position) validate_required_parameters!(positions) values.slice(*positions) end # Checks whether some blank date parameter exists. Note that this is different # than the validate_required_parameters! method, since it just checks for blank # positions instead of missing ones, and does not raise in case one blank position # exists. The caller is responsible to handle the case of this returning true. def blank_date_parameter? (1..3).any? { |position| values[position].blank? } end # If some position is not provided, it errors out a missing parameter exception. def validate_required_parameters!(positions) if missing_parameter = positions.detect { |position| !values.key?(position) } raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})") end end def extract_max_param(upper_cap = 100) [values.keys.max, upper_cap].min end end end end rails-4.2.6/activerecord/lib/active_record/attribute_decorators.rb000066400000000000000000000034741266740050600254610ustar00rootroot00000000000000module ActiveRecord module AttributeDecorators # :nodoc: extend ActiveSupport::Concern included do class_attribute :attribute_type_decorations, instance_accessor: false # :internal: self.attribute_type_decorations = TypeDecorator.new end module ClassMethods # :nodoc: def decorate_attribute_type(column_name, decorator_name, &block) matcher = ->(name, _) { name == column_name.to_s } key = "_#{column_name}_#{decorator_name}" decorate_matching_attribute_types(matcher, key, &block) end def decorate_matching_attribute_types(matcher, decorator_name, &block) clear_caches_calculated_from_columns decorator_name = decorator_name.to_s # Create new hashes so we don't modify parent classes self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block]) end private def add_user_provided_columns(*) super.map do |column| decorated_type = attribute_type_decorations.apply(column.name, column.cast_type) column.with_type(decorated_type) end end end class TypeDecorator # :nodoc: delegate :clear, to: :@decorations def initialize(decorations = {}) @decorations = decorations end def merge(*args) TypeDecorator.new(@decorations.merge(*args)) end def apply(name, type) decorations = decorators_for(name, type) decorations.inject(type) do |new_type, block| block.call(new_type) end end private def decorators_for(name, type) matching(name, type).map(&:last) end def matching(name, type) @decorations.values.select do |(matcher, _)| matcher.call(name, type) end end end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods.rb000066400000000000000000000361731266740050600247610ustar00rootroot00000000000000require 'active_support/core_ext/enumerable' require 'active_support/core_ext/string/filters' require 'mutex_m' require 'thread_safe' module ActiveRecord # = Active Record Attribute Methods module AttributeMethods extend ActiveSupport::Concern include ActiveModel::AttributeMethods included do initialize_generated_modules include Read include Write include BeforeTypeCast include Query include PrimaryKey include TimeZoneConversion include Dirty include Serialization delegate :column_for_attribute, to: :class end AttrNames = Module.new { def self.set_name_cache(name, value) const_name = "ATTR_#{name}" unless const_defined? const_name const_set const_name, value.dup.freeze end end } BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass) class AttributeMethodCache def initialize @module = Module.new @method_cache = ThreadSafe::Cache.new end def [](name) @method_cache.compute_if_absent(name) do safe_name = name.unpack('h*').first temp_method = "__temp__#{safe_name}" ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__ @module.instance_method temp_method end end private # Override this method in the subclasses for method body. def method_body(method_name, const_name) raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method." end end class GeneratedAttributeMethods < Module; end # :nodoc: module ClassMethods def inherited(child_class) #:nodoc: child_class.initialize_generated_modules super end def initialize_generated_modules # :nodoc: @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m } @attribute_methods_generated = false include @generated_attribute_methods super end # Generates all the attribute related methods for columns in the database # accessors, mutators and query methods. def define_attribute_methods # :nodoc: return false if @attribute_methods_generated # Use a mutex; we don't want two threads simultaneously trying to define # attribute methods. generated_attribute_methods.synchronize do return false if @attribute_methods_generated superclass.define_attribute_methods unless self == base_class super(column_names) @attribute_methods_generated = true end true end def undefine_attribute_methods # :nodoc: generated_attribute_methods.synchronize do super if defined?(@attribute_methods_generated) && @attribute_methods_generated @attribute_methods_generated = false end end # Raises a ActiveRecord::DangerousAttributeError exception when an # \Active \Record method is defined in the model, otherwise +false+. # # class Person < ActiveRecord::Base # def save # 'already defined by Active Record' # end # end # # Person.instance_method_already_implemented?(:save) # # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord # # Person.instance_method_already_implemented?(:name) # # => false def instance_method_already_implemented?(method_name) if dangerous_attribute_method?(method_name) raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name." end if superclass == Base super else # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass # defines its own attribute method, then we don't want to overwrite that. defined = method_defined_within?(method_name, superclass, Base) && ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods) defined || super end end # A method name is 'dangerous' if it is already (re)defined by Active Record, but # not by any ancestors. (So 'puts' is not dangerous but 'save' is.) def dangerous_attribute_method?(name) # :nodoc: method_defined_within?(name, Base) end def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc: if klass.method_defined?(name) || klass.private_method_defined?(name) if superklass.method_defined?(name) || superklass.private_method_defined?(name) klass.instance_method(name).owner != superklass.instance_method(name).owner else true end else false end end # A class method is 'dangerous' if it is already (re)defined by Active Record, but # not by any ancestors. (So 'puts' is not dangerous but 'new' is.) def dangerous_class_method?(method_name) BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base) end def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc if klass.respond_to?(name, true) if superklass.respond_to?(name, true) klass.method(name).owner != superklass.method(name).owner else true end else false end end # Returns +true+ if +attribute+ is an attribute method and table exists, # +false+ otherwise. # # class Person < ActiveRecord::Base # end # # Person.attribute_method?('name') # => true # Person.attribute_method?(:age=) # => true # Person.attribute_method?(:nothing) # => false def attribute_method?(attribute) super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, ''))) end # Returns an array of column names as strings if it's not an abstract class and # table exists. Otherwise it returns an empty array. # # class Person < ActiveRecord::Base # end # # Person.attribute_names # # => ["id", "created_at", "updated_at", "name", "age"] def attribute_names @attribute_names ||= if !abstract_class? && table_exists? column_names else [] end end # Returns the column object for the named attribute. # Returns nil if the named attribute does not exist. # # class Person < ActiveRecord::Base # end # # person = Person.new # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter # # => # # # person.column_for_attribute(:nothing) # # => nil def column_for_attribute(name) column = columns_hash[name.to_s] if column.nil? ActiveSupport::Deprecation.warn(<<-MSG.squish) `#column_for_attribute` will return a null object for non-existent columns in Rails 5. Use `#has_attribute?` if you need to check for an attribute's existence. MSG end column end end # A Person object with a name attribute can ask person.respond_to?(:name), # person.respond_to?(:name=), and person.respond_to?(:name?) # which will all return +true+. It also define the attribute methods if they have # not been generated. # # class Person < ActiveRecord::Base # end # # person = Person.new # person.respond_to(:name) # => true # person.respond_to(:name=) # => true # person.respond_to(:name?) # => true # person.respond_to('age') # => true # person.respond_to('age=') # => true # person.respond_to('age?') # => true # person.respond_to(:nothing) # => false def respond_to?(name, include_private = false) return false unless super name = name.to_s # If the result is true then check for the select case. # For queries selecting a subset of columns, return false for unselected columns. # We check defined?(@attributes) not to issue warnings if called on objects that # have been allocated but not yet initialized. if defined?(@attributes) && self.class.column_names.include?(name) return has_attribute?(name) end return true end # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+. # # class Person < ActiveRecord::Base # end # # person = Person.new # person.has_attribute?(:name) # => true # person.has_attribute?('age') # => true # person.has_attribute?(:nothing) # => false def has_attribute?(attr_name) @attributes.key?(attr_name.to_s) end # Returns an array of names for the attributes available on this object. # # class Person < ActiveRecord::Base # end # # person = Person.new # person.attribute_names # # => ["id", "created_at", "updated_at", "name", "age"] def attribute_names @attributes.keys end # Returns a hash of all the attributes with their names as keys and the values of the attributes as values. # # class Person < ActiveRecord::Base # end # # person = Person.create(name: 'Francesco', age: 22) # person.attributes # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22} def attributes @attributes.to_hash end # Returns an #inspect-like string for the value of the # attribute +attr_name+. String attributes are truncated up to 50 # characters, Date and Time attributes are returned in the # :db format, Array attributes are truncated up to 10 values. # Other attributes return the value of #inspect without # modification. # # person = Person.create!(name: 'David Heinemeier Hansson ' * 3) # # person.attribute_for_inspect(:name) # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\"" # # person.attribute_for_inspect(:created_at) # # => "\"2012-10-22 00:15:07\"" # # person.attribute_for_inspect(:tag_ids) # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]" def attribute_for_inspect(attr_name) value = read_attribute(attr_name) if value.is_a?(String) && value.length > 50 "#{value[0, 50]}...".inspect elsif value.is_a?(Date) || value.is_a?(Time) %("#{value.to_s(:db)}") elsif value.is_a?(Array) && value.size > 10 inspected = value.first(10).inspect %(#{inspected[0...-1]}, ...]) else value.inspect end end # Returns +true+ if the specified +attribute+ has been set by the user or by a # database load and is neither +nil+ nor empty? (the latter only applies # to objects that respond to empty?, most notably Strings). Otherwise, +false+. # Note that it always returns +true+ with boolean attributes. # # class Task < ActiveRecord::Base # end # # task = Task.new(title: '', is_done: false) # task.attribute_present?(:title) # => false # task.attribute_present?(:is_done) # => true # task.title = 'Buy milk' # task.is_done = true # task.attribute_present?(:title) # => true # task.attribute_present?(:is_done) # => true def attribute_present?(attribute) value = _read_attribute(attribute) !value.nil? && !(value.respond_to?(:empty?) && value.empty?) end # Returns the value of the attribute identified by attr_name after it has been typecast (for example, # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises # ActiveModel::MissingAttributeError if the identified attribute is missing. # # Note: +:id+ is always present. # # Alias for the read_attribute method. # # class Person < ActiveRecord::Base # belongs_to :organization # end # # person = Person.new(name: 'Francesco', age: '22') # person[:name] # => "Francesco" # person[:age] # => 22 # # person = Person.select('id').first # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id def [](attr_name) read_attribute(attr_name) { |n| missing_attribute(n, caller) } end # Updates the attribute identified by attr_name with the specified +value+. # (Alias for the protected write_attribute method). # # class Person < ActiveRecord::Base # end # # person = Person.new # person[:age] = '22' # person[:age] # => 22 # person[:age] # => Fixnum def []=(attr_name, value) write_attribute(attr_name, value) end protected def clone_attribute_value(reader_method, attribute_name) # :nodoc: value = send(reader_method, attribute_name) value.duplicable? ? value.clone : value rescue TypeError, NoMethodError value end def arel_attributes_with_values_for_create(attribute_names) # :nodoc: arel_attributes_with_values(attributes_for_create(attribute_names)) end def arel_attributes_with_values_for_update(attribute_names) # :nodoc: arel_attributes_with_values(attributes_for_update(attribute_names)) end def attribute_method?(attr_name) # :nodoc: # We check defined? because Syck calls respond_to? before actually calling initialize. defined?(@attributes) && @attributes.key?(attr_name) end private # Returns a Hash of the Arel::Attributes and attribute values that have been # typecasted for use in an Arel insert/update method. def arel_attributes_with_values(attribute_names) attrs = {} arel_table = self.class.arel_table attribute_names.each do |name| attrs[arel_table[name]] = typecasted_attribute_value(name) end attrs end # Filters the primary keys and readonly attributes from the attribute names. def attributes_for_update(attribute_names) attribute_names.reject do |name| readonly_attribute?(name) end end # Filters out the primary keys, from the attribute names, when the primary # key is to be generated (e.g. the id attribute has no value). def attributes_for_create(attribute_names) attribute_names.reject do |name| pk_attribute?(name) && id.nil? end end def readonly_attribute?(name) self.class.readonly_attributes.include?(name) end def pk_attribute?(name) name == self.class.primary_key end def typecasted_attribute_value(name) _read_attribute(name) end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods/000077500000000000000000000000001266740050600244225ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/attribute_methods/before_type_cast.rb000066400000000000000000000056311266740050600302710ustar00rootroot00000000000000module ActiveRecord module AttributeMethods # = Active Record Attribute Methods Before Type Cast # # ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to # read the value of the attributes before typecasting and deserialization. # # class Task < ActiveRecord::Base # end # # task = Task.new(id: '1', completed_on: '2012-10-21') # task.id # => 1 # task.completed_on # => Sun, 21 Oct 2012 # # task.attributes_before_type_cast # # => {"id"=>"1", "completed_on"=>"2012-10-21", ... } # task.read_attribute_before_type_cast('id') # => "1" # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21" # # In addition to #read_attribute_before_type_cast and #attributes_before_type_cast, # it declares a method for all attributes with the *_before_type_cast # suffix. # # task.id_before_type_cast # => "1" # task.completed_on_before_type_cast # => "2012-10-21" module BeforeTypeCast extend ActiveSupport::Concern included do attribute_method_suffix "_before_type_cast" attribute_method_suffix "_came_from_user?" end # Returns the value of the attribute identified by +attr_name+ before # typecasting and deserialization. # # class Task < ActiveRecord::Base # end # # task = Task.new(id: '1', completed_on: '2012-10-21') # task.read_attribute('id') # => 1 # task.read_attribute_before_type_cast('id') # => '1' # task.read_attribute('completed_on') # => Sun, 21 Oct 2012 # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21" # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21" def read_attribute_before_type_cast(attr_name) @attributes[attr_name.to_s].value_before_type_cast end # Returns a hash of attributes before typecasting and deserialization. # # class Task < ActiveRecord::Base # end # # task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21') # task.attributes # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil} # task.attributes_before_type_cast # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil} def attributes_before_type_cast @attributes.values_before_type_cast end private # Handle *_before_type_cast for method_missing. def attribute_before_type_cast(attribute_name) read_attribute_before_type_cast(attribute_name) end def attribute_came_from_user?(attribute_name) @attributes[attribute_name].came_from_user? end end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods/dirty.rb000066400000000000000000000117771266740050600261170ustar00rootroot00000000000000require 'active_support/core_ext/module/attribute_accessors' module ActiveRecord module AttributeMethods module Dirty # :nodoc: extend ActiveSupport::Concern include ActiveModel::Dirty included do if self < ::ActiveRecord::Timestamp raise "You cannot include Dirty after Timestamp" end class_attribute :partial_writes, instance_writer: false self.partial_writes = true end # Attempts to +save+ the record and clears changed attributes if successful. def save(*) if status = super changes_applied end status end # Attempts to save! the record and clears changed attributes if successful. def save!(*) super.tap do changes_applied end end # reload the record and clears changed attributes. def reload(*) super.tap do clear_changes_information end end def initialize_dup(other) # :nodoc: super @original_raw_attributes = nil calculate_changes_from_defaults end def changes_applied super store_original_raw_attributes end def clear_changes_information super original_raw_attributes.clear end def changed_attributes # This should only be set by methods which will call changed_attributes # multiple times when it is known that the computed value cannot change. if defined?(@cached_changed_attributes) @cached_changed_attributes else super.reverse_merge(attributes_changed_in_place).freeze end end def changes cache_changed_attributes do super end end def attribute_changed_in_place?(attr_name) old_value = original_raw_attribute(attr_name) @attributes[attr_name].changed_in_place_from?(old_value) end private def changes_include?(attr_name) super || attribute_changed_in_place?(attr_name) end def calculate_changes_from_defaults @changed_attributes = nil self.class.column_defaults.each do |attr, orig_value| set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value) end end # Wrap write_attribute to remember original attribute value. def write_attribute(attr, value) attr = attr.to_s old_value = old_attribute_value(attr) result = super store_original_raw_attribute(attr) save_changed_attribute(attr, old_value) result end def raw_write_attribute(attr, value) attr = attr.to_s result = super original_raw_attributes[attr] = value result end def save_changed_attribute(attr, old_value) clear_changed_attributes_cache if attribute_changed_by_setter?(attr) clear_attribute_changes(attr) unless _field_changed?(attr, old_value) else set_attribute_was(attr, old_value) if _field_changed?(attr, old_value) end end def old_attribute_value(attr) if attribute_changed?(attr) changed_attributes[attr] else clone_attribute_value(:_read_attribute, attr) end end def _update_record(*) partial_writes? ? super(keys_for_partial_write) : super end def _create_record(*) partial_writes? ? super(keys_for_partial_write) : super end # Serialized attributes should always be written in case they've been # changed in place. def keys_for_partial_write changed & persistable_attribute_names end def _field_changed?(attr, old_value) @attributes[attr].changed_from?(old_value) end def attributes_changed_in_place changed_in_place.each_with_object({}) do |attr_name, h| orig = @attributes[attr_name].original_value h[attr_name] = orig end end def changed_in_place self.class.attribute_names.select do |attr_name| attribute_changed_in_place?(attr_name) end end def original_raw_attribute(attr_name) original_raw_attributes.fetch(attr_name) do read_attribute_before_type_cast(attr_name) end end def original_raw_attributes @original_raw_attributes ||= {} end def store_original_raw_attribute(attr_name) original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil end def store_original_raw_attributes attribute_names.each do |attr| store_original_raw_attribute(attr) end end def cache_changed_attributes @cached_changed_attributes = changed_attributes yield ensure clear_changed_attributes_cache end def clear_changed_attributes_cache remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes) end end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods/primary_key.rb000066400000000000000000000071211266740050600273030ustar00rootroot00000000000000require 'set' module ActiveRecord module AttributeMethods module PrimaryKey extend ActiveSupport::Concern # Returns this record's primary key value wrapped in an Array if one is # available. def to_key sync_with_transaction_state key = self.id [key] if key end # Returns the primary key value. def id if pk = self.class.primary_key sync_with_transaction_state _read_attribute(pk) end end # Sets the primary key value. def id=(value) sync_with_transaction_state write_attribute(self.class.primary_key, value) if self.class.primary_key end # Queries the primary key value. def id? sync_with_transaction_state query_attribute(self.class.primary_key) end # Returns the primary key value before type cast. def id_before_type_cast sync_with_transaction_state read_attribute_before_type_cast(self.class.primary_key) end # Returns the primary key previous value. def id_was sync_with_transaction_state attribute_was(self.class.primary_key) end protected def attribute_method?(attr_name) attr_name == 'id' || super end module ClassMethods def define_method_attribute(attr_name) super if attr_name == primary_key && attr_name != 'id' generated_attribute_methods.send(:alias_method, :id, primary_key) end end ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set def dangerous_attribute_method?(method_name) super && !ID_ATTRIBUTE_METHODS.include?(method_name) end # Defines the primary key field -- can be overridden in subclasses. # Overwriting will negate any effect of the +primary_key_prefix_type+ # setting, though. def primary_key @primary_key = reset_primary_key unless defined? @primary_key @primary_key end # Returns a quoted version of the primary key name, used to construct # SQL statements. def quoted_primary_key @quoted_primary_key ||= connection.quote_column_name(primary_key) end def reset_primary_key #:nodoc: if self == base_class self.primary_key = get_primary_key(base_class.name) else self.primary_key = base_class.primary_key end end def get_primary_key(base_name) #:nodoc: if base_name && primary_key_prefix_type == :table_name base_name.foreign_key(false) elsif base_name && primary_key_prefix_type == :table_name_with_underscore base_name.foreign_key else if ActiveRecord::Base != self && table_exists? connection.schema_cache.primary_keys(table_name) else 'id' end end end # Sets the name of the primary key column. # # class Project < ActiveRecord::Base # self.primary_key = 'sysid' # end # # You can also define the +primary_key+ method yourself: # # class Project < ActiveRecord::Base # def self.primary_key # 'foo_' + super # end # end # # Project.primary_key # => "foo_id" def primary_key=(value) @primary_key = value && value.to_s @quoted_primary_key = nil @attributes_builder = nil end end end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods/query.rb000066400000000000000000000017041266740050600261160ustar00rootroot00000000000000module ActiveRecord module AttributeMethods module Query extend ActiveSupport::Concern included do attribute_method_suffix "?" end def query_attribute(attr_name) value = self[attr_name] case value when true then true when false, nil then false else column = self.class.columns_hash[attr_name] if column.nil? if Numeric === value || value !~ /[^0-9]/ !value.to_i.zero? else return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) !value.blank? end elsif column.number? !value.zero? else !value.blank? end end end private # Handle *? for method_missing. def attribute?(attribute_name) query_attribute(attribute_name) end end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods/read.rb000066400000000000000000000075251266740050600256730ustar00rootroot00000000000000require 'active_support/core_ext/module/method_transplanting' module ActiveRecord module AttributeMethods module Read ReaderMethodCache = Class.new(AttributeMethodCache) { private # We want to generate the methods via module_eval rather than # define_method, because define_method is slower on dispatch. # Evaluating many similar methods may use more memory as the instruction # sequences are duplicated and cached (in MRI). define_method may # be slower on dispatch, but if you're careful about the closure # created, then define_method will consume much less memory. # # But sometimes the database might return columns with # characters that are not allowed in normal method names (like # 'my_column(omg)'. So to work around this we first define with # the __temp__ identifier, and then use alias method to rename # it to what we want. # # We are also defining a constant to hold the frozen string of # the attribute name. Using a constant means that we do not have # to allocate an object on each call to the attribute method. # Making it frozen means that it doesn't get duped when used to # key the @attributes in read_attribute. def method_body(method_name, const_name) <<-EOMETHOD def #{method_name} name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} _read_attribute(name) { |n| missing_attribute(n, caller) } end EOMETHOD end }.new extend ActiveSupport::Concern module ClassMethods [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name| define_method method_name do |*| cached_attributes_deprecation_warning(method_name) true end end protected def cached_attributes_deprecation_warning(method_name) ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached." end if Module.methods_transplantable? def define_method_attribute(name) method = ReaderMethodCache[name] generated_attribute_methods.module_eval { define_method name, method } end else def define_method_attribute(name) safe_name = name.unpack('h*').first temp_method = "__temp__#{safe_name}" ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def #{temp_method} name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} _read_attribute(name) { |n| missing_attribute(n, caller) } end STR generated_attribute_methods.module_eval do alias_method name, temp_method undef_method temp_method end end end end ID = 'id'.freeze # Returns the value of the attribute identified by attr_name after # it has been typecast (for example, "2004-12-12" in a date column is cast # to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name, &block) name = attr_name.to_s name = self.class.primary_key if name == ID _read_attribute(name, &block) end # This method exists to avoid the expensive primary_key check internally, without # breaking compatibility with the read_attribute API def _read_attribute(attr_name) # :nodoc: @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? } end private def attribute(attribute_name) _read_attribute(attribute_name) end end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods/serialization.rb000066400000000000000000000051461266740050600276320ustar00rootroot00000000000000require 'active_support/core_ext/string/filters' module ActiveRecord module AttributeMethods module Serialization extend ActiveSupport::Concern module ClassMethods # If you have an attribute that needs to be saved to the database as an # object, and retrieved as the same object, then specify the name of that # attribute using this method and it will be handled automatically. The # serialization is done through YAML. If +class_name+ is specified, the # serialized object must be of that class on assignment and retrieval. # Otherwise SerializationTypeMismatch will be raised. # # ==== Parameters # # * +attr_name+ - The field name that should be serialized. # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump` # or a class name that the object type should be equal to. # # ==== Example # # # Serialize a preferences attribute. # class User < ActiveRecord::Base # serialize :preferences # end # # # Serialize preferences using JSON as coder. # class User < ActiveRecord::Base # serialize :preferences, JSON # end # # # Serialize preferences as Hash using YAML coder. # class User < ActiveRecord::Base # serialize :preferences, Hash # end def serialize(attr_name, class_name_or_coder = Object) # When ::JSON is used, force it to go through the Active Support JSON encoder # to ensure special objects (e.g. Active Record models) are dumped correctly # using the #as_json hook. coder = if class_name_or_coder == ::JSON Coders::JSON elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } class_name_or_coder else Coders::YAMLColumn.new(class_name_or_coder) end decorate_attribute_type(attr_name, :serialize) do |type| Type::Serialized.new(type, coder) end end def serialized_attributes ActiveSupport::Deprecation.warn(<<-MSG.squish) `serialized_attributes` is deprecated without replacement, and will be removed in Rails 5.0. MSG @serialized_attributes ||= Hash[ columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c| [c.name, c.cast_type.coder] } ] end end end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb000066400000000000000000000043041266740050600312060ustar00rootroot00000000000000module ActiveRecord module AttributeMethods module TimeZoneConversion class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc: include Type::Decorator def type_cast_from_database(value) convert_time_to_time_zone(super) end def type_cast_from_user(value) if value.is_a?(Array) value.map { |v| type_cast_from_user(v) } elsif value.respond_to?(:in_time_zone) begin value.in_time_zone || super rescue ArgumentError nil end end end def convert_time_to_time_zone(value) if value.is_a?(Array) value.map { |v| convert_time_to_time_zone(v) } elsif value.acts_like?(:time) value.in_time_zone else value end end end extend ActiveSupport::Concern included do mattr_accessor :time_zone_aware_attributes, instance_writer: false self.time_zone_aware_attributes = false class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false self.skip_time_zone_conversion_for_attributes = [] end module ClassMethods private def inherited(subclass) # We need to apply this decorator here, rather than on module inclusion. The closure # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or # `skip_time_zone_conversion_for_attributes` would not be picked up. subclass.class_eval do matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) } decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type| TimeZoneConverter.new(type) end end super end def create_time_zone_conversion_attribute?(name, cast_type) time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && (:datetime == cast_type.type) end end end end end rails-4.2.6/activerecord/lib/active_record/attribute_methods/write.rb000066400000000000000000000047411266740050600261070ustar00rootroot00000000000000require 'active_support/core_ext/module/method_transplanting' module ActiveRecord module AttributeMethods module Write WriterMethodCache = Class.new(AttributeMethodCache) { private def method_body(method_name, const_name) <<-EOMETHOD def #{method_name}(value) name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} write_attribute(name, value) end EOMETHOD end }.new extend ActiveSupport::Concern included do attribute_method_suffix "=" end module ClassMethods protected if Module.methods_transplantable? def define_method_attribute=(name) method = WriterMethodCache[name] generated_attribute_methods.module_eval { define_method "#{name}=", method } end else def define_method_attribute=(name) safe_name = name.unpack('h*').first ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} write_attribute(name, value) end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end end end # Updates the attribute identified by attr_name with the # specified +value+. Empty strings for fixnum and float columns are # turned into +nil+. def write_attribute(attr_name, value) write_attribute_with_type_cast(attr_name, value, true) end def raw_write_attribute(attr_name, value) write_attribute_with_type_cast(attr_name, value, false) end private # Handle *= for method_missing. def attribute=(attribute_name, value) write_attribute(attribute_name, value) end def write_attribute_with_type_cast(attr_name, value, should_type_cast) attr_name = attr_name.to_s attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key if should_type_cast @attributes.write_from_user(attr_name, value) else @attributes.write_cast_value(attr_name, value) end value end end end end rails-4.2.6/activerecord/lib/active_record/attribute_set.rb000066400000000000000000000030411266740050600240750ustar00rootroot00000000000000require 'active_record/attribute_set/builder' module ActiveRecord class AttributeSet # :nodoc: def initialize(attributes) @attributes = attributes end def [](name) attributes[name] || Attribute.null(name) end def values_before_type_cast attributes.transform_values(&:value_before_type_cast) end def to_hash initialized_attributes.transform_values(&:value) end alias_method :to_h, :to_hash def key?(name) attributes.key?(name) && self[name].initialized? end def keys attributes.initialized_keys end def fetch_value(name) self[name].value { |n| yield n if block_given? } end def write_from_database(name, value) attributes[name] = self[name].with_value_from_database(value) end def write_from_user(name, value) attributes[name] = self[name].with_value_from_user(value) end def write_cast_value(name, value) attributes[name] = self[name].with_cast_value(value) end def freeze @attributes.freeze super end def initialize_dup(_) @attributes = attributes.dup super end def initialize_clone(_) @attributes = attributes.clone super end def reset(key) if key?(key) write_from_database(key, nil) end end def ==(other) attributes == other.attributes end protected attr_reader :attributes private def initialized_attributes attributes.select { |_, attr| attr.initialized? } end end end rails-4.2.6/activerecord/lib/active_record/attribute_set/000077500000000000000000000000001266740050600235525ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/attribute_set/builder.rb000066400000000000000000000047701266740050600255350ustar00rootroot00000000000000require 'active_record/attribute' module ActiveRecord class AttributeSet # :nodoc: class Builder # :nodoc: attr_reader :types, :always_initialized def initialize(types, always_initialized = nil) @types = types @always_initialized = always_initialized end def build_from_database(values = {}, additional_types = {}) if always_initialized && !values.key?(always_initialized) values[always_initialized] = nil end attributes = LazyAttributeHash.new(types, values, additional_types) AttributeSet.new(attributes) end end end class LazyAttributeHash # :nodoc: delegate :transform_values, to: :materialize def initialize(types, values, additional_types) @types = types @values = values @additional_types = additional_types @materialized = false @delegate_hash = {} end def key?(key) delegate_hash.key?(key) || values.key?(key) || types.key?(key) end def [](key) delegate_hash[key] || assign_default_value(key) end def []=(key, value) if frozen? raise RuntimeError, "Can't modify frozen hash" end delegate_hash[key] = value end def initialized_keys delegate_hash.keys | values.keys end def initialize_dup(_) @delegate_hash = delegate_hash.transform_values(&:dup) super end def select keys = types.keys | values.keys | delegate_hash.keys keys.each_with_object({}) do |key, hash| attribute = self[key] if yield(key, attribute) hash[key] = attribute end end end def ==(other) if other.is_a?(LazyAttributeHash) materialize == other.materialize else materialize == other end end protected attr_reader :types, :values, :additional_types, :delegate_hash def materialize unless @materialized values.each_key { |key| self[key] } types.each_key { |key| self[key] } unless frozen? @materialized = true end end delegate_hash end private def assign_default_value(name) type = additional_types.fetch(name, types[name]) value_present = true value = values.fetch(name) { value_present = false } if value_present delegate_hash[name] = Attribute.from_database(name, value, type) elsif types.key?(name) delegate_hash[name] = Attribute.uninitialized(name, type) end end end end rails-4.2.6/activerecord/lib/active_record/attributes.rb000066400000000000000000000115251266740050600234130ustar00rootroot00000000000000module ActiveRecord module Attributes # :nodoc: extend ActiveSupport::Concern Type = ActiveRecord::Type included do class_attribute :user_provided_columns, instance_accessor: false # :internal: class_attribute :user_provided_defaults, instance_accessor: false # :internal: self.user_provided_columns = {} self.user_provided_defaults = {} delegate :persistable_attribute_names, to: :class end module ClassMethods # :nodoc: # Defines or overrides a attribute on this model. This allows customization of # Active Record's type casting behavior, as well as adding support for user defined # types. # # +name+ The name of the methods to define attribute methods for, and the column which # this will persist to. # # +cast_type+ A type object that contains information about how to type cast the value. # See the examples section for more information. # # ==== Options # The options hash accepts the following options: # # +default+ is the default value that the column should use on a new record. # # ==== Examples # # The type detected by Active Record can be overridden. # # # db/schema.rb # create_table :store_listings, force: true do |t| # t.decimal :price_in_cents # end # # # app/models/store_listing.rb # class StoreListing < ActiveRecord::Base # end # # store_listing = StoreListing.new(price_in_cents: '10.1') # # # before # store_listing.price_in_cents # => BigDecimal.new(10.1) # # class StoreListing < ActiveRecord::Base # attribute :price_in_cents, Type::Integer.new # end # # # after # store_listing.price_in_cents # => 10 # # Users may also define their own custom types, as long as they respond to the methods # defined on the value type. The `type_cast` method on your type object will be called # with values both from the database, and from your controllers. See # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your # type objects inherit from an existing type, or the base value type. # # class MoneyType < ActiveRecord::Type::Integer # def type_cast(value) # if value.include?('$') # price_in_dollars = value.gsub(/\$/, '').to_f # price_in_dollars * 100 # else # value.to_i # end # end # end # # class StoreListing < ActiveRecord::Base # attribute :price_in_cents, MoneyType.new # end # # store_listing = StoreListing.new(price_in_cents: '$10.00') # store_listing.price_in_cents # => 1000 def attribute(name, cast_type, options = {}) name = name.to_s clear_caches_calculated_from_columns # Assign a new hash to ensure that subclasses do not share a hash self.user_provided_columns = user_provided_columns.merge(name => cast_type) if options.key?(:default) self.user_provided_defaults = user_provided_defaults.merge(name => options[:default]) end end # Returns an array of column objects for the table associated with this class. def columns @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name)) end # Returns a hash of column objects for the table associated with this class. def columns_hash @columns_hash ||= Hash[columns.map { |c| [c.name, c] }] end def persistable_attribute_names # :nodoc: @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys end def reset_column_information # :nodoc: super clear_caches_calculated_from_columns end private def add_user_provided_columns(schema_columns) existing_columns = schema_columns.map do |column| new_type = user_provided_columns[column.name] if new_type column.with_type(new_type) else column end end existing_column_names = existing_columns.map(&:name) new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)| connection.new_column(name, nil, type) end existing_columns + new_columns end def clear_caches_calculated_from_columns @attributes_builder = nil @column_names = nil @column_types = nil @columns = nil @columns_hash = nil @content_columns = nil @default_attributes = nil @persistable_attribute_names = nil end def raw_default_values super.merge(user_provided_defaults) end end end end rails-4.2.6/activerecord/lib/active_record/autosave_association.rb000066400000000000000000000425161266740050600254540ustar00rootroot00000000000000module ActiveRecord # = Active Record Autosave Association # # +AutosaveAssociation+ is a module that takes care of automatically saving # associated records when their parent is saved. In addition to saving, it # also destroys any associated records that were marked for destruction. # (See +mark_for_destruction+ and marked_for_destruction?). # # Saving of the parent, its associations, and the destruction of marked # associations, all happen inside a transaction. This should never leave the # database in an inconsistent state. # # If validations for any of the associations fail, their error messages will # be applied to the parent. # # Note that it also means that associations marked for destruction won't # be destroyed directly. They will however still be marked for destruction. # # Note that autosave: false is not same as not declaring :autosave. # When the :autosave option is not present then new association records are # saved but the updated association records are not saved. # # == Validation # # Children records are validated unless :validate is +false+. # # == Callbacks # # Association with autosave option defines several callbacks on your # model (before_save, after_create, after_update). Please note that # callbacks are executed in the order they were defined in # model. You should avoid modifying the association content, before # autosave callbacks are executed. Placing your callbacks after # associations is usually a good practice. # # === One-to-one Example # # class Post < ActiveRecord::Base # has_one :author, autosave: true # end # # Saving changes to the parent and its associated model can now be performed # automatically _and_ atomically: # # post = Post.find(1) # post.title # => "The current global position of migrating ducks" # post.author.name # => "alloy" # # post.title = "On the migration of ducks" # post.author.name = "Eloy Duran" # # post.save # post.reload # post.title # => "On the migration of ducks" # post.author.name # => "Eloy Duran" # # Destroying an associated model, as part of the parent's save action, is as # simple as marking it for destruction: # # post.author.mark_for_destruction # post.author.marked_for_destruction? # => true # # Note that the model is _not_ yet removed from the database: # # id = post.author.id # Author.find_by(id: id).nil? # => false # # post.save # post.reload.author # => nil # # Now it _is_ removed from the database: # # Author.find_by(id: id).nil? # => true # # === One-to-many Example # # When :autosave is not declared new children are saved when their parent is saved: # # class Post < ActiveRecord::Base # has_many :comments # :autosave option is not declared # end # # post = Post.new(title: 'ruby rocks') # post.comments.build(body: 'hello world') # post.save # => saves both post and comment # # post = Post.create(title: 'ruby rocks') # post.comments.build(body: 'hello world') # post.save # => saves both post and comment # # post = Post.create(title: 'ruby rocks') # post.comments.create(body: 'hello world') # post.save # => saves both post and comment # # When :autosave is true all children are saved, no matter whether they # are new records or not: # # class Post < ActiveRecord::Base # has_many :comments, autosave: true # end # # post = Post.create(title: 'ruby rocks') # post.comments.create(body: 'hello world') # post.comments[0].body = 'hi everyone' # post.comments.build(body: "good morning.") # post.title += "!" # post.save # => saves both post and comments. # # Destroying one of the associated models as part of the parent's save action # is as simple as marking it for destruction: # # post.comments # => [#, # # post.comments[1].mark_for_destruction # post.comments[1].marked_for_destruction? # => true # post.comments.length # => 2 # # Note that the model is _not_ yet removed from the database: # # id = post.comments.last.id # Comment.find_by(id: id).nil? # => false # # post.save # post.reload.comments.length # => 1 # # Now it _is_ removed from the database: # # Comment.find_by(id: id).nil? # => true module AutosaveAssociation extend ActiveSupport::Concern module AssociationBuilderExtension #:nodoc: def self.build(model, reflection) model.send(:add_autosave_association_callbacks, reflection) end def self.valid_options [ :autosave ] end end included do Associations::Builder::Association.extensions << AssociationBuilderExtension end module ClassMethods private def define_non_cyclic_method(name, &block) return if method_defined?(name) define_method(name) do |*args| result = true; @_already_called ||= {} # Loop prevention for validation of associations unless @_already_called[name] begin @_already_called[name]=true result = instance_eval(&block) ensure @_already_called[name]=false end end result end end # Adds validation and save callbacks for the association as specified by # the +reflection+. # # For performance reasons, we don't check whether to validate at runtime. # However the validation and callback methods are lazy and those methods # get created when they are invoked for the very first time. However, # this can change, for instance, when using nested attributes, which is # called _after_ the association has been defined. Since we don't want # the callbacks to get defined multiple times, there are guards that # check if the save or validation methods have already been defined # before actually defining them. def add_autosave_association_callbacks(reflection) save_method = :"autosave_associated_records_for_#{reflection.name}" if reflection.collection? before_save :before_save_collection_association define_non_cyclic_method(save_method) { save_collection_association(reflection) } # Doesn't use after_save as that would save associations added in after_create/after_update twice after_create save_method after_update save_method elsif reflection.has_one? define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method) # Configures two callbacks instead of a single after_save so that # the model may rely on their execution order relative to its # own callbacks. # # For example, given that after_creates run before after_saves, if # we configured instead an after_save there would be no way to fire # a custom after_create callback after the child association gets # created. after_create save_method after_update save_method else define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) } before_save save_method end define_autosave_validation_callbacks(reflection) end def define_autosave_validation_callbacks(reflection) validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) if reflection.collection? method = :validate_collection_association else method = :validate_single_association end define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method end end end # Reloads the attributes of the object as usual and clears marked_for_destruction flag. def reload(options = nil) @marked_for_destruction = false @destroyed_by_association = nil super end # Marks this record to be destroyed as part of the parents save transaction. # This does _not_ actually destroy the record instantly, rather child record will be destroyed # when parent.save is called. # # Only useful if the :autosave option on the parent is enabled for this associated model. def mark_for_destruction @marked_for_destruction = true end # Returns whether or not this record will be destroyed as part of the parents save transaction. # # Only useful if the :autosave option on the parent is enabled for this associated model. def marked_for_destruction? @marked_for_destruction end # Records the association that is being destroyed and destroying this # record in the process. def destroyed_by_association=(reflection) @destroyed_by_association = reflection end # Returns the association for the parent being destroyed. # # Used to avoid updating the counter cache unnecessarily. def destroyed_by_association @destroyed_by_association end # Returns whether or not this record has been changed in any way (including whether # any of its nested autosave associations are likewise changed) def changed_for_autosave? new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave? end private # Returns the record for an association collection that should be validated # or saved. If +autosave+ is +false+ only new records will be returned, # unless the parent is/was a new record itself. def associated_records_to_validate_or_save(association, new_record, autosave) if new_record association && association.target elsif autosave association.target.find_all { |record| record.changed_for_autosave? } else association.target.find_all { |record| record.new_record? } end end # go through nested autosave associations that are loaded in memory (without loading # any new ones), and return true if is changed for autosave def nested_records_changed_for_autosave? @_nested_records_changed_for_autosave_already_called ||= false return false if @_nested_records_changed_for_autosave_already_called begin @_nested_records_changed_for_autosave_already_called = true self.class._reflections.values.any? do |reflection| if reflection.options[:autosave] association = association_instance_get(reflection.name) association && Array.wrap(association.target).any?(&:changed_for_autosave?) end end ensure @_nested_records_changed_for_autosave_already_called = false end end # Validate the association if :validate or :autosave is # turned on for the association. def validate_single_association(reflection) association = association_instance_get(reflection.name) record = association && association.reader association_valid?(reflection, record) if record end # Validate the associated records if :validate or # :autosave is turned on for the association specified by # +reflection+. def validate_collection_association(reflection) if association = association_instance_get(reflection.name) if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave]) records.each { |record| association_valid?(reflection, record) } end end end # Returns whether or not the association is valid and applies any errors to # the parent, self, if it wasn't. Skips any :autosave # enabled records if they're marked_for_destruction? or destroyed. def association_valid?(reflection, record) return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?) validation_context = self.validation_context unless [:create, :update].include?(self.validation_context) unless valid = record.valid?(validation_context) if reflection.options[:autosave] record.errors.each do |attribute, message| attribute = "#{reflection.name}.#{attribute}" errors[attribute] << message errors[attribute].uniq! end else errors.add(reflection.name) end end valid end # Is used as a before_save callback to check while saving a collection # association whether or not the parent was a new record before saving. def before_save_collection_association @new_record_before_save = new_record? true end # Saves any new associated records, or all loaded autosave associations if # :autosave is enabled on the association. # # In addition, it destroys all children that were marked for destruction # with mark_for_destruction. # # This all happens inside a transaction, _if_ the Transactions module is included into # ActiveRecord::Base after the AutosaveAssociation module, which it does by default. def save_collection_association(reflection) if association = association_instance_get(reflection.name) autosave = reflection.options[:autosave] if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave) if autosave records_to_destroy = records.select(&:marked_for_destruction?) records_to_destroy.each { |record| association.destroy(record) } records -= records_to_destroy end records.each do |record| next if record.destroyed? saved = true if autosave != false && (@new_record_before_save || record.new_record?) if autosave saved = association.insert_record(record, false) else association.insert_record(record) unless reflection.nested? end elsif autosave saved = record.save(:validate => false) end raise ActiveRecord::Rollback unless saved end end # reconstruct the scope now that we know the owner's id association.reset_scope if association.respond_to?(:reset_scope) end end # Saves the associated record if it's new or :autosave is enabled # on the association. # # In addition, it will destroy the association if it was marked for # destruction with mark_for_destruction. # # This all happens inside a transaction, _if_ the Transactions module is included into # ActiveRecord::Base after the AutosaveAssociation module, which it does by default. def save_has_one_association(reflection) association = association_instance_get(reflection.name) record = association && association.load_target if record && !record.destroyed? autosave = reflection.options[:autosave] if autosave && record.marked_for_destruction? record.destroy elsif autosave != false key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key) unless reflection.through_reflection record[reflection.foreign_key] = key end saved = record.save(:validate => !autosave) raise ActiveRecord::Rollback if !saved && autosave saved end end end end # If the record is new or it has changed, returns true. def record_changed?(reflection, record, key) record.new_record? || (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) || record.attribute_changed?(reflection.foreign_key) end # Saves the associated record if it's new or :autosave is enabled. # # In addition, it will destroy the association if it was marked for destruction. def save_belongs_to_association(reflection) association = association_instance_get(reflection.name) record = association && association.load_target if record && !record.destroyed? autosave = reflection.options[:autosave] if autosave && record.marked_for_destruction? self[reflection.foreign_key] = nil record.destroy elsif autosave != false saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?) if association.updated? association_id = record.send(reflection.options[:primary_key] || :id) self[reflection.foreign_key] = association_id association.loaded! end saved if autosave end end end end end rails-4.2.6/activerecord/lib/active_record/base.rb000066400000000000000000000334471266740050600221460ustar00rootroot00000000000000require 'yaml' require 'set' require 'active_support/benchmarkable' require 'active_support/dependencies' require 'active_support/descendants_tracker' require 'active_support/time' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/class/delegating_attributes' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/transform_values' require 'active_support/core_ext/string/behavior' require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/class/subclasses' require 'arel' require 'active_record/attribute_decorators' require 'active_record/errors' require 'active_record/log_subscriber' require 'active_record/explain_subscriber' require 'active_record/relation/delegation' require 'active_record/attributes' module ActiveRecord #:nodoc: # = Active Record # # Active Record objects don't specify their attributes directly, but rather infer them from # the table definition with which they're linked. Adding, removing, and changing attributes # and their type is done directly in the database. Any change is instantly reflected in the # Active Record objects. The mapping that binds a given Active Record class to a certain # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones. # # See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight. # # == Creation # # Active Records accept constructor parameters either in a hash or as a block. The hash # method is especially useful when you're receiving the data from somewhere else, like an # HTTP request. It works like this: # # user = User.new(name: "David", occupation: "Code Artist") # user.name # => "David" # # You can also use block initialization: # # user = User.new do |u| # u.name = "David" # u.occupation = "Code Artist" # end # # And of course you can just create a bare object and specify the attributes after the fact: # # user = User.new # user.name = "David" # user.occupation = "Code Artist" # # == Conditions # # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement. # The array form is to be used when the condition input is tainted and requires sanitization. The string form can # be used for statements that don't involve tainted data. The hash form works much like the array form, except # only equality and range is possible. Examples: # # class User < ActiveRecord::Base # def self.authenticate_unsafely(user_name, password) # where("user_name = '#{user_name}' AND password = '#{password}'").first # end # # def self.authenticate_safely(user_name, password) # where("user_name = ? AND password = ?", user_name, password).first # end # # def self.authenticate_safely_simply(user_name, password) # where(user_name: user_name, password: password).first # end # end # # The authenticate_unsafely method inserts the parameters directly into the query # and is thus susceptible to SQL-injection attacks if the user_name and +password+ # parameters come directly from an HTTP request. The authenticate_safely and # authenticate_safely_simply both will sanitize the user_name and +password+ # before inserting them in the query, which will ensure that an attacker can't escape the # query and fake the login (or worse). # # When using multiple parameters in the conditions, it can easily become hard to read exactly # what the fourth or fifth question mark is supposed to represent. In those cases, you can # resort to named bind variables instead. That's done by replacing the question marks with # symbols and supplying a hash with values for the matching symbol keys: # # Company.where( # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date", # { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' } # ).first # # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND # operator. For instance: # # Student.where(first_name: "Harvey", status: 1) # Student.where(params[:student]) # # A range may be used in the hash to use the SQL BETWEEN operator: # # Student.where(grade: 9..12) # # An array may be used in the hash to use the SQL IN operator: # # Student.where(grade: [9,11,12]) # # When joining tables, nested hashes or keys written in the form 'table_name.column_name' # can be used to qualify the table name of a particular condition. For instance: # # Student.joins(:schools).where(schools: { category: 'public' }) # Student.joins(:schools).where('schools.category' => 'public' ) # # == Overwriting default accessors # # All column values are automatically available through basic accessors on the Active Record # object, but sometimes you want to specialize this behavior. This can be done by overwriting # the default accessors (using the same name as the attribute) and calling # +super+ to actually change things. # # class Song < ActiveRecord::Base # # Uses an integer of seconds to hold the length of the song # # def length=(minutes) # super(minutes.to_i * 60) # end # # def length # super / 60 # end # end # # You can alternatively use self[:attribute]=(value) and self[:attribute] # or write_attribute(:attribute, value) and read_attribute(:attribute). # # == Attribute query methods # # In addition to the basic accessors, query methods are also automatically available on the Active Record object. # Query methods allow you to test whether an attribute value is present. # For numeric values, present is defined as non-zero. # # For example, an Active Record User with the name attribute has a name? method that you can call # to determine whether the user has a name: # # user = User.new(name: "David") # user.name? # => true # # anonymous = User.new(name: "") # anonymous.name? # => false # # == Accessing attributes before they have been typecasted # # Sometimes you want to be able to read the raw attribute data without having the column-determined # typecast run its course first. That can be done by using the _before_type_cast # accessors that all attributes have. For example, if your Account model has a balance attribute, # you can call account.balance_before_type_cast or account.id_before_type_cast. # # This is especially useful in validation situations where the user might supply a string for an # integer field and you want to display the original string back in an error message. Accessing the # attribute normally would typecast the string to 0, which isn't what you want. # # == Dynamic attribute-based finders # # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects # by simple queries without turning to SQL. They work by appending the name of an attribute # to find_by_ like Person.find_by_user_name. # Instead of writing Person.find_by(user_name: user_name), you can use # Person.find_by_user_name(user_name). # # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an # ActiveRecord::RecordNotFound error if they do not return any records, # like Person.find_by_last_name!. # # It's also possible to use multiple attributes in the same find by separating them with "_and_". # # Person.find_by(user_name: user_name, password: password) # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder # # It's even possible to call these dynamic finder methods on relations and named scopes. # # Payment.order("created_on").find_by_amount(50) # # == Saving arrays, hashes, and other non-mappable objects in text columns # # Active Record can serialize any object in text columns using YAML. To do so, you must # specify this with a call to the class method +serialize+. # This makes it possible to store arrays, hashes, and other non-mappable objects without doing # any additional work. # # class User < ActiveRecord::Base # serialize :preferences # end # # user = User.create(preferences: { "background" => "black", "display" => large }) # User.find(user.id).preferences # => { "background" => "black", "display" => large } # # You can also specify a class option as the second parameter that'll raise an exception # if a serialized object is retrieved as a descendant of a class not in the hierarchy. # # class User < ActiveRecord::Base # serialize :preferences, Hash # end # # user = User.create(preferences: %w( one two three )) # User.find(user.id).preferences # raises SerializationTypeMismatch # # When you specify a class option, the default value for that attribute will be a new # instance of that class. # # class User < ActiveRecord::Base # serialize :preferences, OpenStruct # end # # user = User.new # user.preferences.theme_color = "red" # # # == Single table inheritance # # Active Record allows inheritance by storing the name of the class in a # column that is named "type" by default. See ActiveRecord::Inheritance for # more details. # # == Connection to multiple databases in different models # # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this # connection. But you can also set a class-specific connection. For example, if Course is an # ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection # and Course and all of its subclasses will use this connection instead. # # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is # a Hash indexed by the class. If a connection is requested, the retrieve_connection method # will go up the class-hierarchy until a connection is found in the connection pool. # # == Exceptions # # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record. # * AdapterNotSpecified - The configuration hash used in establish_connection didn't include an # :adapter key. # * AdapterNotFound - The :adapter key used in establish_connection specified a # non-existent adapter # (or a bad spelling of an existing one). # * AssociationTypeMismatch - The object assigned to the association wasn't of the type # specified in the association definition. # * AttributeAssignmentError - An error occurred while doing a mass assignment through the # attributes= method. # You can inspect the +attribute+ property of the exception object to determine which attribute # triggered the error. # * ConnectionNotEstablished - No connection has been established. Use establish_connection # before querying. # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the # attributes= method. The +errors+ property of this exception contains an array of # AttributeAssignmentError # objects that should be inspected to determine which attributes triggered the errors. # * RecordInvalid - raised by save! and create! when the record is invalid. # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal # nothing was found, please check its documentation for further details. # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter. # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message. # # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level). # So it's possible to assign a logger to the class through Base.logger= which will then be used by all # instances in the current object space. class Base extend ActiveModel::Naming extend ActiveSupport::Benchmarkable extend ActiveSupport::DescendantsTracker extend ConnectionHandling extend QueryCache::ClassMethods extend Querying extend Translation extend DynamicMatchers extend Explain extend Enum extend Delegation::DelegateCache include Core include Persistence include ReadonlyAttributes include ModelSchema include Inheritance include Scoping include Sanitization include AttributeAssignment include ActiveModel::Conversion include Integration include Validations include CounterCache include Attributes include AttributeDecorators include Locking::Optimistic include Locking::Pessimistic include AttributeMethods include Callbacks include Timestamp include Associations include ActiveModel::SecurePassword include AutosaveAssociation include NestedAttributes include Aggregations include Transactions include NoTouching include Reflection include Serialization include Store end ActiveSupport.run_load_hooks(:active_record, Base) end rails-4.2.6/activerecord/lib/active_record/callbacks.rb000066400000000000000000000300501266740050600231360ustar00rootroot00000000000000module ActiveRecord # = Active Record Callbacks # # Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic # before or after an alteration of the object state. This can be used to make sure that associated and # dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider # the Base#save call for a new record: # # * (-) save # * (-) valid # * (1) before_validation # * (-) validate # * (2) after_validation # * (3) before_save # * (4) before_create # * (-) create # * (5) after_create # * (6) after_save # * (7) after_commit # # Also, an after_rollback callback can be configured to be triggered whenever a rollback is issued. # Check out ActiveRecord::Transactions for more details about after_commit and # after_rollback. # # Additionally, an after_touch callback is triggered whenever an # object is touched. # # Lastly an after_find and after_initialize callback is triggered for each object that # is found and instantiated by a finder, with after_initialize being triggered after new objects # are instantiated as well. # # There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the # Active Record life cycle. The sequence for calling Base#save for an existing record is similar, # except that each _create callback is replaced by the corresponding _update callback. # # Examples: # class CreditCard < ActiveRecord::Base # # Strip everything but digits, so the user can specify "555 234 34" or # # "5552-3434" and both will mean "55523434" # before_validation(on: :create) do # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number") # end # end # # class Subscription < ActiveRecord::Base # before_create :record_signup # # private # def record_signup # self.signed_up_on = Date.today # end # end # # class Firm < ActiveRecord::Base # # Destroys the associated clients and people when the firm is destroyed # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" } # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" } # end # # == Inheritable callback queues # # Besides the overwritable callback methods, it's also possible to register callbacks through the # use of the callback macros. Their main advantage is that the macros add behavior into a callback # queue that is kept intact down through an inheritance hierarchy. # # class Topic < ActiveRecord::Base # before_destroy :destroy_author # end # # class Reply < Topic # before_destroy :destroy_readers # end # # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is # run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation # where the +before_destroy+ method is overridden: # # class Topic < ActiveRecord::Base # def before_destroy() destroy_author end # end # # class Reply < Topic # def before_destroy() destroy_readers end # end # # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. # So, use the callback macros when you want to ensure that a certain callback is called for the entire # hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant # to decide whether they want to call +super+ and trigger the inherited callbacks. # # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the # callbacks before specifying the associations. Otherwise, you might trigger the loading of a # child before the parent has registered the callbacks and they won't be inherited. # # == Types of callbacks # # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects, # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects # are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for # creating mix-ins), and inline eval methods are deprecated. # # The method reference callbacks work by specifying a protected or private method available in the object, like this: # # class Topic < ActiveRecord::Base # before_destroy :delete_parents # # private # def delete_parents # self.class.delete_all "parent_id = #{id}" # end # end # # The callback objects have methods named after the callback called with the record as the only parameter, such as: # # class BankAccount < ActiveRecord::Base # before_save EncryptionWrapper.new # after_save EncryptionWrapper.new # after_initialize EncryptionWrapper.new # end # # class EncryptionWrapper # def before_save(record) # record.credit_card_number = encrypt(record.credit_card_number) # end # # def after_save(record) # record.credit_card_number = decrypt(record.credit_card_number) # end # # alias_method :after_initialize, :after_save # # private # def encrypt(value) # # Secrecy is committed # end # # def decrypt(value) # # Secrecy is unveiled # end # end # # So you specify the object you want messaged on a given callback. When that callback is triggered, the object has # a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other # initialization data such as the name of the attribute to work with: # # class BankAccount < ActiveRecord::Base # before_save EncryptionWrapper.new("credit_card_number") # after_save EncryptionWrapper.new("credit_card_number") # after_initialize EncryptionWrapper.new("credit_card_number") # end # # class EncryptionWrapper # def initialize(attribute) # @attribute = attribute # end # # def before_save(record) # record.send("#{@attribute}=", encrypt(record.send("#{@attribute}"))) # end # # def after_save(record) # record.send("#{@attribute}=", decrypt(record.send("#{@attribute}"))) # end # # alias_method :after_initialize, :after_save # # private # def encrypt(value) # # Secrecy is committed # end # # def decrypt(value) # # Secrecy is unveiled # end # end # # The callback macros usually accept a symbol for the method they're supposed to run, but you can also # pass a "method string", which will then be evaluated within the binding of the callback. Example: # # class Topic < ActiveRecord::Base # before_destroy 'self.class.delete_all "parent_id = #{id}"' # end # # Notice that single quotes (') are used so the #{id} part isn't evaluated until the callback # is triggered. Also note that these inline callbacks can be stacked just like the regular ones: # # class Topic < ActiveRecord::Base # before_destroy 'self.class.delete_all "parent_id = #{id}"', # 'puts "Evaluated after parents are destroyed"' # end # # == before_validation* returning statements # # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be # aborted and Base#save will return +false+. If Base#save! is called it will raise a # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object. # # == Canceling callbacks # # If a before_* callback returns +false+, all the later callbacks and the associated action are # cancelled. # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as # methods on the model, which are called last. # # == Ordering callbacks # # Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+ # callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option. # # Let's look at the code below: # # class Topic < ActiveRecord::Base # has_many :children, dependent: destroy # # before_destroy :log_children # # private # def log_children # # Child processing # end # end # # In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available # because the +destroy+ callback gets executed first. You can use the +prepend+ option on the +before_destroy+ callback to avoid this. # # class Topic < ActiveRecord::Base # has_many :children, dependent: destroy # # before_destroy :log_children, prepend: true # # private # def log_children # # Child processing # end # end # # This way, the +before_destroy+ gets executed before the dependent: destroy is called, and the data is still available. # # == Transactions # # The entire callback chain of a +save+, save!, or +destroy+ call runs # within a transaction. That includes after_* hooks. If everything # goes fine a COMMIT is executed once the chain has been completed. # # If a before_* callback cancels the action a ROLLBACK is issued. You # can also trigger a ROLLBACK raising an exception in any of the callbacks, # including after_* hooks. Note, however, that in that case the client # needs to be aware of it because an ordinary +save+ will raise such exception # instead of quietly returning +false+. # # == Debugging callbacks # # The callback chain is accessible via the _*_callbacks method on an object. ActiveModel Callbacks support # :before, :after and :around as values for the kind property. The kind property # defines what part of the chain the callback runs in. # # To find all callbacks in the before_save callback chain: # # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) } # # Returns an array of callback objects that form the before_save chain. # # To further check if the before_save chain contains a proc defined as rest_when_dead use the filter property of the callback object: # # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead) # # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model. # module Callbacks extend ActiveSupport::Concern CALLBACKS = [ :after_initialize, :after_find, :after_touch, :before_validation, :after_validation, :before_save, :around_save, :after_save, :before_create, :around_create, :after_create, :before_update, :around_update, :after_update, :before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback ] module ClassMethods include ActiveModel::Callbacks end included do include ActiveModel::Validations::Callbacks define_model_callbacks :initialize, :find, :touch, :only => :after define_model_callbacks :save, :create, :update, :destroy end def destroy #:nodoc: _run_destroy_callbacks { super } end def touch(*) #:nodoc: _run_touch_callbacks { super } end private def create_or_update #:nodoc: _run_save_callbacks { super } end def _create_record #:nodoc: _run_create_callbacks { super } end def _update_record(*) #:nodoc: _run_update_callbacks { super } end end end rails-4.2.6/activerecord/lib/active_record/coders/000077500000000000000000000000001266740050600221535ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/coders/json.rb000066400000000000000000000004031266740050600234460ustar00rootroot00000000000000module ActiveRecord module Coders # :nodoc: class JSON # :nodoc: def self.dump(obj) ActiveSupport::JSON.encode(obj) end def self.load(json) ActiveSupport::JSON.decode(json) unless json.nil? end end end end rails-4.2.6/activerecord/lib/active_record/coders/yaml_column.rb000066400000000000000000000017221266740050600250210ustar00rootroot00000000000000require 'yaml' module ActiveRecord module Coders # :nodoc: class YAMLColumn # :nodoc: attr_accessor :object_class def initialize(object_class = Object) @object_class = object_class end def dump(obj) return if obj.nil? unless obj.is_a?(object_class) raise SerializationTypeMismatch, "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}" end YAML.dump obj end def load(yaml) return object_class.new if object_class != Object && yaml.nil? return yaml unless yaml.is_a?(String) && yaml =~ /^---/ obj = YAML.load(yaml) unless obj.is_a?(object_class) || obj.nil? raise SerializationTypeMismatch, "Attribute was supposed to be a #{object_class}, but was a #{obj.class}" end obj ||= object_class.new if object_class != Object obj end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/000077500000000000000000000000001266740050600247165ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/000077500000000000000000000000001266740050600265215ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb000066400000000000000000000534621266740050600322500ustar00rootroot00000000000000require 'thread' require 'thread_safe' require 'monitor' require 'set' require 'active_support/core_ext/string/filters' module ActiveRecord # Raised when a connection could not be obtained within the connection # acquisition timeout period: because max connections in pool # are in use. class ConnectionTimeoutError < ConnectionNotEstablished end module ConnectionAdapters # Connection pool base class for managing Active Record database # connections. # # == Introduction # # A connection pool synchronizes thread access to a limited number of # database connections. The basic idea is that each thread checks out a # database connection from the pool, uses that connection, and checks the # connection back in. ConnectionPool is completely thread-safe, and will # ensure that a connection cannot be used by two threads at the same time, # as long as ConnectionPool's contract is correctly followed. It will also # handle cases in which there are more threads than connections: if all # connections have been checked out, and a thread tries to checkout a # connection anyway, then ConnectionPool will wait until some other thread # has checked in a connection. # # == Obtaining (checking out) a connection # # Connections can be obtained and used from a connection pool in several # ways: # # 1. Simply use ActiveRecord::Base.connection as with Active Record 2.1 and # earlier (pre-connection-pooling). Eventually, when you're done with # the connection(s) and wish it to be returned to the pool, you call # ActiveRecord::Base.clear_active_connections!. This will be the # default behavior for Active Record when used in conjunction with # Action Pack's request handling cycle. # 2. Manually check out a connection from the pool with # ActiveRecord::Base.connection_pool.checkout. You are responsible for # returning this connection to the pool when finished by calling # ActiveRecord::Base.connection_pool.checkin(connection). # 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which # obtains a connection, yields it as the sole argument to the block, # and returns it to the pool after the block completes. # # Connections in the pool are actually AbstractAdapter objects (or objects # compatible with AbstractAdapter's interface). # # == Options # # There are several connection-pooling-related options that you can add to # your database connection configuration: # # * +pool+: number indicating size of connection pool (default 5) # * +checkout_timeout+: number of seconds to block and wait for a connection # before giving up and raising a timeout error (default 5 seconds). # * +reaping_frequency+: frequency in seconds to periodically run the # Reaper, which attempts to find and recover connections from dead # threads, which can occur if a programmer forgets to close a # connection at the end of a thread or a thread dies unexpectedly. # Regardless of this setting, the Reaper will be invoked before every # blocking wait. (Default nil, which means don't schedule the Reaper). class ConnectionPool # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool # with which it shares a Monitor. But could be a generic Queue. # # The Queue in stdlib's 'thread' could replace this class except # stdlib's doesn't support waiting with a timeout. class Queue def initialize(lock = Monitor.new) @lock = lock @cond = @lock.new_cond @num_waiting = 0 @queue = [] end # Test if any threads are currently waiting on the queue. def any_waiting? synchronize do @num_waiting > 0 end end # Returns the number of threads currently waiting on this # queue. def num_waiting synchronize do @num_waiting end end # Add +element+ to the queue. Never blocks. def add(element) synchronize do @queue.push element @cond.signal end end # If +element+ is in the queue, remove and return it, or nil. def delete(element) synchronize do @queue.delete(element) end end # Remove all elements from the queue. def clear synchronize do @queue.clear end end # Remove the head of the queue. # # If +timeout+ is not given, remove and return the head the # queue if the number of available elements is strictly # greater than the number of threads currently waiting (that # is, don't jump ahead in line). Otherwise, return nil. # # If +timeout+ is given, block if it there is no element # available, waiting up to +timeout+ seconds for an element to # become available. # # Raises: # - ConnectionTimeoutError if +timeout+ is given and no element # becomes available after +timeout+ seconds, def poll(timeout = nil) synchronize do if timeout no_wait_poll || wait_poll(timeout) else no_wait_poll end end end private def synchronize(&block) @lock.synchronize(&block) end # Test if the queue currently contains any elements. def any? !@queue.empty? end # A thread can remove an element from the queue without # waiting if an only if the number of currently available # connections is strictly greater than the number of waiting # threads. def can_remove_no_wait? @queue.size > @num_waiting end # Removes and returns the head of the queue if possible, or nil. def remove @queue.shift end # Remove and return the head the queue if the number of # available elements is strictly greater than the number of # threads currently waiting. Otherwise, return nil. def no_wait_poll remove if can_remove_no_wait? end # Waits on the queue up to +timeout+ seconds, then removes and # returns the head of the queue. def wait_poll(timeout) @num_waiting += 1 t0 = Time.now elapsed = 0 loop do @cond.wait(timeout - elapsed) return remove if any? elapsed = Time.now - t0 if elapsed >= timeout msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' % [timeout, elapsed] raise ConnectionTimeoutError, msg end end ensure @num_waiting -= 1 end end # Every +frequency+ seconds, the reaper will call +reap+ on +pool+. # A reaper instantiated with a nil frequency will never reap the # connection pool. # # Configure the frequency by setting "reaping_frequency" in your # database yaml file. class Reaper attr_reader :pool, :frequency def initialize(pool, frequency) @pool = pool @frequency = frequency end def run return unless frequency Thread.new(frequency, pool) { |t, p| while true sleep t p.reap end } end end include MonitorMixin attr_accessor :automatic_reconnect, :checkout_timeout attr_reader :spec, :connections, :size, :reaper # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification # object which describes database connection information (e.g. adapter, # host name, username, password, etc), as well as the maximum size for # this ConnectionPool. # # The default ConnectionPool maximum size is 5. def initialize(spec) super() @spec = spec @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5 @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f)) @reaper.run # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 # The cache of reserved connections mapped to threads @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size) @connections = [] @automatic_reconnect = true @available = Queue.new self end # Retrieve the connection associated with the current thread, or call # #checkout to obtain one if necessary. # # #connection can be called any number of times; the connection is # held in a hash keyed by the thread id. def connection # this is correctly done double-checked locking # (ThreadSafe::Cache's lookups have volatile semantics) @reserved_connections[current_connection_id] || synchronize do @reserved_connections[current_connection_id] ||= checkout end end # Is there an open connection that is being used for the current thread? def active_connection? synchronize do @reserved_connections.fetch(current_connection_id) { return false }.in_use? end end # Signal that the thread is finished with the current connection. # #release_connection releases the connection-thread association # and returns the connection to the pool. def release_connection(with_id = current_connection_id) synchronize do conn = @reserved_connections.delete(with_id) checkin conn if conn end end # If a connection already exists yield it to the block. If no connection # exists checkout a connection, yield it to the block, and checkin the # connection when finished. def with_connection connection_id = current_connection_id fresh_connection = true unless active_connection? yield connection ensure release_connection(connection_id) if fresh_connection end # Returns true if a connection has already been opened. def connected? synchronize { @connections.any? } end # Disconnects all connections in the pool, and clears the pool. def disconnect! synchronize do @reserved_connections.clear @connections.each do |conn| checkin conn conn.disconnect! end @connections = [] @available.clear end end # Clears the cache which maps classes. def clear_reloadable_connections! synchronize do @reserved_connections.clear @connections.each do |conn| checkin conn conn.disconnect! if conn.requires_reloading? end @connections.delete_if do |conn| conn.requires_reloading? end @available.clear @connections.each do |conn| @available.add conn end end end # Check-out a database connection from the pool, indicating that you want # to use it. You should call #checkin when you no longer need this. # # This is done by either returning and leasing existing connection, or by # creating a new connection and leasing it. # # If all connections are leased and the pool is at capacity (meaning the # number of currently leased connections is greater than or equal to the # size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised. # # Returns: an AbstractAdapter object. # # Raises: # - ConnectionTimeoutError: no connection can be obtained from the pool. def checkout synchronize do conn = acquire_connection conn.lease checkout_and_verify(conn) end end # Check-in a database connection back into the pool, indicating that you # no longer need this connection. # # +conn+: an AbstractAdapter object, which was obtained by earlier by # calling +checkout+ on this pool. def checkin(conn) synchronize do owner = conn.owner conn._run_checkin_callbacks do conn.expire end release conn, owner @available.add conn end end # Remove a connection from the connection pool. The connection will # remain open and active but will no longer be managed by this pool. def remove(conn) synchronize do @connections.delete conn @available.delete conn release conn, conn.owner @available.add checkout_new_connection if @available.any_waiting? end end # Recover lost connections for the pool. A lost connection can occur if # a programmer forgets to checkin a connection at the end of a thread # or a thread dies unexpectedly. def reap stale_connections = synchronize do @connections.select do |conn| conn.in_use? && !conn.owner.alive? end end stale_connections.each do |conn| synchronize do if conn.active? conn.reset! checkin conn else remove conn end end end end private # Acquire a connection by one of 1) immediately removing one # from the queue of available connections, 2) creating a new # connection if the pool is not at capacity, 3) waiting on the # queue for a connection to become available. # # Raises: # - ConnectionTimeoutError if a connection could not be acquired def acquire_connection if conn = @available.poll conn elsif @connections.size < @size checkout_new_connection else reap @available.poll(@checkout_timeout) end end def release(conn, owner) thread_id = owner.object_id if @reserved_connections[thread_id] == conn @reserved_connections.delete thread_id end end def new_connection Base.send(spec.adapter_method, spec.config) end def current_connection_id #:nodoc: Base.connection_id ||= Thread.current.object_id end def checkout_new_connection raise ConnectionNotEstablished unless @automatic_reconnect c = new_connection c.pool = self @connections << c c end def checkout_and_verify(c) c._run_checkout_callbacks do c.verify! end c rescue remove c c.disconnect! raise end end # ConnectionHandler is a collection of ConnectionPool objects. It is used # for keeping separate connection pools for Active Record models that connect # to different databases. # # For example, suppose that you have 5 models, with the following hierarchy: # # class Author < ActiveRecord::Base # end # # class BankAccount < ActiveRecord::Base # end # # class Book < ActiveRecord::Base # establish_connection "library_db" # end # # class ScaryBook < Book # end # # class GoodBook < Book # end # # And a database.yml that looked like this: # # development: # database: my_application # host: localhost # # library_db: # database: library # host: some.library.org # # Your primary database in the development environment is "my_application" # but the Book model connects to a separate database called "library_db" # (this can even be a database on a different machine). # # Book, ScaryBook and GoodBook will all use the same connection pool to # "library_db" while Author, BankAccount, and any other models you create # will use the default connection pool to "my_application". # # The various connection pools are managed by a single instance of # ConnectionHandler accessible via ActiveRecord::Base.connection_handler. # All Active Record models use this handler to determine the connection pool that they # should use. class ConnectionHandler def initialize # These caches are keyed by klass.name, NOT klass. Keying them by klass # alone would lead to memory leaks in development mode as all previous # instances of the class would stay in memory. @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k| h[k] = ThreadSafe::Cache.new(:initial_capacity => 2) end @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k| h[k] = ThreadSafe::Cache.new end end def connection_pool_list owner_to_pool.values.compact end def connection_pools ActiveSupport::Deprecation.warn(<<-MSG.squish) In the next release, this will return the same as `#connection_pool_list`. (An array of pools, rather than a hash mapping specs to pools.) MSG Hash[connection_pool_list.map { |pool| [pool.spec, pool] }] end def establish_connection(owner, spec) @class_to_pool.clear raise RuntimeError, "Anonymous class is not allowed." unless owner.name owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec) end # Returns true if there are any active connections among the connection # pools that the ConnectionHandler is managing. def active_connections? connection_pool_list.any?(&:active_connection?) end # Returns any connections in use by the current thread back to the pool, # and also returns connections to the pool cached by threads that are no # longer alive. def clear_active_connections! connection_pool_list.each(&:release_connection) end # Clears the cache which maps classes. def clear_reloadable_connections! connection_pool_list.each(&:clear_reloadable_connections!) end def clear_all_connections! connection_pool_list.each(&:disconnect!) end # Locate the connection of the nearest super class. This can be an # active or defined connection: if it is the latter, it will be # opened and set as the active connection for the class it was defined # for (not necessarily the current class). def retrieve_connection(klass) #:nodoc: pool = retrieve_connection_pool(klass) raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool conn = pool.connection raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn conn end # Returns true if a connection that's accessible to this class has # already been opened. def connected?(klass) conn = retrieve_connection_pool(klass) conn && conn.connected? end # Remove the connection for this class. This will close the active # connection and the defined connection (if they exist). The result # can be used as an argument for establish_connection, for easily # re-establishing the connection. def remove_connection(owner) if pool = owner_to_pool.delete(owner.name) @class_to_pool.clear pool.automatic_reconnect = false pool.disconnect! pool.spec.config end end # Retrieving the connection pool happens a lot so we cache it in @class_to_pool. # This makes retrieving the connection pool O(1) once the process is warm. # When a connection is established or removed, we invalidate the cache. # # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil. # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that # #fetch is significantly slower than #[]. So in the nil case, no caching will # take place, but that's ok since the nil case is not the common one that we wish # to optimise for. def retrieve_connection_pool(klass) class_to_pool[klass.name] ||= begin until pool = pool_for(klass) klass = klass.superclass break unless klass <= Base end class_to_pool[klass.name] = pool end end private def owner_to_pool @owner_to_pool[Process.pid] end def class_to_pool @class_to_pool[Process.pid] end def pool_for(owner) owner_to_pool.fetch(owner.name) { if ancestor_pool = pool_from_any_process_for(owner) # A connection was established in an ancestor process that must have # subsequently forked. We can't reuse the connection, but we can copy # the specification and establish a new connection with it. establish_connection owner, ancestor_pool.spec else owner_to_pool[owner.name] = nil end } end def pool_from_any_process_for(owner) owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] } owner_to_pool && owner_to_pool[owner.name] end end class ConnectionManagement def initialize(app) @app = app end def call(env) testing = env['rack.test'] response = @app.call(env) response[2] = ::Rack::BodyProxy.new(response[2]) do ActiveRecord::Base.clear_active_connections! unless testing end response rescue Exception ActiveRecord::Base.clear_active_connections! unless testing raise end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb000066400000000000000000000031071266740050600321740ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseLimits # Returns the maximum length of a table alias. def table_alias_length 255 end # Returns the maximum length of a column name. def column_name_length 64 end # Returns the maximum length of a table name. def table_name_length 64 end # Returns the maximum allowed length for an index name. This # limit is enforced by rails and Is less than or equal to # index_name_length. The gap between # index_name_length is to allow internal rails # operations to use prefixes in temporary operations. def allowed_index_name_length index_name_length end # Returns the maximum length of an index name. def index_name_length 64 end # Returns the maximum number of columns per table. def columns_per_table 1024 end # Returns the maximum number of indexes per table. def indexes_per_table 16 end # Returns the maximum number of columns in a multicolumn index. def columns_per_multicolumn_index 16 end # Returns the maximum number of elements in an IN (x,y,z) clause. # nil means no limit. def in_clause_length nil end # Returns the maximum length of an SQL query. def sql_query_length 1048575 end # Returns maximum number of joins in a single query. def joins_per_query 256 end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb000066400000000000000000000343551266740050600330730ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseStatements def initialize super reset_transaction end # Converts an arel AST to SQL def to_sql(arel, binds = []) if arel.respond_to?(:ast) collected = visitor.accept(arel.ast, collector) collected.compile(binds.dup, self) else arel end end # This is used in the StatementCache object. It returns an object that # can be used to query the database repeatedly. def cacheable_query(arel) # :nodoc: if prepared_statements ActiveRecord::StatementCache.query visitor, arel.ast else ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector end end # Returns an ActiveRecord::Result instance. def select_all(arel, name = nil, binds = []) arel, binds = binds_from_relation arel, binds select(to_sql(arel, binds), name, binds) end # Returns a record hash with the column names as keys and column values # as values. def select_one(arel, name = nil, binds = []) select_all(arel, name, binds).first end # Returns a single value from a record def select_value(arel, name = nil, binds = []) if result = select_one(arel, name, binds) result.values.first end end # Returns an array of the values of the first column in a select: # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] def select_values(arel, name = nil) arel, binds = binds_from_relation arel, [] select_rows(to_sql(arel, binds), name, binds).map(&:first) end # Returns an array of arrays containing the field values. # Order is the same as that returned by +columns+. def select_rows(sql, name = nil, binds = []) end undef_method :select_rows # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) end undef_method :execute # Executes +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. def exec_query(sql, name = 'SQL', binds = []) end # Executes insert +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) exec_query(sql, name, binds) end # Executes delete +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. def exec_delete(sql, name, binds) exec_query(sql, name, binds) end # Executes the truncate statement. def truncate(table_name, name = nil) raise NotImplementedError end # Executes update +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. def exec_update(sql, name, binds) exec_query(sql, name, binds) end # Returns the last auto-generated ID from the affected table. # # +id_value+ will be returned unless the value is nil, in # which case the database will attempt to calculate the last inserted # id and return that value. # # If the next id was calculated in advance (as in Oracle), it should be # passed in as +id_value+. def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds) value = exec_insert(sql, name, binds, pk, sequence_name) id_value || last_inserted_id(value) end # Executes the update statement and returns the number of rows affected. def update(arel, name = nil, binds = []) exec_update(to_sql(arel, binds), name, binds) end # Executes the delete statement and returns the number of rows affected. def delete(arel, name = nil, binds = []) exec_delete(to_sql(arel, binds), name, binds) end # Returns +true+ when the connection adapter supports prepared statement # caching, otherwise returns +false+ def supports_statement_cache? false end # Runs the given block in a database transaction, and returns the result # of the block. # # == Nested transactions support # # Most databases don't support true nested transactions. At the time of # writing, the only database that supports true nested transactions that # we're aware of, is MS-SQL. # # In order to get around this problem, #transaction will emulate the effect # of nested transactions, by using savepoints: # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8' # supports savepoints. # # It is safe to call this method if a database transaction is already open, # i.e. if #transaction is called within another #transaction block. In case # of a nested call, #transaction will behave as follows: # # - The block will be run without doing anything. All database statements # that happen within the block are effectively appended to the already # open database transaction. # - However, if +:requires_new+ is set, the block will be wrapped in a # database savepoint acting as a sub-transaction. # # === Caveats # # MySQL doesn't support DDL transactions. If you perform a DDL operation, # then any created savepoints will be automatically released. For example, # if you've created a savepoint, then you execute a CREATE TABLE statement, # then the savepoint that was created will be automatically released. # # This means that, on MySQL, you shouldn't execute DDL operations inside # a #transaction call that you know might create a savepoint. Otherwise, # #transaction will raise exceptions when it tries to release the # already-automatically-released savepoints: # # Model.connection.transaction do # BEGIN # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 # Model.connection.create_table(...) # # active_record_1 now automatically released # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error! # end # # == Transaction isolation # # If your database supports setting the isolation level for a transaction, you can set # it like so: # # Post.transaction(isolation: :serializable) do # # ... # end # # Valid isolation levels are: # # * :read_uncommitted # * :read_committed # * :repeatable_read # * :serializable # # You should consult the documentation for your database to understand the # semantics of these different levels: # # * http://www.postgresql.org/docs/9.1/static/transaction-iso.html # * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html # # An ActiveRecord::TransactionIsolationError will be raised if: # # * The adapter does not support setting the isolation level # * You are joining an existing open transaction # * You are creating a nested (savepoint) transaction # # The mysql, mysql2 and postgresql adapters support setting the transaction # isolation level. However, support is disabled for MySQL versions below 5, # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170] # which means the isolation level gets persisted outside the transaction. def transaction(options = {}) options.assert_valid_keys :requires_new, :joinable, :isolation if !options[:requires_new] && current_transaction.joinable? if options[:isolation] raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction" end yield else transaction_manager.within_new_transaction(options) { yield } end rescue ActiveRecord::Rollback # rollbacks are silently swallowed end attr_reader :transaction_manager #:nodoc: delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager def transaction_open? current_transaction.open? end def reset_transaction #:nodoc: @transaction_manager = TransactionManager.new(self) end # Register a record with the current transaction so that its after_commit and after_rollback callbacks # can be called. def add_transaction_record(record) current_transaction.add_record(record) end def transaction_state current_transaction.state end # Begins the transaction (and turns off auto-committing). def begin_db_transaction() end def transaction_isolation_levels { read_uncommitted: "READ UNCOMMITTED", read_committed: "READ COMMITTED", repeatable_read: "REPEATABLE READ", serializable: "SERIALIZABLE" } end # Begins the transaction with the isolation level set. Raises an error by # default; adapters that support setting the isolation level should implement # this method. def begin_isolated_db_transaction(isolation) raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation" end # Commits the transaction (and turns on auto-committing). def commit_db_transaction() end # Rolls back the transaction (and turns on auto-committing). Must be # done if the transaction block raises an exception or returns false. def rollback_db_transaction exec_rollback_db_transaction end def exec_rollback_db_transaction() end #:nodoc: def rollback_to_savepoint(name = nil) exec_rollback_to_savepoint(name) end def exec_rollback_to_savepoint(name = nil) #:nodoc: end def default_sequence_name(table, column) nil end # Set the sequence to the max value of the table's column. def reset_sequence!(table, column, sequence = nil) # Do nothing by default. Implement for PostgreSQL, Oracle, ... end # Inserts the given fixture into the table. Overridden in adapters that require # something beyond a simple insert (eg. Oracle). def insert_fixture(fixture, table_name) fixture = fixture.stringify_keys columns = schema_cache.columns_hash(table_name) key_list = [] value_list = fixture.map do |name, value| if column = columns[name] key_list << quote_column_name(name) quote(value, column) else raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.) end end execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' end def empty_insert_statement_value "DEFAULT VALUES" end # Sanitizes the given LIMIT parameter in order to prevent SQL injection. # # The +limit+ may be anything that can evaluate to a string via #to_s. It # should look like an integer, or a comma-delimited list of integers, or # an Arel SQL literal. # # Returns Integer and Arel::Nodes::SqlLiteral limits as is. # Returns the sanitized limit parameter, either as an integer, or as a # string which contains a comma-delimited list of integers. def sanitize_limit(limit) if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral) limit elsif limit.to_s.include?(',') Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',') else Integer(limit) end end # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in # an UPDATE statement, so in the MySQL adapters we redefine this to do that. def join_to_update(update, select) #:nodoc: key = update.key subselect = subquery_for(key, select) update.where key.in(subselect) end def join_to_delete(delete, select, key) #:nodoc: subselect = subquery_for(key, select) delete.where key.in(subselect) end protected # Returns a subquery for the given key using the join information. def subquery_for(key, select) subselect = select.clone subselect.projections = [key] subselect end # Returns an ActiveRecord::Result instance. def select(sql, name = nil, binds = []) exec_query(sql, name, binds) end # Returns the last auto-generated ID from the affected table. def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) execute(sql, name) id_value end # Executes the update statement and returns the number of rows affected. def update_sql(sql, name = nil) execute(sql, name) end # Executes the delete statement and returns the number of rows affected. def delete_sql(sql, name = nil) update_sql(sql, name) end def sql_for_insert(sql, pk, id_value, sequence_name, binds) [sql, binds] end def last_inserted_id(result) row = result.rows.first row && row.first end def binds_from_relation(relation, binds) if relation.is_a?(Relation) && binds.empty? relation, binds = relation.arel, relation.bind_values end [relation, binds] end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb000066400000000000000000000053321266740050600313410ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters # :nodoc: module QueryCache class << self def included(base) #:nodoc: dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction end def dirties_query_cache(base, *method_names) method_names.each do |method_name| base.class_eval <<-end_code, __FILE__, __LINE__ + 1 def #{method_name}(*) clear_query_cache if @query_cache_enabled super end end_code end end end attr_reader :query_cache, :query_cache_enabled def initialize(*) super @query_cache = Hash.new { |h,sql| h[sql] = {} } @query_cache_enabled = false end # Enable the query cache within the block. def cache old, @query_cache_enabled = @query_cache_enabled, true yield ensure @query_cache_enabled = old clear_query_cache unless @query_cache_enabled end def enable_query_cache! @query_cache_enabled = true end def disable_query_cache! @query_cache_enabled = false end # Disable the query cache within the block. def uncached old, @query_cache_enabled = @query_cache_enabled, false yield ensure @query_cache_enabled = old end # Clears the query cache. # # One reason you may wish to call this method explicitly is between queries # that ask the database to randomize results. Otherwise the cache would see # the same SQL query and repeatedly return the same result each time, silently # undermining the randomness you were expecting. def clear_query_cache @query_cache.clear end def select_all(arel, name = nil, binds = []) if @query_cache_enabled && !locked?(arel) arel, binds = binds_from_relation arel, binds sql = to_sql(arel, binds) cache_sql(sql, binds) { super(sql, name, binds) } else super end end private def cache_sql(sql, binds) result = if @query_cache[sql].key?(binds) ActiveSupport::Notifications.instrument("sql.active_record", :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id) @query_cache[sql][binds] else @query_cache[sql][binds] = yield end result.dup end # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such # queries should not be cached. def locked?(arel) arel.respond_to?(:locked) && arel.locked end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb000066400000000000000000000075141266740050600305430ustar00rootroot00000000000000require 'active_support/core_ext/big_decimal/conversions' module ActiveRecord module ConnectionAdapters # :nodoc: module Quoting # Quotes the column value to help prevent # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. def quote(value, column = nil) # records are quoted as their primary key return value.quoted_id if value.respond_to?(:quoted_id) if column value = column.cast_type.type_cast_for_database(value) end _quote(value) end # Cast a +value+ to a type that the database understands. For example, # SQLite does not understand dates, so this method will convert a Date # to a String. def type_cast(value, column) if value.respond_to?(:quoted_id) && value.respond_to?(:id) return value.id end if column value = column.cast_type.type_cast_for_database(value) end _type_cast(value) rescue TypeError to_type = column ? " to #{column.type}" : "" raise TypeError, "can't cast #{value.class}#{to_type}" end # Quotes a string, escaping any ' (single quote) and \ (backslash) # characters. def quote_string(s) s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode) end # Quotes the column name. Defaults to no quoting. def quote_column_name(column_name) column_name end # Quotes the table name. Defaults to column name quoting. def quote_table_name(table_name) quote_column_name(table_name) end # Override to return the quoted table name for assignment. Defaults to # table quoting. # # This works for mysql and mysql2 where table.column can be used to # resolve ambiguity. # # We override this in the sqlite3 and postgresql adapters to use only # the column name (as per syntax requirements). def quote_table_name_for_assignment(table, attr) quote_table_name("#{table}.#{attr}") end def quoted_true "'t'" end def unquoted_true 't' end def quoted_false "'f'" end def unquoted_false 'f' end def quoted_date(value) if value.acts_like?(:time) zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal if value.respond_to?(zone_conversion_method) value = value.send(zone_conversion_method) end end value.to_s(:db) end private def types_which_need_no_typecasting [nil, Numeric, String] end def _quote(value) case value when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data "'#{quote_string(value.to_s)}'" when true then quoted_true when false then quoted_false when nil then "NULL" # BigDecimals need to be put in a non-normalized form and quoted. when BigDecimal then value.to_s('F') when Numeric, ActiveSupport::Duration then value.to_s when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value}'" else "'#{quote_string(YAML.dump(value))}'" end end def _type_cast(value) case value when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data value.to_s when true then unquoted_true when false then unquoted_false # BigDecimals need to be put in a non-normalized form and quoted. when BigDecimal then value.to_s('F') when Date, Time then quoted_date(value) when *types_which_need_no_typecasting value else raise TypeError end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb000066400000000000000000000007641266740050600312500ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module Savepoints #:nodoc: def supports_savepoints? true end def create_savepoint(name = current_savepoint_name) execute("SAVEPOINT #{name}") end def exec_rollback_to_savepoint(name = current_savepoint_name) execute("ROLLBACK TO SAVEPOINT #{name}") end def release_savepoint(name = current_savepoint_name) execute("RELEASE SAVEPOINT #{name}") end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb000066400000000000000000000103671266740050600322010ustar00rootroot00000000000000require 'active_support/core_ext/string/strip' module ActiveRecord module ConnectionAdapters class AbstractAdapter class SchemaCreation # :nodoc: def initialize(conn) @conn = conn @cache = {} end def accept(o) m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}" send m, o end def visit_AddColumn(o) "ADD #{accept(o)}" end private def visit_AlterTable(o) sql = "ALTER TABLE #{quote_table_name(o.name)} " sql << o.adds.map { |col| visit_AddColumn col }.join(' ') sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ') sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ') end def visit_ColumnDefinition(o) sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale) column_sql = "#{quote_column_name(o.name)} #{sql_type}" add_column_options!(column_sql, column_options(o)) unless o.primary_key? column_sql end def visit_TableDefinition(o) create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE " create_sql << "#{quote_table_name(o.name)} " create_sql << "(#{o.columns.map { |c| accept c }.join(', ')}) " unless o.as create_sql << "#{o.options}" create_sql << " AS #{@conn.to_sql(o.as)}" if o.as create_sql end def visit_AddForeignKey(o) sql = <<-SQL.strip_heredoc ADD CONSTRAINT #{quote_column_name(o.name)} FOREIGN KEY (#{quote_column_name(o.column)}) REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) SQL sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update sql end def visit_DropForeignKey(name) "DROP CONSTRAINT #{quote_column_name(name)}" end def column_options(o) column_options = {} column_options[:null] = o.null unless o.null.nil? column_options[:default] = o.default unless o.default.nil? column_options[:column] = o column_options[:first] = o.first column_options[:after] = o.after column_options end def quote_column_name(name) @conn.quote_column_name name end def quote_table_name(name) @conn.quote_table_name name end def type_to_sql(type, limit, precision, scale) @conn.type_to_sql type.to_sym, limit, precision, scale end def add_column_options!(sql, options) sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(options) # must explicitly check for :null to allow change_column to work on migrations if options[:null] == false sql << " NOT NULL" end if options[:auto_increment] == true sql << " AUTO_INCREMENT" end sql end def quote_value(value, column) column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale) column.cast_type ||= type_for_column(column) @conn.quote(value, column) end def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end def action_sql(action, dependency) case dependency when :nullify then "ON #{action} SET NULL" when :cascade then "ON #{action} CASCADE" when :restrict then "ON #{action} RESTRICT" else raise ArgumentError, <<-MSG.strip_heredoc '#{dependency}' is not supported for :on_update or :on_delete. Supported values are: :nullify, :cascade, :restrict MSG end end def type_for_column(column) @conn.lookup_cast_type(column.sql_type) end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb000066400000000000000000000514361266740050600327120ustar00rootroot00000000000000require 'date' require 'set' require 'bigdecimal' require 'bigdecimal/util' module ActiveRecord module ConnectionAdapters #:nodoc: # Abstract representation of an index definition on a table. Instances of # this type are typically created and returned by methods in database # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc: end # Abstract representation of a column definition. Instances of this type # are typically created by methods in TableDefinition, and added to the # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type, :cast_type) #:nodoc: def primary_key? primary_key || type.to_sym == :primary_key end end class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc: end class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc: def name options[:name] end def column options[:column] end def primary_key options[:primary_key] || default_primary_key end def on_delete options[:on_delete] end def on_update options[:on_update] end def custom_primary_key? options[:primary_key] != default_primary_key end private def default_primary_key "id" end end module TimestampDefaultDeprecation # :nodoc: def emit_warning_if_null_unspecified(sym, options) return if options.key?(:null) ActiveSupport::Deprecation.warn(<<-MSG.squish) `##{sym}` was called without specifying an option for `null`. In Rails 5, this behavior will change to `null: false`. You should manually specify `null: true` to prevent the behavior of your existing migrations from changing. MSG end end # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # # Inside migration files, the +t+ object in +create_table+ # is actually of this type: # # class SomeMigration < ActiveRecord::Migration # def up # create_table :foo do |t| # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition" # end # end # # def down # ... # end # end # # The table definitions # The Columns are stored as a ColumnDefinition in the +columns+ attribute. class TableDefinition include TimestampDefaultDeprecation # An array of ColumnDefinition objects, representing the column changes # that have been defined. attr_accessor :indexes attr_reader :name, :temporary, :options, :as, :foreign_keys def initialize(types, name, temporary, options, as = nil) @columns_hash = {} @indexes = {} @foreign_keys = [] @native = types @temporary = temporary @options = options @as = as @name = name end def columns; @columns_hash.values; end # Appends a primary key definition to the table definition. # Can be called multiple times, but this is probably not a good idea. def primary_key(name, type = :primary_key, options = {}) column(name, type, options.merge(:primary_key => true)) end # Returns a ColumnDefinition for the column with name +name+. def [](name) @columns_hash[name.to_s] end # Instantiates a new column for the table. # The +type+ parameter is normally one of the migrations native types, # which is one of the following: # :primary_key, :string, :text, # :integer, :float, :decimal, # :datetime, :time, :date, # :binary, :boolean. # # You may use a type not in this list as long as it is supported by your # database (for example, "polygon" in MySQL), but this will not be database # agnostic and should usually be avoided. # # Available options are (none of these exists by default): # * :limit - # Requests a maximum column length. This is number of characters for :string and # :text columns and number of bytes for :binary and :integer columns. # * :default - # The column's default value. Use nil for NULL. # * :null - # Allows or disallows +NULL+ values in the column. This option could # have been named :null_allowed. # * :precision - # Specifies the precision for a :decimal column. # * :scale - # Specifies the scale for a :decimal column. # * :index - # Create an index for the column. Can be either true or an options hash. # # Note: The precision is the total number of significant digits # and the scale is the number of digits that can be stored following # the decimal point. For example, the number 123.45 has a precision of 5 # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can # range from -999.99 to 999.99. # # Please be aware of different RDBMS implementations behavior with # :decimal columns: # * The SQL standard says the default scale should be 0, :scale <= # :precision, and makes no comments about the requirements of # :precision. # * MySQL: :precision [1..63], :scale [0..30]. # Default is (10,0). # * PostgreSQL: :precision [1..infinity], # :scale [0..infinity]. No default. # * SQLite2: Any :precision and :scale may be used. # Internal storage as strings. No default. # * SQLite3: No restrictions on :precision and :scale, # but the maximum supported :precision is 16. No default. # * Oracle: :precision [1..38], :scale [-84..127]. # Default is (38,0). # * DB2: :precision [1..63], :scale [0..62]. # Default unknown. # * SqlServer?: :precision [1..38], :scale [0..38]. # Default (38,0). # # This method returns self. # # == Examples # # Assuming +td+ is an instance of TableDefinition # td.column(:granted, :boolean) # # granted BOOLEAN # # td.column(:picture, :binary, limit: 2.megabytes) # # => picture BLOB(2097152) # # td.column(:sales_stage, :string, limit: 20, default: 'new', null: false) # # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL # # td.column(:bill_gates_money, :decimal, precision: 15, scale: 2) # # => bill_gates_money DECIMAL(15,2) # # td.column(:sensor_reading, :decimal, precision: 30, scale: 20) # # => sensor_reading DECIMAL(30,20) # # # While :scale defaults to zero on most databases, it # # probably wouldn't hurt to include it. # td.column(:huge_integer, :decimal, precision: 30) # # => huge_integer DECIMAL(30) # # # Defines a column with a database-specific type. # td.column(:foo, 'polygon') # # => foo polygon # # == Short-hand examples # # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types. # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined # in a single statement. # # What can be written like this with the regular calls to column: # # create_table :products do |t| # t.column :shop_id, :integer # t.column :creator_id, :integer # t.column :item_number, :string # t.column :name, :string, default: "Untitled" # t.column :value, :string, default: "Untitled" # t.column :created_at, :datetime # t.column :updated_at, :datetime # end # add_index :products, :item_number # # can also be written as follows using the short-hand: # # create_table :products do |t| # t.integer :shop_id, :creator_id # t.string :item_number, index: true # t.string :name, :value, default: "Untitled" # t.timestamps null: false # end # # There's a short-hand method for each of the type values declared at the top. And then there's # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes. # # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type # column if the :polymorphic option is supplied. If :polymorphic is a hash of # options, these will be used when creating the _type column. The :index option # will also create an index, similar to calling add_index. So what can be written like this: # # create_table :taggings do |t| # t.integer :tag_id, :tagger_id, :taggable_id # t.string :tagger_type # t.string :taggable_type, default: 'Photo' # end # add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id' # add_index :taggings, [:tagger_id, :tagger_type] # # Can also be written as follows using references: # # create_table :taggings do |t| # t.references :tag, index: { name: 'index_taggings_on_tag_id' } # t.references :tagger, polymorphic: true, index: true # t.references :taggable, polymorphic: { default: 'Photo' } # end def column(name, type, options = {}) name = name.to_s type = type.to_sym options = options.dup if @columns_hash[name] && @columns_hash[name].primary_key? raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table." end index_options = options.delete(:index) index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options @columns_hash[name] = new_column_definition(name, type, options) self end def remove_column(name) @columns_hash.delete name.to_s end [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| define_method column_type do |*args| options = args.extract_options! column_names = args column_names.each { |name| column(name, column_type, options) } end end # Adds index options to the indexes hash, keyed by column name # This is primarily used to track indexes that need to be created after the table # # index(:account_id, name: 'index_projects_on_account_id') def index(column_name, options = {}) indexes[column_name] = options end def foreign_key(table_name, options = {}) # :nodoc: foreign_keys.push([table_name, options]) end # Appends :datetime columns :created_at and # :updated_at to the table. See SchemaStatements#add_timestamps # # t.timestamps null: false def timestamps(*args) options = args.extract_options! emit_warning_if_null_unspecified(:timestamps, options) column(:created_at, :datetime, options) column(:updated_at, :datetime, options) end # Adds a reference. # # t.references(:user) # t.belongs_to(:supplier, foreign_key: true) # # See SchemaStatements#add_reference for details of the options you can use. def references(*args) options = args.extract_options! polymorphic = options.delete(:polymorphic) index_options = options.delete(:index) foreign_key_options = options.delete(:foreign_key) type = options.delete(:type) || :integer if polymorphic && foreign_key_options raise ArgumentError, "Cannot add a foreign key on a polymorphic relation" end args.each do |col| column("#{col}_id", type, options) column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options if foreign_key_options to_table = Base.pluralize_table_names ? col.to_s.pluralize : col.to_s foreign_key(to_table, foreign_key_options.is_a?(Hash) ? foreign_key_options : {}) end end end alias :belongs_to :references def new_column_definition(name, type, options) # :nodoc: type = aliased_types(type.to_s, type) column = create_column_definition name, type limit = options.fetch(:limit) do native[type][:limit] if native[type].is_a?(Hash) end column.limit = limit column.precision = options[:precision] column.scale = options[:scale] column.default = options[:default] column.null = options[:null] column.first = options[:first] column.after = options[:after] column.primary_key = type == :primary_key || options[:primary_key] column end private def create_column_definition(name, type) ColumnDefinition.new name, type end def native @native end def aliased_types(name, fallback) 'timestamp' == name ? :datetime : fallback end end class AlterTable # :nodoc: attr_reader :adds attr_reader :foreign_key_adds attr_reader :foreign_key_drops def initialize(td) @td = td @adds = [] @foreign_key_adds = [] @foreign_key_drops = [] end def name; @td.name; end def add_foreign_key(to_table, options) @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options) end def drop_foreign_key(name) @foreign_key_drops << name end def add_column(name, type, options) name = name.to_s type = type.to_sym @adds << @td.new_column_definition(name, type, options) end end # Represents an SQL table in an abstract way for updating a table. # Also see TableDefinition and SchemaStatements#create_table # # Available transformations are: # # change_table :table do |t| # t.column # t.index # t.rename_index # t.timestamps # t.change # t.change_default # t.rename # t.references # t.belongs_to # t.string # t.text # t.integer # t.float # t.decimal # t.datetime # t.timestamp # t.time # t.date # t.binary # t.boolean # t.remove # t.remove_references # t.remove_belongs_to # t.remove_index # t.remove_timestamps # end # class Table attr_reader :name def initialize(table_name, base) @name = table_name @base = base end # Adds a new column to the named table. # See TableDefinition#column for details of the options you can use. # # ====== Creating a simple column # t.column(:name, :string) def column(column_name, type, options = {}) @base.add_column(name, column_name, type, options) end # Checks to see if a column exists. See SchemaStatements#column_exists? def column_exists?(column_name, type = nil, options = {}) @base.column_exists?(name, column_name, type, options) end # Adds a new index to the table. +column_name+ can be a single Symbol, or # an Array of Symbols. See SchemaStatements#add_index # # ====== Creating a simple index # t.index(:name) # ====== Creating a unique index # t.index([:branch_id, :party_id], unique: true) # ====== Creating a named index # t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party') def index(column_name, options = {}) @base.add_index(name, column_name, options) end # Checks to see if an index exists. See SchemaStatements#index_exists? def index_exists?(column_name, options = {}) @base.index_exists?(name, column_name, options) end # Renames the given index on the table. # # t.rename_index(:user_id, :account_id) def rename_index(index_name, new_index_name) @base.rename_index(name, index_name, new_index_name) end # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps # # t.timestamps null: false def timestamps(options = {}) @base.add_timestamps(name, options) end # Changes the column's definition according to the new options. # See TableDefinition#column for details of the options you can use. # # t.change(:name, :string, limit: 80) # t.change(:description, :text) def change(column_name, type, options = {}) @base.change_column(name, column_name, type, options) end # Sets a new default value for a column. See SchemaStatements#change_column_default # # t.change_default(:qualification, 'new') # t.change_default(:authorized, 1) def change_default(column_name, default) @base.change_column_default(name, column_name, default) end # Removes the column(s) from the table definition. # # t.remove(:qualification) # t.remove(:qualification, :experience) def remove(*column_names) @base.remove_columns(name, *column_names) end # Removes the given index from the table. # # ====== Remove the index_table_name_on_column in the table_name table # t.remove_index :column # ====== Remove the index named index_table_name_on_branch_id in the table_name table # t.remove_index column: :branch_id # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table # t.remove_index column: [:branch_id, :party_id] # ====== Remove the index named by_branch_party in the table_name table # t.remove_index name: :by_branch_party def remove_index(options = {}) @base.remove_index(name, options) end # Removes the timestamp columns (+created_at+ and +updated_at+) from the table. # # t.remove_timestamps def remove_timestamps(options = {}) @base.remove_timestamps(name, options) end # Renames a column. # # t.rename(:description, :name) def rename(column_name, new_column_name) @base.rename_column(name, column_name, new_column_name) end # Adds a reference. # # t.references(:user) # t.belongs_to(:supplier, foreign_key: true) # # See SchemaStatements#add_reference for details of the options you can use. def references(*args) options = args.extract_options! args.each do |ref_name| @base.add_reference(name, ref_name, options) end end alias :belongs_to :references # Removes a reference. Optionally removes a +type+ column. # remove_references and remove_belongs_to are acceptable. # # t.remove_references(:user) # t.remove_belongs_to(:supplier, polymorphic: true) # # See SchemaStatements#remove_reference def remove_references(*args) options = args.extract_options! args.each do |ref_name| @base.remove_reference(name, ref_name, options) end end alias :remove_belongs_to :remove_references # Adds a column or columns of a specified type # # t.string(:goat) # t.string(:goat, :sheep) [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| define_method column_type do |*args| options = args.extract_options! args.each do |column_name| @base.add_column(name, column_name, column_type, options) end end end private def native @base.native_database_types end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb000066400000000000000000000033611266740050600316650ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters # :nodoc: # The goal of this module is to move Adapter specific column # definitions to the Adapter instead of having it in the schema # dumper itself. This code represents the normal case. # We can then redefine how certain data types may be handled in the schema dumper on the # Adapter level by over-writing this code inside the database specific adapters module ColumnDumper def column_spec(column, types) spec = prepare_column_options(column, types) (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")} spec end # This can be overridden on a Adapter level basis to support other # extended datatypes (Example: Adding an array option in the # PostgreSQLAdapter) def prepare_column_options(column, types) spec = {} spec[:name] = column.name.inspect spec[:type] = column.type.to_s spec[:null] = 'false' unless column.null limit = column.limit || types[column.type][:limit] spec[:limit] = limit.inspect if limit spec[:precision] = column.precision.inspect if column.precision spec[:scale] = column.scale.inspect if column.scale default = schema_default(column) if column.has_default? spec[:default] = default unless default.nil? spec end # Lists the valid migration options def migration_keys [:name, :limit, :precision, :scale, :default, :null] end private def schema_default(column) default = column.type_cast_from_database(column.default) unless default.nil? column.type_cast_for_schema(default) end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb000066400000000000000000001176761266740050600325770ustar00rootroot00000000000000require 'active_record/migration/join_table' require 'active_support/core_ext/string/access' require 'digest' module ActiveRecord module ConnectionAdapters # :nodoc: module SchemaStatements include ActiveRecord::Migration::JoinTable # Returns a hash of mappings from the abstract data types to the native # database types. See TableDefinition#column for details on the recognized # abstract data types. def native_database_types {} end # Truncates a table alias according to the limits of the current adapter. def table_alias_for(table_name) table_name[0...table_alias_length].tr('.', '_') end # Returns the relation names useable to back Active Record models. # For most adapters this means all tables and views. def data_sources tables end # Checks to see if the data source +name+ exists on the database. # # data_source_exists?(:ebooks) # def data_source_exists?(name) data_sources.include?(name.to_s) end # Checks to see if the table +table_name+ exists on the database. # # table_exists?(:developers) # def table_exists?(table_name) tables.include?(table_name.to_s) end # Returns an array of indexes for the given table. # def indexes(table_name, name = nil) end # Checks to see if an index exists on a table for a given index definition. # # # Check an index exists # index_exists?(:suppliers, :company_id) # # # Check an index on multiple columns exists # index_exists?(:suppliers, [:company_id, :company_type]) # # # Check a unique index exists # index_exists?(:suppliers, :company_id, unique: true) # # # Check an index with a custom name exists # index_exists?(:suppliers, :company_id, name: "idx_company_id") # def index_exists?(table_name, column_name, options = {}) column_names = Array(column_name).map(&:to_s) index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names) checks = [] checks << lambda { |i| i.name == index_name } checks << lambda { |i| i.columns == column_names } checks << lambda { |i| i.unique } if options[:unique] indexes(table_name).any? { |i| checks.all? { |check| check[i] } } end # Returns an array of Column objects for the table specified by +table_name+. # See the concrete implementation for details on the expected parameter values. def columns(table_name) end # Checks to see if a column exists in a given table. # # # Check a column exists # column_exists?(:suppliers, :name) # # # Check a column exists of a particular type # column_exists?(:suppliers, :name, :string) # # # Check a column exists with a specific definition # column_exists?(:suppliers, :name, :string, limit: 100) # column_exists?(:suppliers, :name, :string, default: 'default') # column_exists?(:suppliers, :name, :string, null: false) # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2) # def column_exists?(table_name, column_name, type = nil, options = {}) column_name = column_name.to_s columns(table_name).any?{ |c| c.name == column_name && (!type || c.type == type) && (!options.key?(:limit) || c.limit == options[:limit]) && (!options.key?(:precision) || c.precision == options[:precision]) && (!options.key?(:scale) || c.scale == options[:scale]) && (!options.key?(:default) || c.default == options[:default]) && (!options.key?(:null) || c.null == options[:null]) } end # Creates a new table with the name +table_name+. +table_name+ may either # be a String or a Symbol. # # There are two ways to work with +create_table+. You can use the block # form or the regular form, like this: # # === Block form # # # create_table() passes a TableDefinition object to the block. # # This form will not only create the table, but also columns for the # # table. # # create_table(:suppliers) do |t| # t.column :name, :string, limit: 60 # # Other fields here # end # # === Block form, with shorthand # # # You can also use the column types as method calls, rather than calling the column method. # create_table(:suppliers) do |t| # t.string :name, limit: 60 # # Other fields here # end # # === Regular form # # # Creates a table called 'suppliers' with no columns. # create_table(:suppliers) # # Add a column to 'suppliers'. # add_column(:suppliers, :name, :string, {limit: 60}) # # The +options+ hash can include the following keys: # [:id] # Whether to automatically add a primary key column. Defaults to true. # Join tables for +has_and_belongs_to_many+ should set it to false. # [:primary_key] # The name of the primary key, if one is to be added automatically. # Defaults to +id+. If :id is false this option is ignored. # # Note that Active Record models will automatically detect their # primary key. This can be avoided by using +self.primary_key=+ on the model # to define the key explicitly. # # [:options] # Any extra options you want appended to the table definition. # [:temporary] # Make a temporary table. # [:force] # Set to true to drop the table before creating it. # Set to +:cascade+ to drop dependent objects as well. # Defaults to false. # [:as] # SQL to use to generate the table. When this option is used, the block is # ignored, as are the :id and :primary_key options. # # ====== Add a backend specific option to the generated SQL (MySQL) # # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') # # generates: # # CREATE TABLE suppliers ( # id int(11) DEFAULT NULL auto_increment PRIMARY KEY # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 # # ====== Rename the primary key column # # create_table(:objects, primary_key: 'guid') do |t| # t.column :name, :string, limit: 80 # end # # generates: # # CREATE TABLE objects ( # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY, # name varchar(80) # ) # # ====== Do not add a primary key column # # create_table(:categories_suppliers, id: false) do |t| # t.column :category_id, :integer # t.column :supplier_id, :integer # end # # generates: # # CREATE TABLE categories_suppliers ( # category_id int, # supplier_id int # ) # # ====== Create a temporary table based on a query # # create_table(:long_query, temporary: true, # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id") # # generates: # # CREATE TEMPORARY TABLE long_query AS # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id # # See also TableDefinition#column for details on how to create columns. def create_table(table_name, options = {}) td = create_table_definition table_name, options[:temporary], options[:options], options[:as] if options[:id] != false && !options[:as] pk = options.fetch(:primary_key) do Base.get_primary_key table_name.to_s.singularize end td.primary_key pk, options.fetch(:id, :primary_key), options end yield td if block_given? if options[:force] && table_exists?(table_name) drop_table(table_name, options) end result = execute schema_creation.accept td unless supports_indexes_in_create? td.indexes.each_pair do |column_name, index_options| add_index(table_name, column_name, index_options) end end td.foreign_keys.each do |other_table_name, foreign_key_options| add_foreign_key(table_name, other_table_name, foreign_key_options) end result end # Creates a new join table with the name created using the lexical order of the first two # arguments. These arguments can be a String or a Symbol. # # # Creates a table called 'assemblies_parts' with no id. # create_join_table(:assemblies, :parts) # # You can pass a +options+ hash can include the following keys: # [:table_name] # Sets the table name overriding the default # [:column_options] # Any extra options you want appended to the columns definition. # [:options] # Any extra options you want appended to the table definition. # [:temporary] # Make a temporary table. # [:force] # Set to true to drop the table before creating it. # Defaults to false. # # Note that +create_join_table+ does not create any indices by default; you can use # its block form to do so yourself: # # create_join_table :products, :categories do |t| # t.index :product_id # t.index :category_id # end # # ====== Add a backend specific option to the generated SQL (MySQL) # # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') # # generates: # # CREATE TABLE assemblies_parts ( # assembly_id int NOT NULL, # part_id int NOT NULL, # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 # def create_join_table(table_1, table_2, options = {}) join_table_name = find_join_table_name(table_1, table_2, options) column_options = options.delete(:column_options) || {} column_options.reverse_merge!(null: false) t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key } create_table(join_table_name, options.merge!(id: false)) do |td| td.integer t1_column, column_options td.integer t2_column, column_options yield td if block_given? end end # Drops the join table specified by the given arguments. # See +create_join_table+ for details. # # Although this command ignores the block if one is given, it can be helpful # to provide one in a migration's +change+ method so it can be reverted. # In that case, the block will be used by create_join_table. def drop_join_table(table_1, table_2, options = {}) join_table_name = find_join_table_name(table_1, table_2, options) drop_table(join_table_name) end # A block for changing columns in +table+. # # # change_table() yields a Table instance # change_table(:suppliers) do |t| # t.column :name, :string, limit: 60 # # Other column alterations here # end # # The +options+ hash can include the following keys: # [:bulk] # Set this to true to make this a bulk alter query, such as # # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ... # # Defaults to false. # # ====== Add a column # # change_table(:suppliers) do |t| # t.column :name, :string, limit: 60 # end # # ====== Add 2 integer columns # # change_table(:suppliers) do |t| # t.integer :width, :height, null: false, default: 0 # end # # ====== Add created_at/updated_at columns # # change_table(:suppliers) do |t| # t.timestamps # end # # ====== Add a foreign key column # # change_table(:suppliers) do |t| # t.references :company # end # # Creates a company_id(integer) column. # # ====== Add a polymorphic foreign key column # # change_table(:suppliers) do |t| # t.belongs_to :company, polymorphic: true # end # # Creates company_type(varchar) and company_id(integer) columns. # # ====== Remove a column # # change_table(:suppliers) do |t| # t.remove :company # end # # ====== Remove several columns # # change_table(:suppliers) do |t| # t.remove :company_id # t.remove :width, :height # end # # ====== Remove an index # # change_table(:suppliers) do |t| # t.remove_index :company_id # end # # See also Table for details on all of the various column transformation. def change_table(table_name, options = {}) if supports_bulk_alter? && options[:bulk] recorder = ActiveRecord::Migration::CommandRecorder.new(self) yield update_table_definition(table_name, recorder) bulk_change_table(table_name, recorder.commands) else yield update_table_definition(table_name, self) end end # Renames a table. # # rename_table('octopuses', 'octopi') # def rename_table(table_name, new_name) raise NotImplementedError, "rename_table is not implemented" end # Drops a table from the database. # # [:force] # Set to +:cascade+ to drop dependent objects as well. # Defaults to false. # # Although this command ignores most +options+ and the block if one is given, # it can be helpful to provide these in a migration's +change+ method so it can be reverted. # In that case, +options+ and the block will be used by create_table. def drop_table(table_name, options = {}) execute "DROP TABLE #{quote_table_name(table_name)}" end # Adds a new column to the named table. # See TableDefinition#column for details of the options you can use. def add_column(table_name, column_name, type, options = {}) at = create_alter_table table_name at.add_column(column_name, type, options) execute schema_creation.accept at end # Removes the given columns from the table definition. # # remove_columns(:suppliers, :qualification, :experience) # def remove_columns(table_name, *column_names) raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty? column_names.each do |column_name| remove_column(table_name, column_name) end end # Removes the column from the table definition. # # remove_column(:suppliers, :qualification) # # The +type+ and +options+ parameters will be ignored if present. It can be helpful # to provide these in a migration's +change+ method so it can be reverted. # In that case, +type+ and +options+ will be used by add_column. def remove_column(table_name, column_name, type = nil, options = {}) execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}" end # Changes the column's definition according to the new options. # See TableDefinition#column for details of the options you can use. # # change_column(:suppliers, :name, :string, limit: 80) # change_column(:accounts, :description, :text) # def change_column(table_name, column_name, type, options = {}) raise NotImplementedError, "change_column is not implemented" end # Sets a new default value for a column: # # change_column_default(:suppliers, :qualification, 'new') # change_column_default(:accounts, :authorized, 1) # # Setting the default to +nil+ effectively drops the default: # # change_column_default(:users, :email, nil) # def change_column_default(table_name, column_name, default) raise NotImplementedError, "change_column_default is not implemented" end # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag # indicates whether the value can be +NULL+. For example # # change_column_null(:users, :nickname, false) # # says nicknames cannot be +NULL+ (adds the constraint), whereas # # change_column_null(:users, :nickname, true) # # allows them to be +NULL+ (drops the constraint). # # The method accepts an optional fourth argument to replace existing # +NULL+s with some other value. Use that one when enabling the # constraint if needed, since otherwise those rows would not be valid. # # Please note the fourth argument does not set a column's default. def change_column_null(table_name, column_name, null, default = nil) raise NotImplementedError, "change_column_null is not implemented" end # Renames a column. # # rename_column(:suppliers, :description, :name) # def rename_column(table_name, column_name, new_column_name) raise NotImplementedError, "rename_column is not implemented" end # Adds a new index to the table. +column_name+ can be a single Symbol, or # an Array of Symbols. # # The index will be named after the table and the column name(s), unless # you pass :name as an option. # # ====== Creating a simple index # # add_index(:suppliers, :name) # # generates: # # CREATE INDEX suppliers_name_index ON suppliers(name) # # ====== Creating a unique index # # add_index(:accounts, [:branch_id, :party_id], unique: true) # # generates: # # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id) # # ====== Creating a named index # # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party') # # generates: # # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id) # # ====== Creating an index with specific key length # # add_index(:accounts, :name, name: 'by_name', length: 10) # # generates: # # CREATE INDEX by_name ON accounts(name(10)) # # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15}) # # generates: # # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) # # Note: SQLite doesn't support index length. # # ====== Creating an index with a sort order (desc or asc, asc is the default) # # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc}) # # generates: # # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) # # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it). # # ====== Creating a partial index # # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active") # # generates: # # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active # # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+. # # ====== Creating an index with a specific method # # add_index(:developers, :name, using: 'btree') # # generates: # # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL # # Note: only supported by PostgreSQL and MySQL # # ====== Creating an index with a specific type # # add_index(:developers, :name, type: :fulltext) # # generates: # # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL # # Note: only supported by MySQL. Supported: :fulltext and :spatial on MyISAM tables. def add_index(table_name, column_name, options = {}) index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}" end # Removes the given index from the table. # # Removes the +index_accounts_on_column+ in the +accounts+ table. # # remove_index :accounts, :column # # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table. # # remove_index :accounts, column: :branch_id # # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table. # # remove_index :accounts, column: [:branch_id, :party_id] # # Removes the index named +by_branch_party+ in the +accounts+ table. # # remove_index :accounts, name: :by_branch_party # def remove_index(table_name, options = {}) remove_index!(table_name, index_name_for_remove(table_name, options)) end def remove_index!(table_name, index_name) #:nodoc: execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}" end # Renames an index. # # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+: # # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name' # def rename_index(table_name, old_name, new_name) validate_index_length!(table_name, new_name) # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance) old_index_def = indexes(table_name).detect { |i| i.name == old_name } return unless old_index_def add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique) remove_index(table_name, name: old_name) end def index_name(table_name, options) #:nodoc: if Hash === options if options[:column] "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}" elsif options[:name] options[:name] else raise ArgumentError, "You must specify the index name" end else index_name(table_name, :column => options) end end # Verifies the existence of an index with a given name. # # The default argument is returned if the underlying implementation does not define the indexes method, # as there's no way to determine the correct answer in that case. def index_name_exists?(table_name, index_name, default) return default unless respond_to?(:indexes) index_name = index_name.to_s indexes(table_name).detect { |i| i.name == index_name } end # Adds a reference. The reference column is an integer by default, # the :type option can be used to specify a different type. # Optionally adds a +_type+ column, if :polymorphic option is provided. # add_reference and add_belongs_to are acceptable. # # The +options+ hash can include the following keys: # [:type] # The reference column type. Defaults to +:integer+. # [:index] # Add an appropriate index. Defaults to false. # [:foreign_key] # Add an appropriate foreign key. Defaults to false. # [:polymorphic] # Wether an additional +_type+ column should be added. Defaults to false. # # ====== Create a user_id integer column # # add_reference(:products, :user) # # ====== Create a user_id string column # # add_reference(:products, :user, type: :string) # # ====== Create supplier_id, supplier_type columns and appropriate index # # add_reference(:products, :supplier, polymorphic: true, index: true) # def add_reference(table_name, ref_name, options = {}) polymorphic = options.delete(:polymorphic) index_options = options.delete(:index) type = options.delete(:type) || :integer foreign_key_options = options.delete(:foreign_key) if polymorphic && foreign_key_options raise ArgumentError, "Cannot add a foreign key to a polymorphic relation" end add_column(table_name, "#{ref_name}_id", type, options) add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options if foreign_key_options to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name add_foreign_key(table_name, to_table, foreign_key_options.is_a?(Hash) ? foreign_key_options : {}) end end alias :add_belongs_to :add_reference # Removes the reference(s). Also removes a +type+ column if one exists. # remove_reference, remove_references and remove_belongs_to are acceptable. # # ====== Remove the reference # # remove_reference(:products, :user, index: true) # # ====== Remove polymorphic reference # # remove_reference(:products, :supplier, polymorphic: true) # # ====== Remove the reference with a foreign key # # remove_reference(:products, :user, index: true, foreign_key: true) # def remove_reference(table_name, ref_name, options = {}) if options[:foreign_key] to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name remove_foreign_key(table_name, to_table) end remove_column(table_name, "#{ref_name}_id") remove_column(table_name, "#{ref_name}_type") if options[:polymorphic] end alias :remove_belongs_to :remove_reference # Returns an array of foreign keys for the given table. # The foreign keys are represented as +ForeignKeyDefinition+ objects. def foreign_keys(table_name) raise NotImplementedError, "foreign_keys is not implemented" end # Adds a new foreign key. +from_table+ is the table with the key column, # +to_table+ contains the referenced primary key. # # The foreign key will be named after the following pattern: fk_rails_. # +identifier+ is a 10 character long string which is deterministically generated from the # +from_table+ and +column+. A custom name can be specified with the :name option. # # ====== Creating a simple foreign key # # add_foreign_key :articles, :authors # # generates: # # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") # # ====== Creating a foreign key on a specific column # # add_foreign_key :articles, :users, column: :author_id, primary_key: :lng_id # # generates: # # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id") # # ====== Creating a cascading foreign key # # add_foreign_key :articles, :authors, on_delete: :cascade # # generates: # # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE # # The +options+ hash can include the following keys: # [:column] # The foreign key column name on +from_table+. Defaults to to_table.singularize + "_id" # [:primary_key] # The primary key column name on +to_table+. Defaults to +id+. # [:name] # The constraint name. Defaults to fk_rails_. # [:on_delete] # Action that happens ON DELETE. Valid values are +:nullify+, +:cascade:+ and +:restrict+ # [:on_update] # Action that happens ON UPDATE. Valid values are +:nullify+, +:cascade:+ and +:restrict+ def add_foreign_key(from_table, to_table, options = {}) return unless supports_foreign_keys? options[:column] ||= foreign_key_column_for(to_table) options = { column: options[:column], primary_key: options[:primary_key], name: foreign_key_name(from_table, options), on_delete: options[:on_delete], on_update: options[:on_update] } at = create_alter_table from_table at.add_foreign_key to_table, options execute schema_creation.accept(at) end # Removes the given foreign key from the table. # # Removes the foreign key on +accounts.branch_id+. # # remove_foreign_key :accounts, :branches # # Removes the foreign key on +accounts.owner_id+. # # remove_foreign_key :accounts, column: :owner_id # # Removes the foreign key named +special_fk_name+ on the +accounts+ table. # # remove_foreign_key :accounts, name: :special_fk_name # def remove_foreign_key(from_table, options_or_to_table = {}) return unless supports_foreign_keys? if options_or_to_table.is_a?(Hash) options = options_or_to_table else options = { column: foreign_key_column_for(options_or_to_table) } end fk_name_to_delete = options.fetch(:name) do fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s } if fk_to_delete fk_to_delete.name else raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'" end end at = create_alter_table from_table at.drop_foreign_key fk_name_to_delete execute schema_creation.accept(at) end def foreign_key_column_for(table_name) # :nodoc: prefix = Base.table_name_prefix suffix = Base.table_name_suffix name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s "#{name.singularize}_id" end def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name ActiveRecord::SchemaMigration.order('version').map { |sm| "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');" }.join "\n\n" end # Should not be called normally, but this operation is non-destructive. # The migrations module handles this automatically. def initialize_schema_migrations_table ActiveRecord::SchemaMigration.create_table end def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths) migrations_paths = Array(migrations_paths) version = version.to_i sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i } paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" } versions = Dir[*paths].map do |filename| filename.split('/').last.split('_').first.to_i end unless migrated.include?(version) execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" end inserted = Set.new (versions - migrated).each do |v| if inserted.include?(v) raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict." elsif v < version execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" inserted << v end end end def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: if native = native_database_types[type.to_sym] column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup if type == :decimal # ignore limit, use precision and scale scale ||= native[:scale] if precision ||= native[:precision] if scale column_type_sql << "(#{precision},#{scale})" else column_type_sql << "(#{precision})" end elsif scale raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified" end elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit]) column_type_sql << "(#{limit})" end column_type_sql else type.to_s end end # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT. # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they # require the order columns appear in the SELECT. # # columns_for_distinct("posts.id", ["posts.created_at desc"]) # def columns_for_distinct(columns, orders) # :nodoc: columns end include TimestampDefaultDeprecation # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. # Additional options (like null: false) are forwarded to #add_column. # # add_timestamps(:suppliers, null: false) # def add_timestamps(table_name, options = {}) emit_warning_if_null_unspecified(:add_timestamps, options) add_column table_name, :created_at, :datetime, options add_column table_name, :updated_at, :datetime, options end # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition. # # remove_timestamps(:suppliers) # def remove_timestamps(table_name, options = {}) remove_column table_name, :updated_at remove_column table_name, :created_at end def update_table_definition(table_name, base) #:nodoc: Table.new(table_name, base) end def add_index_options(table_name, column_name, options = {}) #:nodoc: column_names = Array(column_name) index_name = index_name(table_name, column: column_names) options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) index_type = options[:unique] ? "UNIQUE" : "" index_type = options[:type].to_s if options.key?(:type) index_name = options[:name].to_s if options.key?(:name) max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length if options.key?(:algorithm) algorithm = index_algorithms.fetch(options[:algorithm]) { raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") } end using = "USING #{options[:using]}" if options[:using].present? if supports_partial_index? index_options = options[:where] ? " WHERE #{options[:where]}" : "" end if index_name.length > max_index_length raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" end if table_exists?(table_name) && index_name_exists?(table_name, index_name, false) raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end index_columns = quoted_columns_for_index(column_names, options).join(", ") [index_name, index_type, index_columns, index_options, algorithm, using] end protected def add_index_sort_order(option_strings, column_names, options = {}) if options.is_a?(Hash) && order = options[:order] case order when Hash column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)} when String column_names.each {|name| option_strings[name] += " #{order.upcase}"} end end return option_strings end # Overridden by the MySQL adapter for supporting index lengths def quoted_columns_for_index(column_names, options = {}) option_strings = Hash[column_names.map {|name| [name, '']}] # add index sort order if supported if supports_index_sort_order? option_strings = add_index_sort_order(option_strings, column_names, options) end column_names.map {|name| quote_column_name(name) + option_strings[name]} end def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end def index_name_for_remove(table_name, options = {}) index_name = index_name(table_name, options) unless index_name_exists?(table_name, index_name, true) if options.is_a?(Hash) && options.has_key?(:name) options_without_column = options.dup options_without_column.delete :column index_name_without_column = index_name(table_name, options_without_column) return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false) end raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" end index_name end def rename_table_indexes(table_name, new_name) indexes(new_name).each do |index| generated_index_name = index_name(table_name, column: index.columns) if generated_index_name == index.name rename_index new_name, generated_index_name, index_name(new_name, column: index.columns) end end end def rename_column_indexes(table_name, column_name, new_column_name) column_name, new_column_name = column_name.to_s, new_column_name.to_s indexes(table_name).each do |index| next unless index.columns.include?(new_column_name) old_columns = index.columns.dup old_columns[old_columns.index(new_column_name)] = column_name generated_index_name = index_name(table_name, column: old_columns) if generated_index_name == index.name rename_index table_name, generated_index_name, index_name(table_name, column: index.columns) end end end private def create_table_definition(name, temporary, options, as = nil) TableDefinition.new native_database_types, name, temporary, options, as end def create_alter_table(name) AlterTable.new create_table_definition(name, false, {}) end def foreign_key_name(table_name, options) # :nodoc: identifier = "#{table_name}_#{options.fetch(:column)}_fk" hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) options.fetch(:name) do "fk_rails_#{hashed_identifier}" end end def validate_index_length!(table_name, new_name) if new_name.length > allowed_index_name_length raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb000066400000000000000000000117301266740050600313750ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters class TransactionState attr_reader :parent VALID_STATES = Set.new([:committed, :rolledback, nil]) def initialize(state = nil) @state = state @parent = nil end def finalized? @state end def committed? @state == :committed end def rolledback? @state == :rolledback end def completed? committed? || rolledback? end def set_state(state) if !VALID_STATES.include?(state) raise ArgumentError, "Invalid transaction state: #{state}" end @state = state end end class NullTransaction #:nodoc: def initialize; end def closed?; true; end def open?; false; end def joinable?; false; end def add_record(record); end end class Transaction #:nodoc: attr_reader :connection, :state, :records, :savepoint_name attr_writer :joinable def initialize(connection, options) @connection = connection @state = TransactionState.new @records = [] @joinable = options.fetch(:joinable, true) end def add_record(record) records << record end def rollback @state.set_state(:rolledback) end def rollback_records ite = records.uniq while record = ite.shift begin record.rolledback! full_rollback? rescue => e raise if ActiveRecord::Base.raise_in_transactional_callbacks record.logger.error(e) if record.respond_to?(:logger) && record.logger end end ensure ite.each do |i| i.rolledback!(full_rollback?, false) end end def commit @state.set_state(:committed) end def commit_records ite = records.uniq while record = ite.shift begin record.committed! rescue => e raise if ActiveRecord::Base.raise_in_transactional_callbacks record.logger.error(e) if record.respond_to?(:logger) && record.logger end end ensure ite.each do |i| i.committed!(false) end end def full_rollback?; true; end def joinable?; @joinable; end def closed?; false; end def open?; !closed?; end end class SavepointTransaction < Transaction def initialize(connection, savepoint_name, options) super(connection, options) if options[:isolation] raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction" end connection.create_savepoint(@savepoint_name = savepoint_name) end def rollback connection.rollback_to_savepoint(savepoint_name) super rollback_records end def commit connection.release_savepoint(savepoint_name) super parent = connection.transaction_manager.current_transaction records.each { |r| parent.add_record(r) } end def full_rollback?; false; end end class RealTransaction < Transaction def initialize(connection, options) super if options[:isolation] connection.begin_isolated_db_transaction(options[:isolation]) else connection.begin_db_transaction end end def rollback connection.rollback_db_transaction super rollback_records end def commit connection.commit_db_transaction super commit_records end end class TransactionManager #:nodoc: def initialize(connection) @stack = [] @connection = connection end def begin_transaction(options = {}) transaction = if @stack.empty? RealTransaction.new(@connection, options) else SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options) end @stack.push(transaction) transaction end def commit_transaction @stack.pop.commit end def rollback_transaction @stack.pop.rollback end def within_new_transaction(options = {}) transaction = begin_transaction options yield rescue Exception => error rollback_transaction if transaction raise ensure unless error if Thread.current.status == 'aborting' rollback_transaction if transaction else begin commit_transaction rescue Exception transaction.rollback unless transaction.state.completed? raise end end end end def open_transactions @stack.size end def current_transaction @stack.last || NULL_TRANSACTION end private NULL_TRANSACTION = NullTransaction.new end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb000066400000000000000000000346061266740050600305570ustar00rootroot00000000000000require 'date' require 'bigdecimal' require 'bigdecimal/util' require 'active_record/type' require 'active_support/core_ext/benchmark' require 'active_record/connection_adapters/schema_cache' require 'active_record/connection_adapters/abstract/schema_dumper' require 'active_record/connection_adapters/abstract/schema_creation' require 'monitor' require 'arel/collectors/bind' require 'arel/collectors/sql_string' module ActiveRecord module ConnectionAdapters # :nodoc: extend ActiveSupport::Autoload autoload :Column autoload :ConnectionSpecification autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do autoload :IndexDefinition autoload :ColumnDefinition autoload :ChangeColumnDefinition autoload :TableDefinition autoload :Table autoload :AlterTable autoload :TimestampDefaultDeprecation end autoload_at 'active_record/connection_adapters/abstract/connection_pool' do autoload :ConnectionHandler autoload :ConnectionManagement end autoload_under 'abstract' do autoload :SchemaStatements autoload :DatabaseStatements autoload :DatabaseLimits autoload :Quoting autoload :ConnectionPool autoload :QueryCache autoload :Savepoints end autoload_at 'active_record/connection_adapters/abstract/transaction' do autoload :TransactionManager autoload :NullTransaction autoload :RealTransaction autoload :SavepointTransaction autoload :TransactionState end # Active Record supports multiple database systems. AbstractAdapter and # related classes form the abstraction layer which makes this possible. # An AbstractAdapter represents a connection to a database, and provides an # abstract interface for database-specific functionality such as establishing # a connection, escaping values, building the right SQL fragments for ':offset' # and ':limit' options, etc. # # All the concrete database adapters follow the interface laid down in this class. # ActiveRecord::Base.connection returns an AbstractAdapter object, which # you can use. # # Most of the methods in the adapter are useful during migrations. Most # notably, the instance methods provided by SchemaStatement are very useful. class AbstractAdapter ADAPTER_NAME = 'Abstract'.freeze include Quoting, DatabaseStatements, SchemaStatements include DatabaseLimits include QueryCache include ActiveSupport::Callbacks include MonitorMixin include ColumnDumper SIMPLE_INT = /\A\d+\z/ define_callbacks :checkout, :checkin attr_accessor :visitor, :pool attr_reader :schema_cache, :owner, :logger alias :in_use? :owner def self.type_cast_config_to_integer(config) if config =~ SIMPLE_INT config.to_i else config end end def self.type_cast_config_to_boolean(config) if config == "false" false else config end end attr_reader :prepared_statements def initialize(connection, logger = nil, pool = nil) #:nodoc: super() @connection = connection @owner = nil @instrumenter = ActiveSupport::Notifications.instrumenter @logger = logger @pool = pool @schema_cache = SchemaCache.new self @visitor = nil @prepared_statements = false end class BindCollector < Arel::Collectors::Bind def compile(bvs, conn) super(bvs.map { |bv| conn.quote(*bv.reverse) }) end end class SQLString < Arel::Collectors::SQLString def compile(bvs, conn) super(bvs) end end def collector if prepared_statements SQLString.new else BindCollector.new end end def valid_type?(type) true end def schema_creation SchemaCreation.new self end def lease synchronize do unless in_use? @owner = Thread.current end end end def schema_cache=(cache) cache.connection = self @schema_cache = cache end def expire @owner = nil end def unprepared_statement old_prepared_statements, @prepared_statements = @prepared_statements, false yield ensure @prepared_statements = old_prepared_statements end # Returns the human-readable name of the adapter. Use mixed case - one # can always use downcase if needed. def adapter_name self.class::ADAPTER_NAME end # Does this adapter support migrations? def supports_migrations? false end # Can this adapter determine the primary key for tables not attached # to an Active Record class, such as join tables? def supports_primary_key? false end # Does this adapter support DDL rollbacks in transactions? That is, would # CREATE TABLE or ALTER TABLE get rolled back by a transaction? def supports_ddl_transactions? false end def supports_bulk_alter? false end # Does this adapter support savepoints? def supports_savepoints? false end # Should primary key values be selected from their corresponding # sequence before the insert statement? If true, next_sequence_value # is called before each insert to set the record's primary key. def prefetch_primary_key?(table_name = nil) false end # Does this adapter support index sort order? def supports_index_sort_order? false end # Does this adapter support partial indices? def supports_partial_index? false end # Does this adapter support explain? def supports_explain? false end # Does this adapter support setting the isolation level for a transaction? def supports_transaction_isolation? false end # Does this adapter support database extensions? def supports_extensions? false end # Does this adapter support creating indexes in the same statement as # creating the table? def supports_indexes_in_create? false end # Does this adapter support creating foreign key constraints? def supports_foreign_keys? false end # Does this adapter support views? def supports_views? false end # This is meant to be implemented by the adapters that support extensions def disable_extension(name) end # This is meant to be implemented by the adapters that support extensions def enable_extension(name) end # A list of extensions, to be filled in by adapters that support them. def extensions [] end # A list of index algorithms, to be filled by adapters that support them. def index_algorithms {} end # QUOTING ================================================== # Returns a bind substitution value given a bind +column+ # NOTE: The column param is currently being used by the sqlserver-adapter def substitute_at(column, _unused = 0) Arel::Nodes::BindParam.new end # REFERENTIAL INTEGRITY ==================================== # Override to turn off referential integrity while executing &block. def disable_referential_integrity yield end # CONNECTION MANAGEMENT ==================================== # Checks whether the connection to the database is still active. This includes # checking whether the database is actually capable of responding, i.e. whether # the connection isn't stale. def active? end # Disconnects from the database if already connected, and establishes a # new connection with the database. Implementors should call super if they # override the default implementation. def reconnect! clear_cache! reset_transaction end # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! clear_cache! reset_transaction end # Reset the state of this connection, directing the DBMS to clear # transactions and other connection-related server-side state. Usually a # database-dependent operation. # # The default implementation does nothing; the implementation should be # overridden by concrete adapters. def reset! # this should be overridden by concrete adapters end ### # Clear any caching the database adapter may be doing, for example # clearing the prepared statement cache. This is database specific. def clear_cache! # this should be overridden by concrete adapters end # Returns true if its required to reload the connection between requests for development mode. def requires_reloading? false end # Checks whether the connection to the database is still active (i.e. not stale). # This is done under the hood by calling active?. If the connection # is no longer active, then this method will reconnect to the database. def verify!(*ignored) reconnect! unless active? end # Provides access to the underlying database driver for this adapter. For # example, this method returns a Mysql object in case of MysqlAdapter, # and a PGconn object in case of PostgreSQLAdapter. # # This is useful for when you need to call a proprietary method such as # PostgreSQL's lo_* methods. def raw_connection @connection end def create_savepoint(name = nil) end def release_savepoint(name = nil) end def case_sensitive_modifier(node, table_attribute) node end def case_sensitive_comparison(table, attribute, column, value) table_attr = table[attribute] value = case_sensitive_modifier(value, table_attr) unless value.nil? table_attr.eq(value) end def case_insensitive_comparison(table, attribute, column, value) table[attribute].lower.eq(table.lower(value)) end def current_savepoint_name current_transaction.savepoint_name end # Check the connection back in to the connection pool def close pool.checkin self end def type_map # :nodoc: @type_map ||= Type::TypeMap.new.tap do |mapping| initialize_type_map(mapping) end end def new_column(name, default, cast_type, sql_type = nil, null = true) Column.new(name, default, cast_type, sql_type, null) end def lookup_cast_type(sql_type) # :nodoc: type_map.lookup(sql_type) end def column_name_for_operation(operation, node) # :nodoc: visitor.accept(node, collector).value end protected def initialize_type_map(m) # :nodoc: register_class_with_limit m, %r(boolean)i, Type::Boolean register_class_with_limit m, %r(char)i, Type::String register_class_with_limit m, %r(binary)i, Type::Binary register_class_with_limit m, %r(text)i, Type::Text register_class_with_limit m, %r(date)i, Type::Date register_class_with_limit m, %r(time)i, Type::Time register_class_with_limit m, %r(datetime)i, Type::DateTime register_class_with_limit m, %r(float)i, Type::Float register_class_with_limit m, %r(int)i, Type::Integer m.alias_type %r(blob)i, 'binary' m.alias_type %r(clob)i, 'text' m.alias_type %r(timestamp)i, 'datetime' m.alias_type %r(numeric)i, 'decimal' m.alias_type %r(number)i, 'decimal' m.alias_type %r(double)i, 'float' m.register_type(%r(decimal)i) do |sql_type| scale = extract_scale(sql_type) precision = extract_precision(sql_type) if scale == 0 # FIXME: Remove this class as well Type::DecimalWithoutScale.new(precision: precision) else Type::Decimal.new(precision: precision, scale: scale) end end end def reload_type_map # :nodoc: type_map.clear initialize_type_map(type_map) end def register_class_with_limit(mapping, key, klass) # :nodoc: mapping.register_type(key) do |*args| limit = extract_limit(args.last) klass.new(limit: limit) end end def extract_scale(sql_type) # :nodoc: case sql_type when /\((\d+)\)/ then 0 when /\((\d+)(,(\d+))\)/ then $3.to_i end end def extract_precision(sql_type) # :nodoc: $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ end def extract_limit(sql_type) # :nodoc: case sql_type when /^bigint/i 8 when /\((.*)\)/ $1.to_i end end def translate_exception_class(e, sql) begin message = "#{e.class.name}: #{e.message}: #{sql}" rescue Encoding::CompatibilityError message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" end exception = translate_exception(e, message) exception.set_backtrace e.backtrace exception end def log(sql, name = "SQL", binds = [], statement_name = nil) @instrumenter.instrument( "sql.active_record", :sql => sql, :name => name, :connection_id => object_id, :statement_name => statement_name, :binds => binds) { yield } rescue => e raise translate_exception_class(e, sql) end def translate_exception(exception, message) # override in derived class ActiveRecord::StatementInvalid.new(message, exception) end def without_prepared_statement?(binds) !prepared_statements || binds.empty? end def column_for(table_name, column_name) # :nodoc: column_name = column_name.to_s columns(table_name).detect { |c| c.name == column_name } || raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb000066400000000000000000000773201266740050600320040ustar00rootroot00000000000000require 'arel/visitors/bind_visitor' require 'active_support/core_ext/string/strip' module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter include Savepoints class SchemaCreation < AbstractAdapter::SchemaCreation def visit_AddColumn(o) add_column_position!(super, column_options(o)) end private def visit_DropForeignKey(name) "DROP FOREIGN KEY #{name}" end def visit_TableDefinition(o) name = o.name create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} " statements = o.columns.map { |c| accept c } statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) }) create_sql << "(#{statements.join(', ')}) " if statements.present? create_sql << "#{o.options}" create_sql << " AS #{@conn.to_sql(o.as)}" if o.as create_sql end def visit_ChangeColumnDefinition(o) column = o.column options = o.options sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale]) change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}" add_column_options!(change_column_sql, options.merge(column: column)) add_column_position!(change_column_sql, options) end def add_column_position!(sql, options) if options[:first] sql << " FIRST" elsif options[:after] sql << " AFTER #{quote_column_name(options[:after])}" end sql end def index_in_create(table_name, column_name, options) index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options) "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}" end end def schema_creation SchemaCreation.new self end def prepare_column_options(column, types) # :nodoc: spec = super spec.delete(:limit) if :boolean === column.type spec end class Column < ConnectionAdapters::Column # :nodoc: attr_reader :collation, :strict, :extra def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "") @strict = strict @collation = collation @extra = extra super(name, default, cast_type, sql_type, null) assert_valid_default(default) extract_default end def extract_default if blob_or_text_column? @default = null || strict ? nil : '' elsif missing_default_forged_as_empty_string?(@default) @default = nil end end def has_default? return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns super end def blob_or_text_column? sql_type =~ /blob/i || type == :text end def case_sensitive? collation && !collation.match(/_ci$/) end def ==(other) super && collation == other.collation && strict == other.strict && extra == other.extra end private # MySQL misreports NOT NULL column default when none is given. # We can't detect this for columns which may have a legitimate '' # default (string) but we can for others (integer, datetime, boolean, # and the rest). # # Test whether the column has default '', is not null, and is not # a type allowing default ''. def missing_default_forged_as_empty_string?(default) type != :string && !null && default == '' end def assert_valid_default(default) if blob_or_text_column? && default.present? raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" end end def attributes_for_hash super + [collation, strict, extra] end end ## # :singleton-method: # By default, the MysqlAdapter will consider all columns of type tinyint(1) # as boolean. If you wish to disable this emulation (which was the default # behavior in versions 0.13.1 and earlier) you can add the following line # to your application.rb file: # # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false class_attribute :emulate_booleans self.emulate_booleans = true LOST_CONNECTION_ERROR_MESSAGES = [ "Server shutdown in progress", "Broken pipe", "Lost connection to MySQL server during query", "MySQL server has gone away" ] QUOTED_TRUE, QUOTED_FALSE = '1', '0' NATIVE_DATABASE_TYPES = { :primary_key => "int(11) auto_increment PRIMARY KEY", :string => { :name => "varchar", :limit => 255 }, :text => { :name => "text" }, :integer => { :name => "int", :limit => 4 }, :float => { :name => "float" }, :decimal => { :name => "decimal" }, :datetime => { :name => "datetime" }, :time => { :name => "time" }, :date => { :name => "date" }, :binary => { :name => "blob" }, :boolean => { :name => "tinyint", :limit => 1 } } INDEX_TYPES = [:fulltext, :spatial] INDEX_USINGS = [:btree, :hash] # FIXME: Make the first parameter more similar for the two adapters def initialize(connection, logger, connection_options, config) super(connection, logger) @connection_options, @config = connection_options, config @quoted_column_names, @quoted_table_names = {}, {} @visitor = Arel::Visitors::MySQL.new self if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true else @prepared_statements = false end end # Returns true, since this connection adapter supports migrations. def supports_migrations? true end def supports_primary_key? true end def supports_bulk_alter? #:nodoc: true end # Technically MySQL allows to create indexes with the sort order syntax # but at the moment (5.5) it doesn't yet implement them def supports_index_sort_order? true end # MySQL 4 technically support transaction isolation, but it is affected by a bug # where the transaction level gets persisted for the whole session: # # http://bugs.mysql.com/bug.php?id=39170 def supports_transaction_isolation? version[0] >= 5 end def supports_indexes_in_create? true end def supports_foreign_keys? true end def supports_views? version[0] >= 5 end def native_database_types NATIVE_DATABASE_TYPES end def index_algorithms { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' } end # HELPER METHODS =========================================== # The two drivers have slightly different ways of yielding hashes of results, so # this method must be implemented to provide a uniform interface. def each_hash(result) # :nodoc: raise NotImplementedError end def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc: Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra) end # Must return the MySQL error number from the exception, if the exception has an # error number. def error_number(exception) # :nodoc: raise NotImplementedError end # QUOTING ================================================== def _quote(value) # :nodoc: if value.is_a?(Type::Binary::Data) "x'#{value.hex}'" else super end end def quote_column_name(name) #:nodoc: @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`" end def quote_table_name(name) #:nodoc: @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`') end def quoted_true QUOTED_TRUE end def unquoted_true 1 end def quoted_false QUOTED_FALSE end def unquoted_false 0 end # REFERENTIAL INTEGRITY ==================================== def disable_referential_integrity #:nodoc: old = select_value("SELECT @@FOREIGN_KEY_CHECKS") begin update("SET FOREIGN_KEY_CHECKS = 0") yield ensure update("SET FOREIGN_KEY_CHECKS = #{old}") end end #-- # DATABASE STATEMENTS ====================================== #++ def clear_cache! super reload_type_map end # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) log(sql, name) { @connection.query(sql) } end # MysqlAdapter has to free a result after using it, so we use this method to write # stuff in an abstract way without concerning ourselves about whether it needs to be # explicitly freed or not. def execute_and_free(sql, name = nil) #:nodoc: yield execute(sql, name) end def update_sql(sql, name = nil) #:nodoc: super @connection.affected_rows end def begin_db_transaction execute "BEGIN" end def begin_isolated_db_transaction(isolation) execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" begin_db_transaction end def commit_db_transaction #:nodoc: execute "COMMIT" end def exec_rollback_db_transaction #:nodoc: execute "ROLLBACK" end # In the simple case, MySQL allows us to place JOINs directly into the UPDATE # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support # these, we must use a subquery. def join_to_update(update, select) #:nodoc: if select.limit || select.offset || select.orders.any? super else update.table select.source update.wheres = select.constraints end end def empty_insert_statement_value "VALUES ()" end # SCHEMA STATEMENTS ======================================== # Drops the database specified on the +name+ attribute # and creates it again using the provided +options+. def recreate_database(name, options = {}) drop_database(name) sql = create_database(name, options) reconnect! sql end # Create a new MySQL database with optional :charset and :collation. # Charset defaults to utf8. # # Example: # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin' # create_database 'matt_development' # create_database 'matt_development', charset: :big5 def create_database(name, options = {}) if options[:collation] execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`" else execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`" end end # Drops a MySQL database. # # Example: # drop_database('sebastian_development') def drop_database(name) #:nodoc: execute "DROP DATABASE IF EXISTS `#{name}`" end def current_database select_value 'SELECT DATABASE() as db' end # Returns the database character set. def charset show_variable 'character_set_database' end # Returns the database collation strategy. def collation show_variable 'collation_database' end def tables(name = nil, database = nil, like = nil) #:nodoc: sql = "SHOW TABLES " sql << "IN #{quote_table_name(database)} " if database sql << "LIKE #{quote(like)}" if like execute_and_free(sql, 'SCHEMA') do |result| result.collect { |field| field.first } end end alias data_sources tables def truncate(table_name, name = nil) execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name end def table_exists?(name) return false unless name.present? return true if tables(nil, nil, name).any? name = name.to_s schema, table = name.split('.', 2) unless table # A table was provided without a schema table = schema schema = nil end tables(nil, schema, table).any? end alias data_source_exists? table_exists? # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: indexes = [] current_index = nil execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result| each_hash(result) do |row| if current_index != row[:Key_name] next if row[:Key_name] == 'PRIMARY' # skip the primary key current_index = row[:Key_name] mysql_index_type = row[:Index_type].downcase.to_sym index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using) end indexes.last.columns << row[:Column_name] indexes.last.lengths << row[:Sub_part] end end indexes end # Returns an array of +Column+ objects for the table specified by +table_name+. def columns(table_name)#:nodoc: sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}" execute_and_free(sql, 'SCHEMA') do |result| each_hash(result).map do |field| field_name = set_field_encoding(field[:Field]) sql_type = field[:Type] cast_type = lookup_cast_type(sql_type) new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra]) end end end def create_table(table_name, options = {}) #:nodoc: super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB")) end def bulk_change_table(table_name, operations) #:nodoc: sqls = operations.flat_map do |command, args| table, arguments = args.shift, args method = :"#{command}_sql" if respond_to?(method, true) send(method, table, *arguments) else raise "Unknown method called : #{method}(#{arguments.inspect})" end end.join(", ") execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}") end # Renames a table. # # Example: # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" rename_table_indexes(table_name, new_name) end def drop_table(table_name, options = {}) execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end def rename_index(table_name, old_name, new_name) if supports_rename_index? validate_index_length!(table_name, new_name) execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}" else super end end def change_column_default(table_name, column_name, default) #:nodoc: column = column_for(table_name, column_name) change_column table_name, column_name, column.sql_type, :default => default end def change_column_null(table_name, column_name, null, default = nil) column = column_for(table_name, column_name) unless null || default.nil? execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end change_column table_name, column_name, column.sql_type, :null => null end def change_column(table_name, column_name, type, options = {}) #:nodoc: execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}") end def rename_column(table_name, column_name, new_column_name) #:nodoc: execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}") rename_column_indexes(table_name, column_name, new_column_name) end def add_index(table_name, column_name, options = {}) #:nodoc: index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}" end def foreign_keys(table_name) fk_info = select_all <<-SQL.strip_heredoc SELECT fk.referenced_table_name as 'to_table' ,fk.referenced_column_name as 'primary_key' ,fk.column_name as 'column' ,fk.constraint_name as 'name' FROM information_schema.key_column_usage fk WHERE fk.referenced_column_name is not null AND fk.table_schema = '#{@config[:database]}' AND fk.table_name = '#{table_name}' SQL create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] fk_info.map do |row| options = { column: row['column'], name: row['name'], primary_key: row['primary_key'] } options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE") options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE") ForeignKeyDefinition.new(table_name, row['to_table'], options) end end # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s when 'binary' case limit when 0..0xfff; "varbinary(#{limit})" when nil; "blob" when 0x1000..0xffffffff; "blob(#{limit})" else raise(ActiveRecordError, "No binary type has character length #{limit}") end when 'integer' case limit when 1; 'tinyint' when 2; 'smallint' when 3; 'mediumint' when nil, 4, 11; 'int(11)' # compatibility with MySQL default when 5..8; 'bigint' else raise(ActiveRecordError, "No integer type has byte size #{limit}") end when 'text' case limit when 0..0xff; 'tinytext' when nil, 0x100..0xffff; 'text' when 0x10000..0xffffff; 'mediumtext' when 0x1000000..0xffffffff; 'longtext' else raise(ActiveRecordError, "No text type has character length #{limit}") end when 'datetime' return super unless precision case precision when 0..6; "datetime(#{precision})" else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.") end else super end end # SHOW VARIABLES LIKE 'name' def show_variable(name) variables = select_all("select @@#{name} as 'Value'", 'SCHEMA') variables.first['Value'] unless variables.empty? rescue ActiveRecord::StatementInvalid nil end # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result| create_table = each_hash(result).first[:"Create Table"] if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/ keys = $1.split(",").map { |key| key.delete('`"') } keys.length == 1 ? [keys.first, nil] : nil else nil end end end # Returns just a table's primary key def primary_key(table) pk_and_sequence = pk_and_sequence_for(table) pk_and_sequence && pk_and_sequence.first end def case_sensitive_modifier(node, table_attribute) node = Arel::Nodes.build_quoted node, table_attribute Arel::Nodes::Bin.new(node) end def case_sensitive_comparison(table, attribute, column, value) if column.case_sensitive? table[attribute].eq(value) else super end end def case_insensitive_comparison(table, attribute, column, value) if column.case_sensitive? super else table[attribute].eq(value) end end # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for # distinct queries, and requires that the ORDER BY include the distinct column. # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html def columns_for_distinct(columns, orders) # :nodoc: order_columns = orders.reject(&:blank?).map { |s| # Convert Arel node to string s = s.to_sql unless s.is_a?(String) # Remove any ASC/DESC modifiers s.gsub(/\s+(?:ASC|DESC)\b/i, '') }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } [super, *order_columns].join(', ') end def strict_mode? self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end def valid_type?(type) !native_database_types[type].nil? end protected def initialize_type_map(m) # :nodoc: super register_class_with_limit m, %r(char)i, MysqlString m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) m.register_type %r(^float)i, Type::Float.new(limit: 24) m.register_type %r(^double)i, Type::Float.new(limit: 53) register_integer_type m, %r(^bigint)i, limit: 8 register_integer_type m, %r(^int)i, limit: 4 register_integer_type m, %r(^mediumint)i, limit: 3 register_integer_type m, %r(^smallint)i, limit: 2 register_integer_type m, %r(^tinyint)i, limit: 1 m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans m.alias_type %r(set)i, 'varchar' m.alias_type %r(year)i, 'integer' m.alias_type %r(bit)i, 'binary' m.register_type(%r(datetime)i) do |sql_type| precision = extract_precision(sql_type) MysqlDateTime.new(precision: precision) end m.register_type(%r(enum)i) do |sql_type| limit = sql_type[/^enum\((.+)\)/i, 1] .split(',').map{|enum| enum.strip.length - 2}.max MysqlString.new(limit: limit) end end def register_integer_type(mapping, key, options) # :nodoc: mapping.register_type(key) do |sql_type| if /unsigned/i =~ sql_type Type::UnsignedInteger.new(options) else Type::Integer.new(options) end end end # MySQL is too stupid to create a temporary table for use subquery, so we have # to give it some prompting in the form of a subsubquery. Ugh! def subquery_for(key, select) subsubselect = select.clone subsubselect.projections = [key] # Materialize subquery by adding distinct # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' subsubselect.distinct unless select.limit || select.offset || select.orders.any? subselect = Arel::SelectManager.new(select.engine) subselect.project Arel.sql(key.name) subselect.from subsubselect.as('__active_record_temp') end def add_index_length(option_strings, column_names, options = {}) if options.is_a?(Hash) && length = options[:length] case length when Hash column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?} when Fixnum column_names.each {|name| option_strings[name] += "(#{length})"} end end return option_strings end def quoted_columns_for_index(column_names, options = {}) option_strings = Hash[column_names.map {|name| [name, '']}] # add index length option_strings = add_index_length(option_strings, column_names, options) # add index sort order option_strings = add_index_sort_order(option_strings, column_names, options) column_names.map {|name| quote_column_name(name) + option_strings[name]} end def translate_exception(exception, message) case error_number(exception) when 1062 RecordNotUnique.new(message, exception) when 1452 InvalidForeignKey.new(message, exception) else super end end def add_column_sql(table_name, column_name, type, options = {}) td = create_table_definition table_name, options[:temporary], options[:options] cd = td.new_column_definition(column_name, type, options) schema_creation.visit_AddColumn cd end def change_column_sql(table_name, column_name, type, options = {}) column = column_for(table_name, column_name) unless options_include_default?(options) options[:default] = column.default end unless options.has_key?(:null) options[:null] = column.null end options[:name] = column.name schema_creation.accept ChangeColumnDefinition.new column, type, options end def rename_column_sql(table_name, column_name, new_column_name) column = column_for(table_name, column_name) options = { name: new_column_name, default: column.default, null: column.null, auto_increment: column.extra == "auto_increment" } current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"] schema_creation.accept ChangeColumnDefinition.new column, current_type, options end def remove_column_sql(table_name, column_name, type = nil, options = {}) "DROP #{quote_column_name(column_name)}" end def remove_columns_sql(table_name, *column_names) column_names.map {|column_name| remove_column_sql(table_name, column_name) } end def add_index_sql(table_name, column_name, options = {}) index_name, index_type, index_columns = add_index_options(table_name, column_name, options) "ADD #{index_type} INDEX #{index_name} (#{index_columns})" end def remove_index_sql(table_name, options = {}) index_name = index_name_for_remove(table_name, options) "DROP INDEX #{index_name}" end def add_timestamps_sql(table_name, options = {}) [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] end def remove_timestamps_sql(table_name, options = {}) [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] end private def version @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } end def mariadb? full_version =~ /mariadb/i end def supports_rename_index? mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6 end def configure_connection variables = @config.fetch(:variables, {}).stringify_keys # By default, MySQL 'where id is null' selects the last inserted id. # Turn this off. http://dev.rubyonrails.org/ticket/6778 variables['sql_auto_is_null'] = 0 # Increase timeout so the server doesn't disconnect us. wait_timeout = @config[:wait_timeout] wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout) # Make MySQL reject illegal values rather than truncating or blanking them, see # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables # If the user has provided another value for sql_mode, don't replace it. unless variables.has_key?('sql_mode') variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : '' end # NAMES does not have an equals sign, see # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430 # (trailing comma because variable_assignments will always have content) if @config[:encoding] encoding = "NAMES #{@config[:encoding]}" encoding << " COLLATE #{@config[:collation]}" if @config[:collation] encoding << ", " end # Gather up all of the SET variables... variable_assignments = variables.map do |k, v| if v == ':default' || v == :default "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default elsif !v.nil? "@@SESSION.#{k} = #{quote(v)}" end # or else nil; compact to clear nils out end.compact.join(', ') # ...and send them all in one query @connection.query "SET #{encoding} #{variable_assignments}" end def extract_foreign_key_action(structure, name, action) # :nodoc: if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/ case $1 when 'CASCADE'; :cascade when 'SET NULL'; :nullify end end end class MysqlDateTime < Type::DateTime # :nodoc: private def has_precision? precision || 0 end end class MysqlString < Type::String # :nodoc: def type_cast_for_database(value) case value when true then "1" when false then "0" else super end end private def cast_value(value) case value when true then "1" when false then "0" else super end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/column.rb000066400000000000000000000052001266740050600265350ustar00rootroot00000000000000require 'set' module ActiveRecord # :stopdoc: module ConnectionAdapters # An abstract definition of a column in a table. class Column TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set module Format ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ end attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function delegate :type, :precision, :scale, :limit, :klass, :accessor, :text?, :number?, :binary?, :changed?, :type_cast_from_user, :type_cast_from_database, :type_cast_for_database, :type_cast_for_schema, to: :cast_type # Instantiates a new column in the table. # # +name+ is the column's name, such as supplier_id in supplier_id int(11). # +default+ is the type-casted default value, such as +new+ in sales_stage varchar(20) default 'new'. # +cast_type+ is the object used for type casting and type information. # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in # company_name varchar(60). # It will be mapped to one of the standard Rails SQL types in the type attribute. # +null+ determines if this column allows +NULL+ values. def initialize(name, default, cast_type, sql_type = nil, null = true) @name = name.freeze @cast_type = cast_type @sql_type = sql_type @null = null @default = default @default_function = nil end def has_default? !default.nil? end # Returns the human name of the column name. # # ===== Examples # Column.new('sales_stage', ...).human_name # => 'Sales stage' def human_name Base.human_attribute_name(@name) end def with_type(type) dup.tap do |clone| clone.instance_variable_set('@cast_type', type) end end def ==(other) other.name == name && other.default == default && other.cast_type == cast_type && other.sql_type == sql_type && other.null == null && other.default_function == default_function end alias :eql? :== def hash attributes_for_hash.hash end private def attributes_for_hash [self.class, name, default, cast_type, sql_type, null, default_function] end end end # :startdoc: end rails-4.2.6/activerecord/lib/active_record/connection_adapters/connection_specification.rb000066400000000000000000000227571266740050600323170ustar00rootroot00000000000000require 'uri' require 'active_support/core_ext/string/filters' module ActiveRecord module ConnectionAdapters class ConnectionSpecification #:nodoc: attr_reader :config, :adapter_method def initialize(config, adapter_method) @config, @adapter_method = config, adapter_method end def initialize_dup(original) @config = original.config.dup end # Expands a connection string into a hash. class ConnectionUrlResolver # :nodoc: # == Example # # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000" # ConnectionUrlResolver.new(url).to_hash # # => { # "adapter" => "postgresql", # "host" => "localhost", # "port" => 9000, # "database" => "foo_test", # "username" => "foo", # "password" => "bar", # "pool" => "5", # "timeout" => "3000" # } def initialize(url) raise "Database URL cannot be empty" if url.blank? @uri = uri_parser.parse(url) @adapter = @uri.scheme.tr('-', '_') @adapter = "postgresql" if @adapter == "postgres" if @uri.opaque @uri.opaque, @query = @uri.opaque.split('?', 2) else @query = @uri.query end end # Converts the given URL to a full connection hash. def to_hash config = raw_config.reject { |_,value| value.blank? } config.map { |key,value| config[key] = uri_parser.unescape(value) if value.is_a? String } config end private def uri @uri end def uri_parser @uri_parser ||= URI::Parser.new end # Converts the query parameters of the URI into a hash. # # "localhost?pool=5&reaping_frequency=2" # # => { "pool" => "5", "reaping_frequency" => "2" } # # returns empty hash if no query present. # # "localhost" # # => {} def query_hash Hash[(@query || '').split("&").map { |pair| pair.split("=") }] end def raw_config if uri.opaque query_hash.merge({ "adapter" => @adapter, "database" => uri.opaque }) else query_hash.merge({ "adapter" => @adapter, "username" => uri.user, "password" => uri.password, "port" => uri.port, "database" => database_from_path, "host" => uri.hostname }) end end # Returns name of the database. def database_from_path if @adapter == 'sqlite3' # 'sqlite3:/foo' is absolute, because that makes sense. The # corresponding relative version, 'sqlite3:foo', is handled # elsewhere, as an "opaque". uri.path else # Only SQLite uses a filename as the "database" name; for # anything else, a leading slash would be silly. uri.path.sub(%r{^/}, "") end end end ## # Builds a ConnectionSpecification from user input. class Resolver # :nodoc: attr_reader :configurations # Accepts a hash two layers deep, keys on the first layer represent # environments such as "production". Keys must be strings. def initialize(configurations) @configurations = configurations end # Returns a hash with database connection information. # # == Examples # # Full hash Configuration. # # configurations = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } } # Resolver.new(configurations).resolve(:production) # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3"} # # Initialized with URL configuration strings. # # configurations = { "production" => "postgresql://localhost/foo" } # Resolver.new(configurations).resolve(:production) # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # def resolve(config) if config resolve_connection config elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call resolve_symbol_connection env.to_sym else raise AdapterNotSpecified end end # Expands each key in @configurations hash into fully resolved hash def resolve_all config = configurations.dup config.each do |key, value| config[key] = resolve(value) if value end config end # Returns an instance of ConnectionSpecification for a given adapter. # Accepts a hash one layer deep that contains all connection information. # # == Example # # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } } # spec = Resolver.new(config).spec(:production) # spec.adapter_method # # => "sqlite3_connection" # spec.config # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } # def spec(config) spec = resolve(config).symbolize_keys raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter" begin require path_to_adapter rescue Gem::LoadError => e raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)." rescue LoadError => e raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace end adapter_method = "#{spec[:adapter]}_connection" ConnectionSpecification.new(spec, adapter_method) end private # Returns fully resolved connection, accepts hash, string or symbol. # Always returns a hash. # # == Examples # # Symbol representing current environment. # # Resolver.new("production" => {}).resolve_connection(:production) # # => {} # # One layer deep hash of connection values. # # Resolver.new({}).resolve_connection("adapter" => "sqlite3") # # => { "adapter" => "sqlite3" } # # Connection URL. # # Resolver.new({}).resolve_connection("postgresql://localhost/foo") # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # def resolve_connection(spec) case spec when Symbol resolve_symbol_connection spec when String resolve_string_connection spec when Hash resolve_hash_connection spec end end def resolve_string_connection(spec) # Rails has historically accepted a string to mean either # an environment key or a URL spec, so we have deprecated # this ambiguous behaviour and in the future this function # can be removed in favor of resolve_url_connection. if configurations.key?(spec) || spec !~ /:/ ActiveSupport::Deprecation.warn(<<-MSG.squish) Passing a string to ActiveRecord::Base.establish_connection for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead. MSG resolve_symbol_connection(spec) else resolve_url_connection(spec) end end # Takes the environment such as +:production+ or +:development+. # This requires that the @configurations was initialized with a key that # matches. # # Resolver.new("production" => {}).resolve_symbol_connection(:production) # # => {} # def resolve_symbol_connection(spec) if config = configurations[spec.to_s] resolve_connection(config) else raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") end end # Accepts a hash. Expands the "url" key that contains a # URL database connection to a full connection # hash and merges with the rest of the hash. # Connection details inside of the "url" key win any merge conflicts def resolve_hash_connection(spec) if spec["url"] && spec["url"] !~ /^jdbc:/ connection_hash = resolve_url_connection(spec.delete("url")) spec.merge!(connection_hash) end spec end # Takes a connection URL. # # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # def resolve_url_connection(url) ConnectionUrlResolver.new(url).to_hash end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb000066400000000000000000000205011266740050600301700ustar00rootroot00000000000000require 'active_record/connection_adapters/abstract_mysql_adapter' gem 'mysql2', '>= 0.3.13', '< 0.5' require 'mysql2' module ActiveRecord module ConnectionHandling # :nodoc: # Establishes a connection to the database that's used by all Active Record objects. def mysql2_connection(config) config = config.symbolize_keys config[:username] = 'root' if config[:username].nil? if Mysql2::Client.const_defined? :FOUND_ROWS config[:flags] = Mysql2::Client::FOUND_ROWS end client = Mysql2::Client.new(config) options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0] ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config) rescue Mysql2::Error => error if error.message.include?("Unknown database") raise ActiveRecord::NoDatabaseError.new(error.message, error) else raise end end end module ConnectionAdapters class Mysql2Adapter < AbstractMysqlAdapter ADAPTER_NAME = 'Mysql2'.freeze def initialize(connection, logger, connection_options, config) super @prepared_statements = false configure_connection end MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191 def initialize_schema_migrations_table if charset == 'utf8mb4' ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4) else ActiveRecord::SchemaMigration.create_table end end def supports_explain? true end # HELPER METHODS =========================================== def each_hash(result) # :nodoc: if block_given? result.each(:as => :hash, :symbolize_keys => true) do |row| yield row end else to_enum(:each_hash, result) end end def error_number(exception) exception.error_number if exception.respond_to?(:error_number) end #-- # QUOTING ================================================== #++ def quote_string(string) @connection.escape(string) end def quoted_date(value) if value.acts_like?(:time) && value.respond_to?(:usec) "#{super}.#{sprintf("%06d", value.usec)}" else super end end #-- # CONNECTION MANAGEMENT ==================================== #++ def active? return false unless @connection @connection.ping end def reconnect! super disconnect! connect end alias :reset! :reconnect! # Disconnects from the database if already connected. # Otherwise, this method does nothing. def disconnect! super unless @connection.nil? @connection.close @connection = nil end end #-- # DATABASE STATEMENTS ====================================== #++ def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds.dup)}" start = Time.now result = exec_query(sql, 'EXPLAIN', binds) elapsed = Time.now - start ExplainPrettyPrinter.new.pp(result, elapsed) end class ExplainPrettyPrinter # :nodoc: # Pretty prints the result of a EXPLAIN in a way that resembles the output of the # MySQL shell: # # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ # 2 rows in set (0.00 sec) # # This is an exercise in Ruby hyperrealism :). def pp(result, elapsed) widths = compute_column_widths(result) separator = build_separator(widths) pp = [] pp << separator pp << build_cells(result.columns, widths) pp << separator result.rows.each do |row| pp << build_cells(row, widths) end pp << separator pp << build_footer(result.rows.length, elapsed) pp.join("\n") + "\n" end private def compute_column_widths(result) [].tap do |widths| result.columns.each_with_index do |column, i| cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} widths << cells_in_column.map(&:length).max end end end def build_separator(widths) padding = 1 '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' end def build_cells(items, widths) cells = [] items.each_with_index do |item, i| item = 'NULL' if item.nil? justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' cells << item.to_s.send(justifier, widths[i]) end '| ' + cells.join(' | ') + ' |' end def build_footer(nrows, elapsed) rows_label = nrows == 1 ? 'row' : 'rows' "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed end end # FIXME: re-enable the following once a "better" query_cache solution is in core # # The overrides below perform much better than the originals in AbstractAdapter # because we're able to take advantage of mysql2's lazy-loading capabilities # # # Returns a record hash with the column names as keys and column values # # as values. # def select_one(sql, name = nil) # result = execute(sql, name) # result.each(as: :hash) do |r| # return r # end # end # # # Returns a single value from a record # def select_value(sql, name = nil) # result = execute(sql, name) # if first = result.first # first.first # end # end # # # Returns an array of the values of the first column in a select: # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] # def select_values(sql, name = nil) # execute(sql, name).map { |row| row.first } # end # Returns an array of arrays containing the field values. # Order is the same as that returned by +columns+. def select_rows(sql, name = nil, binds = []) execute(sql, name).to_a end # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) if @connection # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been # made since we established the connection @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone end super end def exec_query(sql, name = 'SQL', binds = []) result = execute(sql, name) ActiveRecord::Result.new(result.fields, result.to_a) end alias exec_without_stmt exec_query def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) super id_value || @connection.last_id end alias :create :insert_sql def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) execute to_sql(sql, binds), name end def exec_delete(sql, name, binds) execute to_sql(sql, binds), name @connection.affected_rows end alias :exec_update :exec_delete def last_inserted_id(result) @connection.last_id end private def connect @connection = Mysql2::Client.new(@config) configure_connection end def configure_connection @connection.query_options.merge!(:as => :array) super end def full_version @full_version ||= @connection.server_info[:version] end def set_field_encoding field_name field_name end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb000066400000000000000000000374611266740050600301230ustar00rootroot00000000000000require 'active_record/connection_adapters/abstract_mysql_adapter' require 'active_record/connection_adapters/statement_pool' require 'active_support/core_ext/hash/keys' gem 'mysql', '~> 2.9' require 'mysql' class Mysql class Time def to_date Date.new(year, month, day) end end class Stmt; include Enumerable end class Result; include Enumerable end end module ActiveRecord module ConnectionHandling # :nodoc: # Establishes a connection to the database that's used by all Active Record objects. def mysql_connection(config) config = config.symbolize_keys host = config[:host] port = config[:port] socket = config[:socket] username = config[:username] ? config[:username].to_s : 'root' password = config[:password].to_s database = config[:database] mysql = Mysql.init mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey] default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0 default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS) options = [host, username, password, database, port, socket, default_flags] ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config) rescue Mysql::Error => error if error.message.include?("Unknown database") raise ActiveRecord::NoDatabaseError.new(error.message, error) else raise end end end module ConnectionAdapters # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/). # # Options: # # * :host - Defaults to "localhost". # * :port - Defaults to 3306. # * :socket - Defaults to "/tmp/mysql.sock". # * :username - Defaults to "root" # * :password - Defaults to nothing. # * :database - The name of the database. No default, must be provided. # * :encoding - (Optional) Sets the client encoding by executing "SET NAMES " after connection. # * :reconnect - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html). # * :strict - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/sql-mode.html) # * :variables - (Optional) A hash session variables to send as SET @@SESSION.key = value on each database connection. Use the value +:default+ to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html). # * :sslca - Necessary to use MySQL with an SSL connection. # * :sslkey - Necessary to use MySQL with an SSL connection. # * :sslcert - Necessary to use MySQL with an SSL connection. # * :sslcapath - Necessary to use MySQL with an SSL connection. # * :sslcipher - Necessary to use MySQL with an SSL connection. # class MysqlAdapter < AbstractMysqlAdapter ADAPTER_NAME = 'MySQL'.freeze class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max = 1000) super @cache = Hash.new { |h,pid| h[pid] = {} } end def each(&block); cache.each(&block); end def key?(key); cache.key?(key); end def [](key); cache[key]; end def length; cache.length; end def delete(key); cache.delete(key); end def []=(sql, key) while @max <= cache.size cache.shift.last[:stmt].close end cache[sql] = key end def clear cache.each_value do |hash| hash[:stmt].close end cache.clear end private def cache @cache[Process.pid] end end def initialize(connection, logger, connection_options, config) super @statements = StatementPool.new(@connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @client_encoding = nil connect end # Returns true, since this connection adapter supports prepared statement # caching. def supports_statement_cache? true end # HELPER METHODS =========================================== def each_hash(result) # :nodoc: if block_given? result.each_hash do |row| row.symbolize_keys! yield row end else to_enum(:each_hash, result) end end def error_number(exception) # :nodoc: exception.errno if exception.respond_to?(:errno) end # QUOTING ================================================== def quote_string(string) #:nodoc: @connection.quote(string) end #-- # CONNECTION MANAGEMENT ==================================== #++ def active? if @connection.respond_to?(:stat) @connection.stat else @connection.query 'select 1' end # mysql-ruby doesn't raise an exception when stat fails. if @connection.respond_to?(:errno) @connection.errno.zero? else true end rescue Mysql::Error false end def reconnect! super disconnect! connect end # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! super @connection.close rescue nil end def reset! if @connection.respond_to?(:change_user) # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to # reset the connection is to change the user to the same user. @connection.change_user(@config[:username], @config[:password], @config[:database]) configure_connection end end #-- # DATABASE STATEMENTS ====================================== #++ def select_rows(sql, name = nil, binds = []) @connection.query_with_result = true rows = exec_query(sql, name, binds).rows @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end # Clears the prepared statements cache. def clear_cache! super @statements.clear end # Taken from here: # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb # Author: TOMITA Masahiro ENCODINGS = { "armscii8" => nil, "ascii" => Encoding::US_ASCII, "big5" => Encoding::Big5, "binary" => Encoding::ASCII_8BIT, "cp1250" => Encoding::Windows_1250, "cp1251" => Encoding::Windows_1251, "cp1256" => Encoding::Windows_1256, "cp1257" => Encoding::Windows_1257, "cp850" => Encoding::CP850, "cp852" => Encoding::CP852, "cp866" => Encoding::IBM866, "cp932" => Encoding::Windows_31J, "dec8" => nil, "eucjpms" => Encoding::EucJP_ms, "euckr" => Encoding::EUC_KR, "gb2312" => Encoding::EUC_CN, "gbk" => Encoding::GBK, "geostd8" => nil, "greek" => Encoding::ISO_8859_7, "hebrew" => Encoding::ISO_8859_8, "hp8" => nil, "keybcs2" => nil, "koi8r" => Encoding::KOI8_R, "koi8u" => Encoding::KOI8_U, "latin1" => Encoding::ISO_8859_1, "latin2" => Encoding::ISO_8859_2, "latin5" => Encoding::ISO_8859_9, "latin7" => Encoding::ISO_8859_13, "macce" => Encoding::MacCentEuro, "macroman" => Encoding::MacRoman, "sjis" => Encoding::SHIFT_JIS, "swe7" => nil, "tis620" => Encoding::TIS_620, "ucs2" => Encoding::UTF_16BE, "ujis" => Encoding::EucJP_ms, "utf8" => Encoding::UTF_8, "utf8mb4" => Encoding::UTF_8, } # Get the client encoding for this database def client_encoding return @client_encoding if @client_encoding result = exec_query( "select @@character_set_client", 'SCHEMA') @client_encoding = ENCODINGS[result.rows.last.last] end def exec_query(sql, name = 'SQL', binds = []) if without_prepared_statement?(binds) result_set, affected_rows = exec_without_stmt(sql, name) else result_set, affected_rows = exec_stmt(sql, name, binds) end yield affected_rows if block_given? result_set end def last_inserted_id(result) @connection.insert_id end module Fields # :nodoc: class DateTime < Type::DateTime # :nodoc: def cast_value(value) if Mysql::Time === value new_time( value.year, value.month, value.day, value.hour, value.minute, value.second, value.second_part) else super end end end class Time < Type::Time # :nodoc: def cast_value(value) if Mysql::Time === value new_time( 2000, 01, 01, value.hour, value.minute, value.second, value.second_part) else super end end end class << self TYPES = Type::HashLookupTypeMap.new # :nodoc: delegate :register_type, :alias_type, to: :TYPES def find_type(field) if field.type == Mysql::Field::TYPE_TINY && field.length > 1 TYPES.lookup(Mysql::Field::TYPE_LONG) else TYPES.lookup(field.type) end end end register_type Mysql::Field::TYPE_TINY, Type::Boolean.new register_type Mysql::Field::TYPE_LONG, Type::Integer.new alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG register_type Mysql::Field::TYPE_DATE, Type::Date.new register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new register_type Mysql::Field::TYPE_TIME, Fields::Time.new register_type Mysql::Field::TYPE_FLOAT, Type::Float.new end def initialize_type_map(m) # :nodoc: super m.register_type %r(datetime)i, Fields::DateTime.new m.register_type %r(time)i, Fields::Time.new end def exec_without_stmt(sql, name = 'SQL') # :nodoc: # Some queries, like SHOW CREATE TABLE don't work through the prepared # statement API. For those queries, we need to use this method. :'( log(sql, name) do result = @connection.query(sql) affected_rows = @connection.affected_rows if result types = {} fields = [] result.fetch_fields.each { |field| field_name = field.name fields << field_name if field.decimals > 0 types[field_name] = Type::Decimal.new else types[field_name] = Fields.find_type field end } result_set = ActiveRecord::Result.new(fields, result.to_a, types) result.free else result_set = ActiveRecord::Result.new([], []) end [result_set, affected_rows] end end def execute_and_free(sql, name = nil) # :nodoc: result = execute(sql, name) ret = yield result result.free ret end def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: super sql, name id_value || @connection.insert_id end alias :create :insert_sql def exec_delete(sql, name, binds) # :nodoc: affected_rows = 0 exec_query(sql, name, binds) do |n| affected_rows = n end affected_rows end alias :exec_update :exec_delete def begin_db_transaction #:nodoc: exec_query "BEGIN" end private def exec_stmt(sql, name, binds) cache = {} type_casted_binds = binds.map { |col, val| [col, type_cast(val, col)] } log(sql, name, type_casted_binds) do if binds.empty? stmt = @connection.prepare(sql) else cache = @statements[sql] ||= { :stmt => @connection.prepare(sql) } stmt = cache[:stmt] end begin stmt.execute(*type_casted_binds.map { |_, val| val }) rescue Mysql::Error => e # Older versions of MySQL leave the prepared statement in a bad # place when an error occurs. To support older MySQL versions, we # need to close the statement and delete the statement from the # cache. stmt.close @statements.delete sql raise e end cols = nil if metadata = stmt.result_metadata cols = cache[:cols] ||= metadata.fetch_fields.map { |field| field.name } metadata.free end result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols affected_rows = stmt.affected_rows stmt.free_result stmt.close if binds.empty? [result_set, affected_rows] end end def connect encoding = @config[:encoding] if encoding @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil end if @config[:sslca] || @config[:sslkey] @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) end @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout] @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout] @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout] @connection.real_connect(*@connection_options) # reconnect must be set after real_connect is called, because real_connect sets it to false internally @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=) configure_connection end # Many Rails applications monkey-patch a replacement of the configure_connection method # and don't call 'super', so leave this here even though it looks superfluous. def configure_connection super end def select(sql, name = nil, binds = []) @connection.query_with_result = true rows = super @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end # Returns the full version of the connected MySQL server. def full_version @full_version ||= @connection.server_info end def set_field_encoding field_name field_name.force_encoding(client_encoding) if internal_enc = Encoding.default_internal field_name = field_name.encode!(internal_enc) end field_name end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/000077500000000000000000000000001266740050600271215ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb000066400000000000000000000052461266740050600321470ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module ArrayParser # :nodoc: DOUBLE_QUOTE = '"' BACKSLASH = "\\" COMMA = ',' BRACKET_OPEN = '{' BRACKET_CLOSE = '}' def parse_pg_array(string) # :nodoc: local_index = 0 array = [] while(local_index < string.length) case string[local_index] when BRACKET_OPEN local_index,array = parse_array_contents(array, string, local_index + 1) when BRACKET_CLOSE return array end local_index += 1 end array end private def parse_array_contents(array, string, index) is_escaping = false is_quoted = false was_quoted = false current_item = '' local_index = index while local_index token = string[local_index] if is_escaping current_item << token is_escaping = false else if is_quoted case token when DOUBLE_QUOTE is_quoted = false was_quoted = true when BACKSLASH is_escaping = true else current_item << token end else case token when BACKSLASH is_escaping = true when COMMA add_item_to_array(array, current_item, was_quoted) current_item = '' was_quoted = false when DOUBLE_QUOTE is_quoted = true when BRACKET_OPEN internal_items = [] local_index,internal_items = parse_array_contents(internal_items, string, local_index + 1) array.push(internal_items) when BRACKET_CLOSE add_item_to_array(array, current_item, was_quoted) return local_index,array else current_item << token end end end local_index += 1 end return local_index,array end def add_item_to_array(array, current_item, quoted) return if !quoted && current_item.length == 0 if !quoted && current_item == 'NULL' array.push nil else array.push current_item end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/column.rb000066400000000000000000000011431266740050600307420ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: attr_accessor :array def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil) if sql_type =~ /\[\]$/ @array = true super(name, default, cast_type, sql_type[0..sql_type.length - 3], null) else @array = false super(name, default, cast_type, sql_type, null) end @default_function = default_function end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb000066400000000000000000000174741266740050600334760ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module DatabaseStatements def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) end class ExplainPrettyPrinter # :nodoc: # Pretty prints the result of a EXPLAIN in a way that resembles the output of the # PostgreSQL shell: # # QUERY PLAN # ------------------------------------------------------------------------------ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) # Join Filter: (posts.user_id = users.id) # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) # Index Cond: (id = 1) # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) # Filter: (posts.user_id = 1) # (6 rows) # def pp(result) header = result.columns.first lines = result.rows.map(&:first) # We add 2 because there's one char of padding at both sides, note # the extra hyphens in the example above. width = [header, *lines].map(&:length).max + 2 pp = [] pp << header.center(width).rstrip pp << '-' * width pp += lines.map {|line| " #{line}"} nrows = result.rows.length rows_label = nrows == 1 ? 'row' : 'rows' pp << "(#{nrows} #{rows_label})" pp.join("\n") + "\n" end end def select_value(arel, name = nil, binds = []) arel, binds = binds_from_relation arel, binds sql = to_sql(arel, binds) execute_and_clear(sql, name, binds) do |result| result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0 end end def select_values(arel, name = nil) arel, binds = binds_from_relation arel, [] sql = to_sql(arel, binds) execute_and_clear(sql, name, binds) do |result| if result.nfields > 0 result.column_values(0) else [] end end end # Executes a SELECT query and returns an array of rows. Each row is an # array of field values. def select_rows(sql, name = nil, binds = []) execute_and_clear(sql, name, binds) do |result| result.values end end # Executes an INSERT query and returns the new record's ID def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) unless pk # Extract the table from the insert sql. Yuck. table_ref = extract_table_ref_from_insert_sql(sql) pk = primary_key(table_ref) if table_ref end if pk && use_insert_returning? select_value("#{sql} RETURNING #{quote_column_name(pk)}") elsif pk super last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk)) else super end end def create super.insert end # The internal PostgreSQL identifier of the money data type. MONEY_COLUMN_TYPE_OID = 790 #:nodoc: # The internal PostgreSQL identifier of the BYTEA data type. BYTEA_COLUMN_TYPE_OID = 17 #:nodoc: # create a 2D array representing the result set def result_as_array(res) #:nodoc: # check if we have any binary column and if they need escaping ftypes = Array.new(res.nfields) do |i| [i, res.ftype(i)] end rows = res.values return rows unless ftypes.any? { |_, x| x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID } typehash = ftypes.group_by { |_, type| type } binaries = typehash[BYTEA_COLUMN_TYPE_OID] || [] monies = typehash[MONEY_COLUMN_TYPE_OID] || [] rows.each do |row| # unescape string passed BYTEA field (OID == 17) binaries.each do |index, _| row[index] = unescape_bytea(row[index]) end # If this is a money type column and there are any currency symbols, # then strip them off. Indeed it would be prettier to do this in # PostgreSQLColumn.string_to_decimal but would break form input # fields that call value_before_type_cast. monies.each do |index, _| data = row[index] # Because money output is formatted according to the locale, there are two # cases to consider (note the decimal separators): # (1) $12,345,678.12 # (2) $12.345.678,12 case data when /^-?\D+[\d,]+\.\d{2}$/ # (1) data.gsub!(/[^-\d.]/, '') when /^-?\D+[\d.]+,\d{2}$/ # (2) data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') end end end end # Queries the database and returns the results in an Array-like object def query(sql, name = nil) #:nodoc: log(sql, name) do result_as_array @connection.async_exec(sql) end end # Executes an SQL statement, returning a PGresult object on success # or raising a PGError exception otherwise. def execute(sql, name = nil) log(sql, name) do @connection.async_exec(sql) end end def exec_query(sql, name = 'SQL', binds = []) execute_and_clear(sql, name, binds) do |result| types = {} fields = result.fields fields.each_with_index do |fname, i| ftype = result.ftype i fmod = result.fmod i types[fname] = get_oid_type(ftype, fmod, fname) end ActiveRecord::Result.new(fields, result.values, types) end end def exec_delete(sql, name = 'SQL', binds = []) execute_and_clear(sql, name, binds) {|result| result.cmd_tuples } end alias :exec_update :exec_delete def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk # Extract the table from the insert sql. Yuck. table_ref = extract_table_ref_from_insert_sql(sql) pk = primary_key(table_ref) if table_ref end if pk && use_insert_returning? sql = "#{sql} RETURNING #{quote_column_name(pk)}" end [sql, binds] end def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) val = exec_query(sql, name, binds) if !use_insert_returning? && pk unless sequence_name table_ref = extract_table_ref_from_insert_sql(sql) sequence_name = default_sequence_name(table_ref, pk) return val unless sequence_name end last_insert_id_result(sequence_name) else val end end # Executes an UPDATE query and returns the number of affected tuples. def update_sql(sql, name = nil) super.cmd_tuples end # Begins a transaction. def begin_db_transaction execute "BEGIN" end def begin_isolated_db_transaction(isolation) begin_db_transaction execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" end # Commits a transaction. def commit_db_transaction execute "COMMIT" end # Aborts a transaction. def exec_rollback_db_transaction execute "ROLLBACK" end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb000066400000000000000000000033751266740050600302310ustar00rootroot00000000000000require 'active_record/connection_adapters/postgresql/oid/infinity' require 'active_record/connection_adapters/postgresql/oid/array' require 'active_record/connection_adapters/postgresql/oid/bit' require 'active_record/connection_adapters/postgresql/oid/bit_varying' require 'active_record/connection_adapters/postgresql/oid/bytea' require 'active_record/connection_adapters/postgresql/oid/cidr' require 'active_record/connection_adapters/postgresql/oid/date' require 'active_record/connection_adapters/postgresql/oid/date_time' require 'active_record/connection_adapters/postgresql/oid/decimal' require 'active_record/connection_adapters/postgresql/oid/enum' require 'active_record/connection_adapters/postgresql/oid/float' require 'active_record/connection_adapters/postgresql/oid/hstore' require 'active_record/connection_adapters/postgresql/oid/inet' require 'active_record/connection_adapters/postgresql/oid/integer' require 'active_record/connection_adapters/postgresql/oid/json' require 'active_record/connection_adapters/postgresql/oid/jsonb' require 'active_record/connection_adapters/postgresql/oid/money' require 'active_record/connection_adapters/postgresql/oid/point' require 'active_record/connection_adapters/postgresql/oid/range' require 'active_record/connection_adapters/postgresql/oid/specialized_string' require 'active_record/connection_adapters/postgresql/oid/time' require 'active_record/connection_adapters/postgresql/oid/uuid' require 'active_record/connection_adapters/postgresql/oid/vector' require 'active_record/connection_adapters/postgresql/oid/xml' require 'active_record/connection_adapters/postgresql/oid/type_map_initializer' module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/000077500000000000000000000000001266740050600276745ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb000066400000000000000000000057441266740050600313510ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Array < Type::Value # :nodoc: include Type::Mutable # Loads pg_array_parser if available. String parsing can be # performed quicker by a native extension, which will not create # a large amount of Ruby objects that will need to be garbage # collected. pg_array_parser has a C and Java extension begin require 'pg_array_parser' include PgArrayParser rescue LoadError require 'active_record/connection_adapters/postgresql/array_parser' include PostgreSQL::ArrayParser end attr_reader :subtype, :delimiter delegate :type, :limit, to: :subtype def initialize(subtype, delimiter = ',') @subtype = subtype @delimiter = delimiter end def type_cast_from_database(value) if value.is_a?(::String) type_cast_array(parse_pg_array(value), :type_cast_from_database) else super end end def type_cast_from_user(value) if value.is_a?(::String) value = parse_pg_array(value) end type_cast_array(value, :type_cast_from_user) end def type_cast_for_database(value) if value.is_a?(::Array) cast_value_for_database(value) else super end end private def type_cast_array(value, method) if value.is_a?(::Array) value.map { |item| type_cast_array(item, method) } else @subtype.public_send(method, value) end end def cast_value_for_database(value) if value.is_a?(::Array) casted_values = value.map { |item| cast_value_for_database(item) } "{#{casted_values.join(delimiter)}}" else quote_and_escape(subtype.type_cast_for_database(value)) end end ARRAY_ESCAPE = "\\" * 2 * 2 # escape the backslash twice for PG arrays def quote_and_escape(value) case value when ::String if string_requires_quoting?(value) value = value.gsub(/\\/, ARRAY_ESCAPE) value.gsub!(/"/,"\\\"") %("#{value}") else value end when nil then "NULL" else value end end # See http://www.postgresql.org/docs/9.2/static/arrays.html#ARRAYS-IO # for a list of all cases in which strings will be quoted. def string_requires_quoting?(string) string.empty? || string == "NULL" || string =~ /[\{\}"\\\s]/ || string.include?(delimiter) end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb000066400000000000000000000020411266740050600307740ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Bit < Type::Value # :nodoc: def type :bit end def type_cast(value) if ::String === value case value when /^0x/i value[2..-1].hex.to_s(2) # Hexadecimal notation else value # Bit-string notation end else value end end def type_cast_for_database(value) Data.new(super) if value end class Data def initialize(value) @value = value end def to_s value end def binary? /\A[01]*\Z/ === value end def hex? /\A[0-9A-F]*\Z/i === value end protected attr_reader :value end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb000066400000000000000000000003611266740050600325360ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class BitVarying < OID::Bit # :nodoc: def type :bit_varying end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb000066400000000000000000000005741266740050600313330ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Bytea < Type::Binary # :nodoc: def type_cast_from_database(value) return if value.nil? return value.to_s if value.is_a?(Type::Binary::Data) PGconn.unescape_bytea(super) end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb000066400000000000000000000021471266740050600311460ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Cidr < Type::Value # :nodoc: def type :cidr end def type_cast_for_schema(value) subnet_mask = value.instance_variable_get(:@mask_addr) # If the subnet mask is equal to /32, don't output it if subnet_mask == (2**32 - 1) "\"#{value}\"" else "\"#{value}/#{subnet_mask.to_s(2).count('1')}\"" end end def type_cast_for_database(value) if IPAddr === value "#{value}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}" else value end end def cast_value(value) if value.nil? nil elsif String === value begin IPAddr.new(value) rescue ArgumentError nil end else value end end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb000066400000000000000000000003161266740050600311360ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Date < Type::Date # :nodoc: include Infinity end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb000066400000000000000000000017721266740050600321630ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class DateTime < Type::DateTime # :nodoc: include Infinity def type_cast_for_database(value) if has_precision? && value.acts_like?(:time) && value.year <= 0 bce_year = format("%04d", -value.year + 1) super.sub(/^-?\d+/, bce_year) + " BC" else super end end def cast_value(value) if value.is_a?(::String) case value when 'infinity' then ::Float::INFINITY when '-infinity' then -::Float::INFINITY when / BC$/ astronomical_year = format("%04d", -value[/^\d+/].to_i + 1) super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year)) else super end else value end end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb000066400000000000000000000004631266740050600316220ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Decimal < Type::Decimal # :nodoc: def infinity(options = {}) BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1) end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb000066400000000000000000000005001266740050600311600ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Enum < Type::Value # :nodoc: def type :enum end private def cast_value(value) value.to_s end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb000066400000000000000000000010231266740050600313220ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Float < Type::Float # :nodoc: include Infinity def cast_value(value) case value when ::Float then value when 'Infinity' then ::Float::INFINITY when '-Infinity' then -::Float::INFINITY when 'NaN' then ::Float::NAN else value.to_f end end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb000066400000000000000000000030571266740050600315320ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Hstore < Type::Value # :nodoc: include Type::Mutable def type :hstore end def type_cast_from_database(value) if value.is_a?(::String) ::Hash[value.scan(HstorePair).map { |k, v| v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') [k, v] }] else value end end def type_cast_for_database(value) if value.is_a?(::Hash) value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(', ') else value end end def accessor ActiveRecord::Store::StringKeyedHashAccessor end private HstorePair = begin quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ end def escape_hstore(value) if value.nil? 'NULL' else if value == "" '""' else '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') end end end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb000066400000000000000000000003401266740050600311550ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Inet < Cidr # :nodoc: def type :inet end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb000066400000000000000000000004461266740050600320560ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: module Infinity # :nodoc: def infinity(options = {}) options[:negative] ? -::Float::INFINITY : ::Float::INFINITY end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb000066400000000000000000000003241266740050600316550ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Integer < Type::Integer # :nodoc: include Infinity end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb000066400000000000000000000014271266740050600311760ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Json < Type::Value # :nodoc: include Type::Mutable def type :json end def type_cast_from_database(value) if value.is_a?(::String) ::ActiveSupport::JSON.decode(value) rescue nil else super end end def type_cast_for_database(value) if value.is_a?(::Array) || value.is_a?(::Hash) ::ActiveSupport::JSON.encode(value) else super end end def accessor ActiveRecord::Store::StringKeyedHashAccessor end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb000066400000000000000000000014261266740050600313370ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Jsonb < Json # :nodoc: def type :jsonb end def changed_in_place?(raw_old_value, new_value) # Postgres does not preserve insignificant whitespaces when # roundtripping jsonb columns. This causes some false positives for # the comparison here. Therefore, we need to parse and re-dump the # raw value here to ensure the insignificant whitespaces are # consistent with our encoder's output. raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value)) super(raw_old_value, new_value) end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb000066400000000000000000000020741266740050600313530ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Money < Type::Decimal # :nodoc: include Infinity class_attribute :precision def type :money end def scale 2 end def cast_value(value) return value unless ::String === value # Because money output is formatted according to the locale, there are two # cases to consider (note the decimal separators): # (1) $12,345,678.12 # (2) $12.345.678,12 # Negative values are represented as follows: # (3) -$2.55 # (4) ($2.55) value.sub!(/^\((.+)\)$/, '-\1') # (4) case value when /^-?\D+[\d,]+\.\d{2}$/ # (1) value.gsub!(/[^-\d.]/, '') when /^-?\D+[\d.]+,\d{2}$/ # (2) value.gsub!(/[^-\d,]/, '').sub!(/,/, '.') end super(value) end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb000066400000000000000000000017131266740050600313540ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Point < Type::Value # :nodoc: include Type::Mutable def type :point end def type_cast(value) case value when ::String if value[0] == '(' && value[-1] == ')' value = value[1...-1] end type_cast(value.split(',')) when ::Array value.map { |v| Float(v) } else value end end def type_cast_for_database(value) if value.is_a?(::Array) "(#{number_for_point(value[0])},#{number_for_point(value[1])})" else super end end private def number_for_point(number) number.to_s.gsub(/\.0$/, '') end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb000066400000000000000000000050701266740050600313170ustar00rootroot00000000000000require 'active_support/core_ext/string/filters' module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Range < Type::Value # :nodoc: attr_reader :subtype, :type def initialize(subtype, type) @subtype = subtype @type = type end def type_cast_for_schema(value) value.inspect.gsub('Infinity', '::Float::INFINITY') end def cast_value(value) return if value == 'empty' return value if value.is_a?(::Range) extracted = extract_bounds(value) from = type_cast_single extracted[:from] to = type_cast_single extracted[:to] if !infinity?(from) && extracted[:exclude_start] if from.respond_to?(:succ) from = from.succ ActiveSupport::Deprecation.warn(<<-MSG.squish) Excluding the beginning of a Range is only partialy supported through `#succ`. This is not reliable and will be removed in the future. MSG else raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" end end ::Range.new(from, to, extracted[:exclude_end]) end def type_cast_for_database(value) if value.is_a?(::Range) from = type_cast_single_for_database(value.begin) to = type_cast_single_for_database(value.end) "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}" else super end end private def type_cast_single(value) infinity?(value) ? value : @subtype.type_cast_from_database(value) end def type_cast_single_for_database(value) infinity?(value) ? '' : @subtype.type_cast_for_database(value) end def extract_bounds(value) from, to = value[1..-2].split(',') { from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from, to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to, exclude_start: (value[0] == '('), exclude_end: (value[-1] == ')') } end def infinity?(value) value.respond_to?(:infinite?) && value.infinite? end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb000066400000000000000000000005321266740050600341030ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class SpecializedString < Type::String # :nodoc: attr_reader :type def initialize(type) @type = type end def text? false end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb000066400000000000000000000003161266740050600311570ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Time < Type::Time # :nodoc: include Infinity end end end end end type_map_initializer.rb000066400000000000000000000077431266740050600343760ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oidmodule ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: # This class uses the data from PostgreSQL pg_type table to build # the OID -> Type mapping. # - OID is an integer representing the type. # - Type is an OID::Type object. # This class has side effects on the +store+ passed during initialization. class TypeMapInitializer # :nodoc: def initialize(store) @store = store end def run(records) nodes = records.reject { |row| @store.key? row['oid'].to_i } mapped, nodes = nodes.partition { |row| @store.key? row['typname'] } ranges, nodes = nodes.partition { |row| row['typtype'] == 'r'.freeze } enums, nodes = nodes.partition { |row| row['typtype'] == 'e'.freeze } domains, nodes = nodes.partition { |row| row['typtype'] == 'd'.freeze } arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in'.freeze } composites, nodes = nodes.partition { |row| row['typelem'].to_i != 0 } mapped.each { |row| register_mapped_type(row) } enums.each { |row| register_enum_type(row) } domains.each { |row| register_domain_type(row) } arrays.each { |row| register_array_type(row) } ranges.each { |row| register_range_type(row) } composites.each { |row| register_composite_type(row) } end def query_conditions_for_initial_load(type_map) known_type_names = type_map.keys.map { |n| "'#{n}'" } known_type_types = %w('r' 'e' 'd') <<-SQL % [known_type_names.join(", "), known_type_types.join(", ")] WHERE t.typname IN (%s) OR t.typtype IN (%s) OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure OR t.typelem != 0 SQL end private def register_mapped_type(row) alias_type row['oid'], row['typname'] end def register_enum_type(row) register row['oid'], OID::Enum.new end def register_array_type(row) register_with_subtype(row['oid'], row['typelem'].to_i) do |subtype| OID::Array.new(subtype, row['typdelim']) end end def register_range_type(row) register_with_subtype(row['oid'], row['rngsubtype'].to_i) do |subtype| OID::Range.new(subtype, row['typname'].to_sym) end end def register_domain_type(row) if base_type = @store.lookup(row["typbasetype"].to_i) register row['oid'], base_type else warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." end end def register_composite_type(row) if subtype = @store.lookup(row['typelem'].to_i) register row['oid'], OID::Vector.new(row['typdelim'], subtype) end end def register(oid, oid_type = nil, &block) oid = assert_valid_registration(oid, oid_type || block) if block_given? @store.register_type(oid, &block) else @store.register_type(oid, oid_type) end end def alias_type(oid, target) oid = assert_valid_registration(oid, target) @store.alias_type(oid, target) end def register_with_subtype(oid, target_oid) if @store.key?(target_oid) register(oid) do |_, *args| yield @store.lookup(target_oid, *args) end end end def assert_valid_registration(oid, oid_type) raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? oid.to_i end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb000066400000000000000000000007141266740050600311710ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Uuid < Type::Value # :nodoc: ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x alias_method :type_cast_for_database, :type_cast_from_database def type :uuid end def type_cast(value) value.to_s[ACCEPTABLE_UUID, 0] end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb000066400000000000000000000014001266740050600315160ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Vector < Type::Value # :nodoc: attr_reader :delim, :subtype # +delim+ corresponds to the `typdelim` column in the pg_types # table. +subtype+ is derived from the `typelem` column in the # pg_types table. def initialize(delim, subtype) @delim = delim @subtype = subtype end # FIXME: this should probably split on +delim+ and use +subtype+ # to cast the values. Unfortunately, the current Rails behavior # is to just return the string. def type_cast(value) value end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb000066400000000000000000000010251266740050600310170ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Xml < Type::String # :nodoc: def type :xml end def type_cast_for_database(value) return unless value Data.new(super) end class Data # :nodoc: def initialize(value) @value = value end def to_s @value end end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb000066400000000000000000000061341266740050600311400ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module Quoting # Escapes binary strings for bytea input to the database. def escape_bytea(value) @connection.escape_bytea(value) if value end # Unescapes bytea output from a database to the binary string it represents. # NOTE: This is NOT an inverse of escape_bytea! This is only to be used # on escaped binary output from database drive. def unescape_bytea(value) @connection.unescape_bytea(value) if value end # Quotes strings for use in SQL input. def quote_string(s) #:nodoc: @connection.escape(s) end # Checks the following cases: # # - table_name # - "table.name" # - schema_name.table_name # - schema_name."table.name" # - "schema.name".table_name # - "schema.name"."table.name" def quote_table_name(name) Utils.extract_schema_qualified_name(name.to_s).quoted end def quote_table_name_for_assignment(table, attr) quote_column_name(attr) end # Quotes column names for use in SQL queries. def quote_column_name(name) #:nodoc: PGconn.quote_ident(name.to_s) end # Quote date/time values for use in SQL input. Includes microseconds # if the value is a Time responding to usec. def quoted_date(value) #:nodoc: result = super if value.acts_like?(:time) && value.respond_to?(:usec) result = "#{result}.#{sprintf("%06d", value.usec)}" end if value.year <= 0 bce_year = format("%04d", -value.year + 1) result = result.sub(/^-?\d+/, bce_year) + " BC" end result end # Does not quote function default values for UUID columns def quote_default_value(value, column) #:nodoc: if column.type == :uuid && value =~ /\(\)/ value else quote(value, column) end end private def _quote(value) case value when Type::Binary::Data "'#{escape_bytea(value.to_s)}'" when OID::Xml::Data "xml '#{quote_string(value.to_s)}'" when OID::Bit::Data if value.binary? "B'#{value}'" elsif value.hex? "X'#{value}'" end when Float if value.infinite? || value.nan? "'#{value}'" else super end else super end end def _type_cast(value) case value when Type::Binary::Data # Return a bind param hash with format as binary. # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc # for more information { value: value.to_s, format: 1 } when OID::Xml::Data, OID::Bit::Data value.to_s else super end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb000066400000000000000000000020231266740050600340410ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module ReferentialIntegrity # :nodoc: def supports_disable_referential_integrity? # :nodoc: true end def disable_referential_integrity # :nodoc: if supports_disable_referential_integrity? begin execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) rescue execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";")) end end yield ensure if supports_disable_referential_integrity? begin execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) rescue execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";")) end end end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb000066400000000000000000000104561266740050600333070ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL module ColumnMethods def xml(*args) options = args.extract_options! column(args[0], :xml, options) end def tsvector(*args) options = args.extract_options! column(args[0], :tsvector, options) end def int4range(name, options = {}) column(name, :int4range, options) end def int8range(name, options = {}) column(name, :int8range, options) end def tsrange(name, options = {}) column(name, :tsrange, options) end def tstzrange(name, options = {}) column(name, :tstzrange, options) end def numrange(name, options = {}) column(name, :numrange, options) end def daterange(name, options = {}) column(name, :daterange, options) end def hstore(name, options = {}) column(name, :hstore, options) end def ltree(name, options = {}) column(name, :ltree, options) end def inet(name, options = {}) column(name, :inet, options) end def cidr(name, options = {}) column(name, :cidr, options) end def macaddr(name, options = {}) column(name, :macaddr, options) end def uuid(name, options = {}) column(name, :uuid, options) end def json(name, options = {}) column(name, :json, options) end def jsonb(name, options = {}) column(name, :jsonb, options) end def citext(name, options = {}) column(name, :citext, options) end def point(name, options = {}) column(name, :point, options) end def bit(name, options = {}) column(name, :bit, options) end def bit_varying(name, options = {}) column(name, :bit_varying, options) end def money(name, options = {}) column(name, :money, options) end end class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition attr_accessor :array end class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods # Defines the primary key field. # Use of the native PostgreSQL UUID type is supported, and can be used # by defining your tables as such: # # create_table :stuffs, id: :uuid do |t| # t.string :content # t.timestamps # end # # By default, this will use the +uuid_generate_v4()+ function from the # +uuid-ossp+ extension, which MUST be enabled on your database. To enable # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can # set the +:default+ option to +nil+: # # create_table :stuffs, id: false do |t| # t.primary_key :id, :uuid, default: nil # t.uuid :foo_id # t.timestamps # end # # You may also pass a different UUID generation function from +uuid-ossp+ # or another library. # # Note that setting the UUID primary key default value to +nil+ will # require you to assure that you always provide a UUID value before saving # a record (as primary keys cannot be +nil+). This might be done via the # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. def primary_key(name, type = :primary_key, options = {}) return super unless type == :uuid options[:default] = options.fetch(:default, 'uuid_generate_v4()') options[:primary_key] = true column name, type, options end def new_column_definition(name, type, options) # :nodoc: column = super column.array = options[:array] column end private def create_column_definition(name, type) PostgreSQL::ColumnDefinition.new name, type end end class Table < ActiveRecord::ConnectionAdapters::Table include ColumnMethods end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb000066400000000000000000000571631266740050600331710ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL class SchemaCreation < AbstractAdapter::SchemaCreation private def visit_ColumnDefinition(o) sql = super if o.primary_key? && o.type != :primary_key sql << " PRIMARY KEY " add_column_options!(sql, column_options(o)) end sql end def add_column_options!(sql, options) if options[:array] || options[:column].try(:array) sql << '[]' end column = options.fetch(:column) { return super } if column.type == :uuid && options[:default] =~ /\(\)/ sql << " DEFAULT #{options[:default]}" else super end end def type_for_column(column) if column.array @conn.lookup_cast_type("#{column.sql_type}[]") else super end end end module SchemaStatements # Drops the database specified on the +name+ attribute # and creates it again using the provided +options+. def recreate_database(name, options = {}) #:nodoc: drop_database(name) create_database(name, options) end # Create a new PostgreSQL database. Options include :owner, :template, # :encoding (defaults to utf8), :collation, :ctype, # :tablespace, and :connection_limit (note that MySQL uses # :charset while PostgreSQL uses :encoding). # # Example: # create_database config[:database], config # create_database 'foo_development', encoding: 'unicode' def create_database(name, options = {}) options = { encoding: 'utf8' }.merge!(options.symbolize_keys) option_string = options.inject("") do |memo, (key, value)| memo += case key when :owner " OWNER = \"#{value}\"" when :template " TEMPLATE = \"#{value}\"" when :encoding " ENCODING = '#{value}'" when :collation " LC_COLLATE = '#{value}'" when :ctype " LC_CTYPE = '#{value}'" when :tablespace " TABLESPACE = \"#{value}\"" when :connection_limit " CONNECTION LIMIT = #{value}" else "" end end execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}" end # Drops a PostgreSQL database. # # Example: # drop_database 'matt_development' def drop_database(name) #:nodoc: execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" end # Returns the list of all tables in the schema search path. def tables(name = nil) query(<<-SQL, 'SCHEMA').map { |row| row[0] } SELECT tablename FROM pg_tables WHERE schemaname = ANY (current_schemas(false)) SQL end def data_sources # :nodoc select_values(<<-SQL, 'SCHEMA') SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view AND n.nspname = ANY (current_schemas(false)) SQL end # Returns true if table exists. # If the schema is not specified as part of +name+ then it will only find tables within # the current schema search path (regardless of permissions to access tables in other schemas) def table_exists?(name) name = Utils.extract_schema_qualified_name(name.to_s) return false unless name.identifier exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 SELECT COUNT(*) FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view AND c.relname = '#{name.identifier}' AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'} SQL end alias data_source_exists? table_exists? def drop_table(table_name, options = {}) execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end # Returns true if schema exists. def schema_exists?(name) exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}' SQL end def index_name_exists?(table_name, index_name, default) exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 SELECT COUNT(*) FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid INNER JOIN pg_class i ON d.indexrelid = i.oid WHERE i.relkind = 'i' AND i.relname = '#{index_name}' AND t.relname = '#{table_name}' AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) ) SQL end # Returns an array of indexes for the given table. def indexes(table_name, name = nil) result = query(<<-SQL, 'SCHEMA') SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid INNER JOIN pg_class i ON d.indexrelid = i.oid WHERE i.relkind = 'i' AND d.indisprimary = 'f' AND t.relname = '#{table_name}' AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) ) ORDER BY i.relname SQL result.map do |row| index_name = row[0] unique = row[1] == 't' indkey = row[2].split(" ") inddef = row[3] oid = row[4] columns = Hash[query(<<-SQL, "SCHEMA")] SELECT a.attnum, a.attname FROM pg_attribute a WHERE a.attrelid = #{oid} AND a.attnum IN (#{indkey.join(",")}) SQL column_names = columns.values_at(*indkey).compact unless column_names.empty? # add info on sort order for columns (only desc order is explicitly specified, asc is the default) desc_order_columns = inddef.scan(/(\w+) DESC/).flatten orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {} where = inddef.scan(/WHERE (.+)$/).flatten[0] using = inddef.scan(/USING (.+?) /).flatten[0].to_sym IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using) end end.compact end # Returns the list of all column definitions for a table. def columns(table_name) # Limit, precision, and scale are all handled by the superclass. column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod| oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type) default_value = extract_value_from_default(oid, default) default_function = extract_default_function(default_value, default) new_column(column_name, default_value, oid, type, notnull == 'f', default_function) end end def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc: PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function) end # Returns the current database name. def current_database query('select current_database()', 'SCHEMA')[0][0] end # Returns the current schema name. def current_schema query('SELECT current_schema', 'SCHEMA')[0][0] end # Returns the current database encoding format. def encoding query(<<-end_sql, 'SCHEMA')[0][0] SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' end_sql end # Returns the current database collation. def collation query(<<-end_sql, 'SCHEMA')[0][0] SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' end_sql end # Returns the current database ctype. def ctype query(<<-end_sql, 'SCHEMA')[0][0] SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' end_sql end # Returns an array of schema names. def schema_names query(<<-SQL, 'SCHEMA').flatten SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' AND nspname NOT IN ('information_schema') ORDER by nspname; SQL end # Creates a schema for the given schema name. def create_schema schema_name execute "CREATE SCHEMA #{schema_name}" end # Drops the schema for the given schema name. def drop_schema schema_name execute "DROP SCHEMA #{schema_name} CASCADE" end # Sets the schema search path to a string of comma-separated schema names. # Names beginning with $ have to be quoted (e.g. $user => '$user'). # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html # # This should be not be called manually but set in database.yml. def schema_search_path=(schema_csv) if schema_csv execute("SET search_path TO #{schema_csv}", 'SCHEMA') @schema_search_path = schema_csv end end # Returns the active schema search path. def schema_search_path @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0] end # Returns the current client message level. def client_min_messages query('SHOW client_min_messages', 'SCHEMA')[0][0] end # Set the client message level. def client_min_messages=(level) execute("SET client_min_messages TO '#{level}'", 'SCHEMA') end # Returns the sequence name for a table's primary key or some other specified key. def default_sequence_name(table_name, pk = nil) #:nodoc: result = serial_sequence(table_name, pk || 'id') return nil unless result Utils.extract_schema_qualified_name(result).to_s rescue ActiveRecord::StatementInvalid PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s end def serial_sequence(table, column) result = exec_query(<<-eosql, 'SCHEMA') SELECT pg_get_serial_sequence('#{table}', '#{column}') eosql result.rows.first.first end # Sets the sequence of a table's primary key to the specified value. def set_pk_sequence!(table, value) #:nodoc: pk, sequence = pk_and_sequence_for(table) if pk if sequence quoted_sequence = quote_table_name(sequence) select_value <<-end_sql, 'SCHEMA' SELECT setval('#{quoted_sequence}', #{value}) end_sql else @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger end end end # Resets the sequence of a table's primary key to the maximum value. def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: unless pk and sequence default_pk, default_sequence = pk_and_sequence_for(table) pk ||= default_pk sequence ||= default_sequence end if @logger && pk && !sequence @logger.warn "#{table} has primary key #{pk} with no default sequence" end if pk && sequence quoted_sequence = quote_table_name(sequence) select_value <<-end_sql, 'SCHEMA' SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql end end # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) #:nodoc: # First try looking for a sequence with a dependency on the # given table's primary key. result = query(<<-end_sql, 'SCHEMA')[0] SELECT attr.attname, nsp.nspname, seq.relname FROM pg_class seq, pg_attribute attr, pg_depend dep, pg_constraint cons, pg_namespace nsp WHERE seq.oid = dep.objid AND seq.relkind = 'S' AND attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid AND attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1] AND seq.relnamespace = nsp.oid AND cons.contype = 'p' AND dep.classid = 'pg_class'::regclass AND dep.refobjid = '#{quote_table_name(table)}'::regclass end_sql if result.nil? or result.empty? result = query(<<-end_sql, 'SCHEMA')[0] SELECT attr.attname, nsp.nspname, CASE WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1) ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) END FROM pg_class t JOIN pg_attribute attr ON (t.oid = attrelid) JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum) JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid) WHERE t.oid = '#{quote_table_name(table)}'::regclass AND cons.contype = 'p' AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate' end_sql end pk = result.shift if result.last [pk, PostgreSQL::Name.new(*result)] else [pk, nil] end rescue nil end # Returns just a table's primary key def primary_key(table) pks = exec_query(<<-end_sql, 'SCHEMA').rows SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '#{quote_table_name(table)}'::regclass end_sql return nil unless pks.count == 1 pks[0][0] end # Renames a table. # Also renames a table's primary key sequence if the sequence name exists and # matches the Active Record default. # # Example: # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) clear_cache! execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" pk, seq = pk_and_sequence_for(new_name) if seq && seq.identifier == "#{table_name}_#{pk}_seq" new_seq = "#{new_name}_#{pk}_seq" idx = "#{table_name}_pkey" new_idx = "#{new_name}_pkey" execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}" execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" end rename_table_indexes(table_name, new_name) end def add_column(table_name, column_name, type, options = {}) #:nodoc: clear_cache! super end # Changes the column of a table. def change_column(table_name, column_name, type, options = {}) clear_cache! quoted_table_name = quote_table_name(table_name) sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale]) sql_type << "[]" if options[:array] sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}" sql << " USING #{options[:using]}" if options[:using] if options[:cast_as] sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})" end execute sql change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) end # Changes the default value of a table column. def change_column_default(table_name, column_name, default) clear_cache! column = column_for(table_name, column_name) return unless column alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s" if default.nil? # DEFAULT NULL results in the same behavior as DROP DEFAULT. However, PostgreSQL will # cast the default to the columns type, which leaves us with a default like "default NULL::character varying". execute alter_column_query % "DROP DEFAULT" else execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}" end end def change_column_null(table_name, column_name, null, default = nil) clear_cache! unless null || default.nil? column = column_for(table_name, column_name) execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column end execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL") end # Renames a column in a table. def rename_column(table_name, column_name, new_column_name) #:nodoc: clear_cache! execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" rename_column_indexes(table_name, column_name, new_column_name) end def add_index(table_name, column_name, options = {}) #:nodoc: index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}" end def remove_index!(table_name, index_name) #:nodoc: execute "DROP INDEX #{quote_table_name(index_name)}" end def rename_index(table_name, old_name, new_name) validate_index_length!(table_name, new_name) execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" end def foreign_keys(table_name) fk_info = select_all <<-SQL.strip_heredoc SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete FROM pg_constraint c JOIN pg_class t1 ON c.conrelid = t1.oid JOIN pg_class t2 ON c.confrelid = t2.oid JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid JOIN pg_namespace t3 ON c.connamespace = t3.oid WHERE c.contype = 'f' AND t1.relname = #{quote(table_name)} AND t3.nspname = ANY (current_schemas(false)) ORDER BY c.conname SQL fk_info.map do |row| options = { column: row['column'], name: row['name'], primary_key: row['primary_key'] } options[:on_delete] = extract_foreign_key_action(row['on_delete']) options[:on_update] = extract_foreign_key_action(row['on_update']) ForeignKeyDefinition.new(table_name, row['to_table'], options) end end def extract_foreign_key_action(specifier) # :nodoc: case specifier when 'c'; :cascade when 'n'; :nullify when 'r'; :restrict end end def index_name_length 63 end # Maps logical Rails types to PostgreSQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s when 'binary' # PostgreSQL doesn't support limits on binary (bytea) columns. # The hard limit is 1Gb, because of a 32-bit size field, and TOAST. case limit when nil, 0..0x3fffffff; super(type) else raise(ActiveRecordError, "No binary type has byte size #{limit}.") end when 'text' # PostgreSQL doesn't support limits on text columns. # The hard limit is 1Gb, according to section 8.3 in the manual. case limit when nil, 0..0x3fffffff; super(type) else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") end when 'integer' return 'integer' unless limit case limit when 1, 2; 'smallint' when 3, 4; 'integer' when 5..8; 'bigint' else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.") end when 'datetime' return super unless precision case precision when 0..6; "timestamp(#{precision})" else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6") end else super end end # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and # requires that the ORDER BY include the distinct column. def columns_for_distinct(columns, orders) #:nodoc: order_columns = orders.reject(&:blank?).map{ |s| # Convert Arel node to string s = s.to_sql unless s.is_a?(String) # Remove any ASC/DESC modifiers s.gsub(/\s+(?:ASC|DESC)\b/i, '') .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '') }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } [super, *order_columns].join(', ') end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb000066400000000000000000000043101266740050600306040ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters module PostgreSQL # Value Object to hold a schema qualified name. # This is usually the name of a PostgreSQL relation but it can also represent # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent # double quoting. class Name # :nodoc: SEPARATOR = "." attr_reader :schema, :identifier def initialize(schema, identifier) @schema, @identifier = unquote(schema), unquote(identifier) end def to_s parts.join SEPARATOR end def quoted if schema PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier) else PGconn.quote_ident(identifier) end end def ==(o) o.class == self.class && o.parts == parts end alias_method :eql?, :== def hash parts.hash end protected def unquote(part) if part && part.start_with?('"') part[1..-2] else part end end def parts @parts ||= [@schema, @identifier].compact end end module Utils # :nodoc: extend self # Returns an instance of ActiveRecord::ConnectionAdapters::PostgreSQL::Name # extracted from +string+. # +schema+ is nil if not specified in +string+. # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+) # +string+ supports the range of schema/table references understood by PostgreSQL, for example: # # * table_name # * "table.name" # * schema_name.table_name # * schema_name."table.name" # * "schema_name".table_name # * "schema.name"."table name" def extract_schema_qualified_name(string) schema, table = string.scan(/[^".\s]+|"[^"]*"/) if table.nil? table = schema schema = nil end PostgreSQL::Name.new(schema, table) end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb000066400000000000000000000636711266740050600311630ustar00rootroot00000000000000require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/statement_pool' require 'active_record/connection_adapters/postgresql/utils' require 'active_record/connection_adapters/postgresql/column' require 'active_record/connection_adapters/postgresql/oid' require 'active_record/connection_adapters/postgresql/quoting' require 'active_record/connection_adapters/postgresql/referential_integrity' require 'active_record/connection_adapters/postgresql/schema_definitions' require 'active_record/connection_adapters/postgresql/schema_statements' require 'active_record/connection_adapters/postgresql/database_statements' require 'arel/visitors/bind_visitor' # Make sure we're using pg high enough for PGResult#values gem 'pg', '~> 0.15' require 'pg' require 'ipaddr' module ActiveRecord module ConnectionHandling # :nodoc: VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout, :client_encoding, :options, :application_name, :fallback_application_name, :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count, :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey, :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service] # Establishes a connection to the database that's used by all Active Record objects def postgresql_connection(config) conn_params = config.symbolize_keys conn_params.delete_if { |_, v| v.nil? } # Map ActiveRecords param names to PGs. conn_params[:user] = conn_params.delete(:username) if conn_params[:username] conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database] # Forward only valid config params to PGconn.connect. conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) } # The postgres drivers don't allow the creation of an unconnected PGconn object, # so just pass a nil connection object for the time being. ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config) end end module ConnectionAdapters # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver. # # Options: # # * :host - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets, # the default is to connect to localhost. # * :port - Defaults to 5432. # * :username - Defaults to be the same as the operating system name of the user running the application. # * :password - Password to be used if the server demands password authentication. # * :database - Defaults to be the same as the user name. # * :schema_search_path - An optional schema search path for the connection given # as a string of comma-separated schema names. This is backward-compatible with the :schema_order option. # * :encoding - An optional client encoding that is used in a SET client_encoding TO # call on the connection. # * :min_messages - An optional client min messages that is used in a # SET client_min_messages TO call on the connection. # * :variables - An optional hash of additional parameters that # will be used in SET SESSION key = val calls on the connection. # * :insert_returning - An optional boolean to control the use of RETURNING for INSERT statements # defaults to true. # # Any further options are used as connection parameters to libpq. See # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the # list of parameters. # # In addition, default connection parameters of libpq can be set per environment variables. # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html . class PostgreSQLAdapter < AbstractAdapter ADAPTER_NAME = 'PostgreSQL'.freeze NATIVE_DATABASE_TYPES = { primary_key: "serial primary key", bigserial: "bigserial", string: { name: "character varying" }, text: { name: "text" }, integer: { name: "integer" }, float: { name: "float" }, decimal: { name: "decimal" }, datetime: { name: "timestamp" }, time: { name: "time" }, date: { name: "date" }, daterange: { name: "daterange" }, numrange: { name: "numrange" }, tsrange: { name: "tsrange" }, tstzrange: { name: "tstzrange" }, int4range: { name: "int4range" }, int8range: { name: "int8range" }, binary: { name: "bytea" }, boolean: { name: "boolean" }, bigint: { name: "bigint" }, xml: { name: "xml" }, tsvector: { name: "tsvector" }, hstore: { name: "hstore" }, inet: { name: "inet" }, cidr: { name: "cidr" }, macaddr: { name: "macaddr" }, uuid: { name: "uuid" }, json: { name: "json" }, jsonb: { name: "jsonb" }, ltree: { name: "ltree" }, citext: { name: "citext" }, point: { name: "point" }, bit: { name: "bit" }, bit_varying: { name: "bit varying" }, money: { name: "money" }, } OID = PostgreSQL::OID #:nodoc: include PostgreSQL::Quoting include PostgreSQL::ReferentialIntegrity include PostgreSQL::SchemaStatements include PostgreSQL::DatabaseStatements include Savepoints def schema_creation # :nodoc: PostgreSQL::SchemaCreation.new self end # Adds +:array+ option to the default set provided by the # AbstractAdapter def prepare_column_options(column, types) # :nodoc: spec = super spec[:array] = 'true' if column.respond_to?(:array) && column.array spec[:default] = "\"#{column.default_function}\"" if column.default_function spec end # Adds +:array+ as a valid migration key def migration_keys super + [:array] end # Returns +true+, since this connection adapter supports prepared statement # caching. def supports_statement_cache? true end def supports_index_sort_order? true end def supports_partial_index? true end def supports_transaction_isolation? true end def supports_foreign_keys? true end def supports_views? true end def index_algorithms { concurrently: 'CONCURRENTLY' } end class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max) super @counter = 0 @cache = Hash.new { |h,pid| h[pid] = {} } end def each(&block); cache.each(&block); end def key?(key); cache.key?(key); end def [](key); cache[key]; end def length; cache.length; end def next_key "a#{@counter + 1}" end def []=(sql, key) while @max <= cache.size dealloc(cache.shift.last) end @counter += 1 cache[sql] = key end def clear cache.each_value do |stmt_key| dealloc stmt_key end cache.clear end def delete(sql_key) dealloc cache[sql_key] cache.delete sql_key end private def cache @cache[Process.pid] end def dealloc(key) @connection.query "DEALLOCATE #{key}" if connection_active? end def connection_active? @connection.status == PGconn::CONNECTION_OK rescue PGError false end end # Initializes and connects a PostgreSQL adapter. def initialize(connection, logger, connection_parameters, config) super(connection, logger) @visitor = Arel::Visitors::PostgreSQL.new self if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true else @prepared_statements = false end @connection_parameters, @config = connection_parameters, config # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil @table_alias_length = nil connect @statements = StatementPool.new @connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) if postgresql_version < 80200 raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" end @type_map = Type::HashLookupTypeMap.new initialize_type_map(type_map) @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true end # Clears the prepared statements cache. def clear_cache! @statements.clear end def truncate(table_name, name = nil) exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, [] end # Is this connection alive and ready for queries? def active? @connection.query 'SELECT 1' true rescue PGError false end # Close then reopen the connection. def reconnect! super @connection.reset configure_connection end def reset! clear_cache! reset_transaction unless @connection.transaction_status == ::PG::PQTRANS_IDLE @connection.query 'ROLLBACK' end @connection.query 'DISCARD ALL' configure_connection end # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! super @connection.close rescue nil end def native_database_types #:nodoc: NATIVE_DATABASE_TYPES end # Returns true, since this connection adapter supports migrations. def supports_migrations? true end # Does PostgreSQL support finding primary key on non-Active Record tables? def supports_primary_key? #:nodoc: true end def set_standard_conforming_strings execute('SET standard_conforming_strings = on', 'SCHEMA') end def supports_ddl_transactions? true end def supports_explain? true end # Returns true if pg > 9.1 def supports_extensions? postgresql_version >= 90100 end # Range datatypes weren't introduced until PostgreSQL 9.2 def supports_ranges? postgresql_version >= 90200 end def supports_materialized_views? postgresql_version >= 90300 end def enable_extension(name) exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap { reload_type_map } end def disable_extension(name) exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap { reload_type_map } end def extension_enabled?(name) if supports_extensions? res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", 'SCHEMA' res.cast_values.first end end def extensions if supports_extensions? exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values else super end end # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i end # Set the authorized user for this session def session_auth=(user) clear_cache! exec_query "SET SESSION AUTHORIZATION #{user}" end def use_insert_returning? @use_insert_returning end def valid_type?(type) !native_database_types[type].nil? end def update_table_definition(table_name, base) #:nodoc: PostgreSQL::Table.new(table_name, base) end def lookup_cast_type(sql_type) # :nodoc: oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i super(oid) end def column_name_for_operation(operation, node) # :nodoc: OPERATION_ALIASES.fetch(operation) { operation.downcase } end OPERATION_ALIASES = { # :nodoc: "maximum" => "max", "minimum" => "min", "average" => "avg", } protected # Returns the version of the connected PostgreSQL server. def postgresql_version @connection.server_version end # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html FOREIGN_KEY_VIOLATION = "23503" UNIQUE_VIOLATION = "23505" def translate_exception(exception, message) return exception unless exception.respond_to?(:result) case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE) when UNIQUE_VIOLATION RecordNotUnique.new(message, exception) when FOREIGN_KEY_VIOLATION InvalidForeignKey.new(message, exception) else super end end private def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc: if !type_map.key?(oid) load_additional_types(type_map, [oid]) end type_map.fetch(oid, fmod, sql_type) { warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String." Type::Value.new.tap do |cast_type| type_map.register_type(oid, cast_type) end } end def initialize_type_map(m) # :nodoc: register_class_with_limit m, 'int2', OID::Integer register_class_with_limit m, 'int4', OID::Integer register_class_with_limit m, 'int8', OID::Integer m.alias_type 'oid', 'int2' m.register_type 'float4', OID::Float.new m.alias_type 'float8', 'float4' m.register_type 'text', Type::Text.new register_class_with_limit m, 'varchar', Type::String m.alias_type 'char', 'varchar' m.alias_type 'name', 'varchar' m.alias_type 'bpchar', 'varchar' m.register_type 'bool', Type::Boolean.new register_class_with_limit m, 'bit', OID::Bit register_class_with_limit m, 'varbit', OID::BitVarying m.alias_type 'timestamptz', 'timestamp' m.register_type 'date', OID::Date.new m.register_type 'time', OID::Time.new m.register_type 'money', OID::Money.new m.register_type 'bytea', OID::Bytea.new m.register_type 'point', OID::Point.new m.register_type 'hstore', OID::Hstore.new m.register_type 'json', OID::Json.new m.register_type 'jsonb', OID::Jsonb.new m.register_type 'cidr', OID::Cidr.new m.register_type 'inet', OID::Inet.new m.register_type 'uuid', OID::Uuid.new m.register_type 'xml', OID::Xml.new m.register_type 'tsvector', OID::SpecializedString.new(:tsvector) m.register_type 'macaddr', OID::SpecializedString.new(:macaddr) m.register_type 'citext', OID::SpecializedString.new(:citext) m.register_type 'ltree', OID::SpecializedString.new(:ltree) # FIXME: why are we keeping these types as strings? m.alias_type 'interval', 'varchar' m.alias_type 'path', 'varchar' m.alias_type 'line', 'varchar' m.alias_type 'polygon', 'varchar' m.alias_type 'circle', 'varchar' m.alias_type 'lseg', 'varchar' m.alias_type 'box', 'varchar' m.register_type 'timestamp' do |_, _, sql_type| precision = extract_precision(sql_type) OID::DateTime.new(precision: precision) end m.register_type 'numeric' do |_, fmod, sql_type| precision = extract_precision(sql_type) scale = extract_scale(sql_type) # The type for the numeric depends on the width of the field, # so we'll do something special here. # # When dealing with decimal columns: # # places after decimal = fmod - 4 & 0xffff # places before decimal = (fmod - 4) >> 16 & 0xffff if fmod && (fmod - 4 & 0xffff).zero? # FIXME: Remove this class, and the second argument to # lookups on PG Type::DecimalWithoutScale.new(precision: precision) else OID::Decimal.new(precision: precision, scale: scale) end end load_additional_types(m) end def extract_limit(sql_type) # :nodoc: case sql_type when /^bigint/i, /^int8/i 8 when /^smallint/i 2 else super end end # Extracts the value from a PostgreSQL column default definition. def extract_value_from_default(oid, default) # :nodoc: case default # Quoted types when /\A[\(B]?'(.*)'::/m $1.gsub(/''/, "'") # Boolean types when 'true', 'false' default # Numeric types when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/ $1 # Object identifier types when /\A-?\d+\z/ $1 else # Anything else is blank, some user type, or some function # and we can't know the value of that, so return nil. nil end end def extract_default_function(default_value, default) # :nodoc: default if has_default_function?(default_value, default) end def has_default_function?(default_value, default) # :nodoc: !default_value && (%r{\w+\(.*\)} === default) end def load_additional_types(type_map, oids = nil) # :nodoc: initializer = OID::TypeMapInitializer.new(type_map) if supports_ranges? query = <<-SQL SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype FROM pg_type as t LEFT JOIN pg_range as r ON oid = rngtypid SQL else query = <<-SQL SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype FROM pg_type as t SQL end if oids query += "WHERE t.oid::integer IN (%s)" % oids.join(", ") else query += initializer.query_conditions_for_initial_load(type_map) end execute_and_clear(query, 'SCHEMA', []) do |records| initializer.run(records) end end FEATURE_NOT_SUPPORTED = "0A000" #:nodoc: def execute_and_clear(sql, name, binds) result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) : exec_cache(sql, name, binds) ret = yield result result.clear ret end def exec_no_cache(sql, name, binds) log(sql, name, binds) { @connection.async_exec(sql, []) } end def exec_cache(sql, name, binds) stmt_key = prepare_statement(sql) type_casted_binds = binds.map { |col, val| [col, type_cast(val, col)] } log(sql, name, type_casted_binds, stmt_key) do @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val }) end rescue ActiveRecord::StatementInvalid => e pgerror = e.original_exception # Get the PG code for the failure. Annoyingly, the code for # prepared statements whose return value may have changed is # FEATURE_NOT_SUPPORTED. Check here for more details: # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 begin code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) rescue raise e end if FEATURE_NOT_SUPPORTED == code @statements.delete sql_key(sql) retry else raise e end end # Returns the statement identifier for the client side cache # of statements def sql_key(sql) "#{schema_search_path}-#{sql}" end # Prepare the statement if it hasn't been prepared, return # the statement key. def prepare_statement(sql) sql_key = sql_key(sql) unless @statements.key? sql_key nextkey = @statements.next_key begin @connection.prepare nextkey, sql rescue => e raise translate_exception_class(e, sql) end # Clear the queue @connection.get_last_result @statements[sql_key] = nextkey end @statements[sql_key] end # Connects to a PostgreSQL server and sets up the adapter depending on the # connected server's characteristics. def connect @connection = PGconn.connect(@connection_parameters) # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision # should know about this but can't detect it there, so deal with it here. OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10 configure_connection rescue ::PG::Error => error if error.message.include?("does not exist") raise ActiveRecord::NoDatabaseError.new(error.message, error) else raise end end # Configures the encoding, verbosity, schema search path, and time zone of the connection. # This is called by #connect and should not be called manually. def configure_connection if @config[:encoding] @connection.set_client_encoding(@config[:encoding]) end self.client_min_messages = @config[:min_messages] || 'warning' self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] # Use standard-conforming strings so we don't have to do the E'...' dance. set_standard_conforming_strings # If using Active Record's time zone support configure the connection to return # TIMESTAMP WITH ZONE types in UTC. # (SET TIME ZONE does not use an equals sign like other SET variables) if ActiveRecord::Base.default_timezone == :utc execute("SET time zone 'UTC'", 'SCHEMA') elsif @local_tz execute("SET time zone '#{@local_tz}'", 'SCHEMA') end # SET statements from :variables config hash # http://www.postgresql.org/docs/8.3/static/sql-set.html variables = @config[:variables] || {} variables.map do |k, v| if v == ':default' || v == :default # Sets the value to the global or compile default execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA') elsif !v.nil? execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA') end end end # Returns the current ID of a table's sequence. def last_insert_id(sequence_name) #:nodoc: Integer(last_insert_id_value(sequence_name)) end def last_insert_id_value(sequence_name) last_insert_id_result(sequence_name).rows.first.first end def last_insert_id_result(sequence_name) #:nodoc: exec_query("SELECT currval('#{sequence_name}')", 'SQL') end # Returns the list of a table's column names, data types, and default values. # # The underlying query is roughly: # SELECT column.name, column.type, default.value # FROM column LEFT JOIN default # ON column.table_id = default.table_id # AND column.num = default.column_num # WHERE column.table_id = get_table_id('table_name') # AND column.num > 0 # AND NOT column.is_dropped # ORDER BY column.num # # If the table name is not prefixed with a schema, the database will # take the first match from the schema search path. # # Query implementation notes: # - format_type includes the column size constraint, e.g. varchar(50) # - ::regclass is a function that gives the id for a table name def column_definitions(table_name) # :nodoc: exec_query(<<-end_sql, 'SCHEMA').rows SELECT a.attname, format_type(a.atttypid, a.atttypmod), pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum end_sql end def extract_table_ref_from_insert_sql(sql) # :nodoc: sql[/into\s+([^\(]*).*values\s*\(/im] $1.strip if $1 end def create_table_definition(name, temporary, options, as = nil) # :nodoc: PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/schema_cache.rb000066400000000000000000000046541266740050600276370ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters class SchemaCache attr_reader :version attr_accessor :connection def initialize(conn) @connection = conn @columns = {} @columns_hash = {} @primary_keys = {} @tables = {} end def primary_keys(table_name) @primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil end # A cached lookup for table existence. def table_exists?(name) prepare_tables if @tables.empty? return @tables[name] if @tables.key? name @tables[name] = connection.table_exists?(name) end # Add internal cache for table with +table_name+. def add(table_name) if table_exists?(table_name) primary_keys(table_name) columns(table_name) columns_hash(table_name) end end def tables(name) @tables[name] end # Get the columns for a table def columns(table_name) @columns[table_name] ||= connection.columns(table_name) end # Get the columns for a table as a hash, key is the column name # value is the column object. def columns_hash(table_name) @columns_hash[table_name] ||= Hash[columns(table_name).map { |col| [col.name, col] }] end # Clears out internal caches def clear! @columns.clear @columns_hash.clear @primary_keys.clear @tables.clear @version = nil end def size [@columns, @columns_hash, @primary_keys, @tables].map { |x| x.size }.inject :+ end # Clear out internal caches for table with +table_name+. def clear_table_cache!(table_name) @columns.delete table_name @columns_hash.delete table_name @primary_keys.delete table_name @tables.delete table_name end def marshal_dump # if we get current version during initialization, it happens stack over flow. @version = ActiveRecord::Migrator.current_version [@version, @columns, @columns_hash, @primary_keys, @tables] end def marshal_load(array) @version, @columns, @columns_hash, @primary_keys, @tables = array end private def prepare_tables connection.tables.each { |table| @tables[table] = true } end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb000066400000000000000000000462021266740050600303330ustar00rootroot00000000000000require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/statement_pool' require 'arel/visitors/bind_visitor' gem 'sqlite3', '~> 1.3.6' require 'sqlite3' module ActiveRecord module ConnectionHandling # :nodoc: # sqlite3 adapter reuses sqlite_connection. def sqlite3_connection(config) # Require database. unless config[:database] raise ArgumentError, "No database file specified. Missing argument: database" end # Allow database path relative to Rails.root, but only if the database # path is not the special path that tells sqlite to build a database only # in memory. if ':memory:' != config[:database] config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root) dirname = File.dirname(config[:database]) Dir.mkdir(dirname) unless File.directory?(dirname) end db = SQLite3::Database.new( config[:database].to_s, :results_as_hash => true ) db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config) rescue Errno::ENOENT => error if error.message.include?("No such file or directory") raise ActiveRecord::NoDatabaseError.new(error.message, error) else raise end end end module ConnectionAdapters #:nodoc: class SQLite3Binary < Type::Binary # :nodoc: def cast_value(value) if value.encoding != Encoding::ASCII_8BIT value = value.force_encoding(Encoding::ASCII_8BIT) end value end end # The SQLite3 adapter works SQLite 3.6.16 or newer # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3). # # Options: # # * :database - Path to the database file. class SQLite3Adapter < AbstractAdapter ADAPTER_NAME = 'SQLite'.freeze include Savepoints NATIVE_DATABASE_TYPES = { primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL', string: { name: "varchar" }, text: { name: "text" }, integer: { name: "integer" }, float: { name: "float" }, decimal: { name: "decimal" }, datetime: { name: "datetime" }, time: { name: "time" }, date: { name: "date" }, binary: { name: "blob" }, boolean: { name: "boolean" } } class Version include Comparable def initialize(version_string) @version = version_string.split('.').map { |v| v.to_i } end def <=>(version_string) @version <=> version_string.split('.').map { |v| v.to_i } end end class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max) super @cache = Hash.new { |h,pid| h[pid] = {} } end def each(&block); cache.each(&block); end def key?(key); cache.key?(key); end def [](key); cache[key]; end def length; cache.length; end def []=(sql, key) while @max <= cache.size dealloc(cache.shift.last[:stmt]) end cache[sql] = key end def clear cache.each_value do |hash| dealloc hash[:stmt] end cache.clear end private def cache @cache[$$] end def dealloc(stmt) stmt.close unless stmt.closed? end end def initialize(connection, logger, connection_options, config) super(connection, logger) @active = nil @statements = StatementPool.new(@connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @config = config @visitor = Arel::Visitors::SQLite.new self if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true else @prepared_statements = false end end def supports_ddl_transactions? true end def supports_savepoints? true end def supports_partial_index? sqlite_version >= '3.8.0' end # Returns true, since this connection adapter supports prepared statement # caching. def supports_statement_cache? true end # Returns true, since this connection adapter supports migrations. def supports_migrations? #:nodoc: true end def supports_primary_key? #:nodoc: true end def requires_reloading? true end def supports_views? true end def active? @active != false end # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! super @active = false @connection.close rescue nil end # Clears the prepared statements cache. def clear_cache! @statements.clear end def supports_index_sort_order? true end # Returns 62. SQLite supports index names up to 64 # characters. The rest is used by rails internally to perform # temporary rename operations def allowed_index_name_length index_name_length - 2 end def native_database_types #:nodoc: NATIVE_DATABASE_TYPES end # Returns the current database encoding format as a string, eg: 'UTF-8' def encoding @connection.encoding.to_s end def supports_explain? true end # QUOTING ================================================== def _quote(value) # :nodoc: case value when Type::Binary::Data "x'#{value.hex}'" else super end end def _type_cast(value) # :nodoc: case value when BigDecimal value.to_f when String if value.encoding == Encoding::ASCII_8BIT super(value.encode(Encoding::UTF_8)) else super end else super end end def quote_string(s) #:nodoc: @connection.class.quote(s) end def quote_table_name_for_assignment(table, attr) quote_column_name(attr) end def quote_column_name(name) #:nodoc: %Q("#{name.to_s.gsub('"', '""')}") end # Quote date/time values for use in SQL input. Includes microseconds # if the value is a Time responding to usec. def quoted_date(value) #:nodoc: if value.respond_to?(:usec) "#{super}.#{sprintf("%06d", value.usec)}" else super end end #-- # DATABASE STATEMENTS ====================================== #++ def explain(arel, binds = []) sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', [])) end class ExplainPrettyPrinter # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles # the output of the SQLite shell: # # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) # 0|1|1|SCAN TABLE posts (~100000 rows) # def pp(result) # :nodoc: result.rows.map do |row| row.join('|') end.join("\n") + "\n" end end def exec_query(sql, name = nil, binds = []) type_casted_binds = binds.map { |col, val| [col, type_cast(val, col)] } log(sql, name, type_casted_binds) do # Don't cache statements if they are not prepared if without_prepared_statement?(binds) stmt = @connection.prepare(sql) begin cols = stmt.columns records = stmt.to_a ensure stmt.close end stmt = records else cache = @statements[sql] ||= { :stmt => @connection.prepare(sql) } stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! stmt.bind_params type_casted_binds.map { |_, val| val } end ActiveRecord::Result.new(cols, stmt.to_a) end end def exec_delete(sql, name = 'SQL', binds = []) exec_query(sql, name, binds) @connection.changes end alias :exec_update :exec_delete def last_inserted_id(result) @connection.last_insert_row_id end def execute(sql, name = nil) #:nodoc: log(sql, name) { @connection.execute(sql) } end def update_sql(sql, name = nil) #:nodoc: super @connection.changes end def delete_sql(sql, name = nil) #:nodoc: sql += " WHERE 1=1" unless sql =~ /WHERE/i super sql, name end def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: super id_value || @connection.last_insert_row_id end alias :create :insert_sql def select_rows(sql, name = nil, binds = []) exec_query(sql, name, binds).rows end def begin_db_transaction #:nodoc: log('begin transaction',nil) { @connection.transaction } end def commit_db_transaction #:nodoc: log('commit transaction',nil) { @connection.commit } end def exec_rollback_db_transaction #:nodoc: log('rollback transaction',nil) { @connection.rollback } end # SCHEMA STATEMENTS ======================================== def tables(name = nil, table_name = nil) #:nodoc: sql = <<-SQL SELECT name FROM sqlite_master WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence' SQL sql << " AND name = #{quote_table_name(table_name)}" if table_name exec_query(sql, 'SCHEMA').map do |row| row['name'] end end alias data_sources tables def table_exists?(table_name) table_name && tables(nil, table_name).any? end alias data_source_exists? table_exists? # Returns an array of +Column+ objects for the table specified by +table_name+. def columns(table_name) #:nodoc: table_structure(table_name).map do |field| case field["dflt_value"] when /^null$/i field["dflt_value"] = nil when /^'(.*)'$/m field["dflt_value"] = $1.gsub("''", "'") when /^"(.*)"$/m field["dflt_value"] = $1.gsub('""', '"') end sql_type = field['type'] cast_type = lookup_cast_type(sql_type) new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0) end end # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row| sql = <<-SQL SELECT sql FROM sqlite_master WHERE name=#{quote(row['name'])} AND type='index' UNION ALL SELECT sql FROM sqlite_temp_master WHERE name=#{quote(row['name'])} AND type='index' SQL index_sql = exec_query(sql).first['sql'] match = /\sWHERE\s+(.+)$/i.match(index_sql) where = match[1] if match IndexDefinition.new( table_name, row['name'], row['unique'] != 0, exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col| col['name'] }, nil, nil, where) end end def primary_key(table_name) #:nodoc: pks = table_structure(table_name).select { |f| f['pk'] > 0 } return nil unless pks.count == 1 pks[0]['name'] end def remove_index!(table_name, index_name) #:nodoc: exec_query "DROP INDEX #{quote_column_name(index_name)}" end # Renames a table. # # Example: # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" rename_table_indexes(table_name, new_name) end # See: http://www.sqlite.org/lang_altertable.html # SQLite has an additional restriction on the ALTER TABLE statement def valid_alter_table_type?(type) type.to_sym != :primary_key end def add_column(table_name, column_name, type, options = {}) #:nodoc: if valid_alter_table_type?(type) super(table_name, column_name, type, options) else alter_table(table_name) do |definition| definition.column(column_name, type, options) end end end def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc: alter_table(table_name) do |definition| definition.remove_column column_name end end def change_column_default(table_name, column_name, default) #:nodoc: alter_table(table_name) do |definition| definition[column_name].default = default end end def change_column_null(table_name, column_name, null, default = nil) unless null || default.nil? exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end alter_table(table_name) do |definition| definition[column_name].null = null end end def change_column(table_name, column_name, type, options = {}) #:nodoc: alter_table(table_name) do |definition| include_default = options_include_default?(options) definition[column_name].instance_eval do self.type = type self.limit = options[:limit] if options.include?(:limit) self.default = options[:default] if include_default self.null = options[:null] if options.include?(:null) self.precision = options[:precision] if options.include?(:precision) self.scale = options[:scale] if options.include?(:scale) end end end def rename_column(table_name, column_name, new_column_name) #:nodoc: column = column_for(table_name, column_name) alter_table(table_name, rename: {column.name => new_column_name.to_s}) rename_column_indexes(table_name, column.name, new_column_name) end protected def initialize_type_map(m) super m.register_type(/binary/i, SQLite3Binary.new) end def table_structure(table_name) structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? structure end def alter_table(table_name, options = {}) #:nodoc: altered_table_name = "a#{table_name}" caller = lambda {|definition| yield definition if block_given?} transaction do move_table(table_name, altered_table_name, options.merge(:temporary => true)) move_table(altered_table_name, table_name, &caller) end end def move_table(from, to, options = {}, &block) #:nodoc: copy_table(from, to, options, &block) drop_table(from) end def copy_table(from, to, options = {}) #:nodoc: from_primary_key = primary_key(from) options[:id] = false create_table(to, options) do |definition| @definition = definition @definition.primary_key(from_primary_key) if from_primary_key.present? columns(from).each do |column| column_name = options[:rename] ? (options[:rename][column.name] || options[:rename][column.name.to_sym] || column.name) : column.name next if column_name == from_primary_key @definition.column(column_name, column.type, :limit => column.limit, :default => column.default, :precision => column.precision, :scale => column.scale, :null => column.null) end yield @definition if block_given? end copy_table_indexes(from, to, options[:rename] || {}) copy_table_contents(from, to, @definition.columns.map {|column| column.name}, options[:rename] || {}) end def copy_table_indexes(from, to, rename = {}) #:nodoc: indexes(from).each do |index| name = index.name if to == "a#{from}" name = "t#{name}" elsif from == "a#{to}" name = name[1..-1] end to_column_names = columns(to).map { |c| c.name } columns = index.columns.map {|c| rename[c] || c }.select do |column| to_column_names.include?(column) end unless columns.empty? # index name can't be the same opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true } opts[:unique] = true if index.unique add_index(to, columns, opts) end end end def copy_table_contents(from, to, columns, rename = {}) #:nodoc: column_mappings = Hash[columns.map {|name| [name, name]}] rename.each { |a| column_mappings[a.last] = a.first } from_columns = columns(from).collect {|col| col.name} columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} quoted_columns = columns.map { |col| quote_column_name(col) } * ',' quoted_to = quote_table_name(to) raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }] exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row| sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES (" column_values = columns.map do |col| quote(row[column_mappings[col]], raw_column_mappings[col]) end sql << column_values * ', ' sql << ')' exec_query sql end end def sqlite_version @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)')) end def translate_exception(exception, message) case exception.message # SQLite 3.8.2 returns a newly formatted error message: # UNIQUE constraint failed: *table_name*.*column_name* # Older versions of SQLite return: # column *column_name* is not unique when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/ RecordNotUnique.new(message, exception) else super end end end end end rails-4.2.6/activerecord/lib/active_record/connection_adapters/statement_pool.rb000066400000000000000000000012451266740050600303020ustar00rootroot00000000000000module ActiveRecord module ConnectionAdapters class StatementPool include Enumerable def initialize(connection, max = 1000) @connection = connection @max = max end def each raise NotImplementedError end def key?(key) raise NotImplementedError end def [](key) raise NotImplementedError end def length raise NotImplementedError end def []=(sql, key) raise NotImplementedError end def clear raise NotImplementedError end def delete(key) raise NotImplementedError end end end end rails-4.2.6/activerecord/lib/active_record/connection_handling.rb000066400000000000000000000101471266740050600252270ustar00rootroot00000000000000module ActiveRecord module ConnectionHandling RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] } DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" } # Establishes the connection to the database. Accepts a hash as input where # the :adapter key must be specified with the name of a database adapter (in lower-case) # example for regular databases (MySQL, Postgresql, etc): # # ActiveRecord::Base.establish_connection( # adapter: "mysql", # host: "localhost", # username: "myuser", # password: "mypass", # database: "somedatabase" # ) # # Example for SQLite database: # # ActiveRecord::Base.establish_connection( # adapter: "sqlite3", # database: "path/to/dbfile" # ) # # Also accepts keys as strings (for parsing from YAML for example): # # ActiveRecord::Base.establish_connection( # "adapter" => "sqlite3", # "database" => "path/to/dbfile" # ) # # Or a URL: # # ActiveRecord::Base.establish_connection( # "postgres://myuser:mypass@localhost/somedatabase" # ) # # In case ActiveRecord::Base.configurations is set (Rails # automatically loads the contents of config/database.yml into it), # a symbol can also be given as argument, representing a key in the # configuration hash: # # ActiveRecord::Base.establish_connection(:production) # # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError # may be returned on an error. def establish_connection(spec = nil) spec ||= DEFAULT_ENV.call.to_sym resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations spec = resolver.spec(spec) unless respond_to?(spec.adapter_method) raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" end remove_connection connection_handler.establish_connection self, spec end class MergeAndResolveDefaultUrlConfig # :nodoc: def initialize(raw_configurations) @raw_config = raw_configurations.dup @env = DEFAULT_ENV.call.to_s end # Returns fully resolved connection hashes. # Merges connection information from `ENV['DATABASE_URL']` if available. def resolve ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all end private def config @raw_config.dup.tap do |cfg| if url = ENV['DATABASE_URL'] cfg[@env] ||= {} cfg[@env]["url"] ||= url end end end end # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work unrelated # to any of the specific Active Records. def connection retrieve_connection end def connection_id ActiveRecord::RuntimeRegistry.connection_id end def connection_id=(connection_id) ActiveRecord::RuntimeRegistry.connection_id = connection_id end # Returns the configuration of the associated connection as a hash: # # ActiveRecord::Base.connection_config # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"} # # Please use only for reading. def connection_config connection_pool.spec.config end def connection_pool connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished end def retrieve_connection connection_handler.retrieve_connection(self) end # Returns +true+ if Active Record is connected. def connected? connection_handler.connected?(self) end def remove_connection(klass = self) connection_handler.remove_connection(klass) end def clear_cache! # :nodoc: connection.schema_cache.clear! end delegate :clear_active_connections!, :clear_reloadable_connections!, :clear_all_connections!, :to => :connection_handler end end rails-4.2.6/activerecord/lib/active_record/core.rb000066400000000000000000000467501266740050600221650ustar00rootroot00000000000000require 'thread' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/string/filters' module ActiveRecord module Core extend ActiveSupport::Concern included do ## # :singleton-method: # # Accepts a logger conforming to the interface of Log4r which is then # passed on to any new database connections made and which can be # retrieved on both a class and instance level by calling +logger+. mattr_accessor :logger, instance_writer: false ## # Contains the database configuration - as is typically stored in config/database.yml - # as a Hash. # # For example, the following database.yml... # # development: # adapter: sqlite3 # database: db/development.sqlite3 # # production: # adapter: sqlite3 # database: db/production.sqlite3 # # ...would result in ActiveRecord::Base.configurations to look like this: # # { # 'development' => { # 'adapter' => 'sqlite3', # 'database' => 'db/development.sqlite3' # }, # 'production' => { # 'adapter' => 'sqlite3', # 'database' => 'db/production.sqlite3' # } # } def self.configurations=(config) @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve end self.configurations = {} # Returns fully resolved configurations hash def self.configurations @@configurations end ## # :singleton-method: # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling # dates and times from the database. This is set to :utc by default. mattr_accessor :default_timezone, instance_writer: false self.default_timezone = :utc ## # :singleton-method: # Specifies the format to use when dumping the database schema with Rails' # Rakefile. If :sql, the schema is dumped as (potentially database- # specific) SQL statements. If :ruby, the schema is dumped as an # ActiveRecord::Schema file which can be loaded into any database that # supports migrations. Use :ruby if you want to have different database # adapters for, e.g., your development and test environments. mattr_accessor :schema_format, instance_writer: false self.schema_format = :ruby ## # :singleton-method: # Specify whether or not to use timestamps for migration versions mattr_accessor :timestamped_migrations, instance_writer: false self.timestamped_migrations = true ## # :singleton-method: # Specify whether schema dump should happen at the end of the # db:migrate rake task. This is true by default, which is useful for the # development environment. This should ideally be false in the production # environment where dumping schema is rarely needed. mattr_accessor :dump_schema_after_migration, instance_writer: false self.dump_schema_after_migration = true mattr_accessor :maintain_test_schema, instance_accessor: false def self.disable_implicit_join_references=(value) ActiveSupport::Deprecation.warn(<<-MSG.squish) Implicit join references were removed with Rails 4.1. Make sure to remove this configuration because it does nothing. MSG end class_attribute :default_connection_handler, instance_writer: false class_attribute :find_by_statement_cache def self.connection_handler ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler end def self.connection_handler=(handler) ActiveRecord::RuntimeRegistry.connection_handler = handler end self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new end module ClassMethods def allocate define_attribute_methods super end def initialize_find_by_cache # :nodoc: self.find_by_statement_cache = {}.extend(Mutex_m) end def inherited(child_class) # :nodoc: child_class.initialize_find_by_cache super end def find(*ids) # :nodoc: # We don't have cache keys for this stuff yet return super unless ids.length == 1 # Allow symbols to super to maintain compatibility for deprecated finders until Rails 5 return super if ids.first.kind_of?(Symbol) return super if block_given? || primary_key.nil? || default_scopes.any? || current_scope || columns_hash.include?(inheritance_column) || ids.first.kind_of?(Array) id = ids.first if ActiveRecord::Base === id id = id.id ActiveSupport::Deprecation.warn(<<-MSG.squish) You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id` MSG end key = primary_key s = find_by_statement_cache[key] || find_by_statement_cache.synchronize { find_by_statement_cache[key] ||= StatementCache.create(connection) { |params| where(key => params.bind).limit(1) } } record = s.execute([id], self, connection).first unless record raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}" end record rescue RangeError raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'" end def find_by(*args) # :nodoc: return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any? return super if default_scopes.any? hash = args.first return super if hash.values.any? { |v| v.nil? || Array === v || Hash === v } # We can't cache Post.find_by(author: david) ...yet return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) } key = hash.keys klass = self s = find_by_statement_cache[key] || find_by_statement_cache.synchronize { find_by_statement_cache[key] ||= StatementCache.create(connection) { |params| wheres = key.each_with_object({}) { |param,o| o[param] = params.bind } klass.where(wheres).limit(1) } } begin s.execute(hash.values, self, connection).first rescue TypeError => e raise ActiveRecord::StatementInvalid.new(e.message, e) rescue RangeError nil end end def find_by!(*args) # :nodoc: find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}") end def initialize_generated_modules # :nodoc: generated_association_methods end def generated_association_methods @generated_association_methods ||= begin mod = const_set(:GeneratedAssociationMethods, Module.new) include mod mod end end # Returns a string like 'Post(id:integer, title:string, body:text)' def inspect if self == Base super elsif abstract_class? "#{super}(abstract)" elsif !connected? "#{super} (call '#{super}.connection' to establish a connection)" elsif table_exists? attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' "#{super}(#{attr_list})" else "#{super}(Table doesn't exist)" end end # Overwrite the default class equality method to provide support for association proxies. def ===(object) object.is_a?(self) end # Returns an instance of Arel::Table loaded with the current table name. # # class Post < ActiveRecord::Base # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) } # end def arel_table # :nodoc: @arel_table ||= Arel::Table.new(table_name, arel_engine) end # Returns the Arel engine. def arel_engine # :nodoc: @arel_engine ||= if Base == self || connection_handler.retrieve_connection_pool(self) self else superclass.arel_engine end end private def relation #:nodoc: relation = Relation.create(self, arel_table) if finder_needs_type_condition? relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) else relation end end end # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with # attributes but not yet saved (pass a hash with key names matching the associated table column names). # In both instances, valid attribute keys are determined by the column names of the associated table -- # hence you can't have attributes that aren't part of the table columns. # # ==== Example: # # Instantiates a single new object # User.new(first_name: 'Jamie') def initialize(attributes = nil, options = {}) @attributes = self.class._default_attributes.dup self.class.define_attribute_methods init_internals initialize_internals_callback # +options+ argument is only needed to make protected_attributes gem easier to hook. # Remove it when we drop support to this gem. init_attributes(attributes, options) if attributes yield self if block_given? _run_initialize_callbacks end # Initialize an empty model object from +coder+. +coder+ should be # the result of previously encoding an Active Record model, using # `encode_with` # # class Post < ActiveRecord::Base # end # # old_post = Post.new(title: "hello world") # coder = {} # old_post.encode_with(coder) # # post = Post.allocate # post.init_with(coder) # post.title # => 'hello world' def init_with(coder) coder = LegacyYamlAdapter.convert(self.class, coder) @attributes = coder['attributes'] init_internals @new_record = coder['new_record'] self.class.define_attribute_methods _run_find_callbacks _run_initialize_callbacks self end ## # :method: clone # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied. # That means that modifying attributes of the clone will modify the original, since they will both point to the # same attributes hash. If you need a copy of your attributes hash, please use the #dup method. # # user = User.first # new_user = user.clone # user.name # => "Bob" # new_user.name = "Joe" # user.name # => "Joe" # # user.object_id == new_user.object_id # => false # user.name.object_id == new_user.name.object_id # => true # # user.name.object_id == user.dup.name.object_id # => false ## # :method: dup # Duped objects have no id assigned and are treated as new records. Note # that this is a "shallow" copy as it copies the object's attributes # only, not its associations. The extent of a "deep" copy is application # specific and is therefore left to the application to implement according # to its need. # The dup method does not preserve the timestamps (created|updated)_(at|on). ## def initialize_dup(other) # :nodoc: @attributes = @attributes.dup @attributes.reset(self.class.primary_key) _run_initialize_callbacks @aggregation_cache = {} @association_cache = {} @new_record = true @destroyed = false super end # Populate +coder+ with attributes about this record that should be # serialized. The structure of +coder+ defined in this method is # guaranteed to match the structure of +coder+ passed to the +init_with+ # method. # # Example: # # class Post < ActiveRecord::Base # end # coder = {} # Post.new.encode_with(coder) # coder # => {"attributes" => {"id" => nil, ... }} def encode_with(coder) # FIXME: Remove this when we better serialize attributes coder['raw_attributes'] = attributes_before_type_cast coder['attributes'] = @attributes coder['new_record'] = new_record? coder['active_record_yaml_version'] = 0 end # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+. # # Note that new records are different from any other record by definition, unless the # other record is the receiver itself. Besides, if you fetch existing records with # +select+ and leave the ID out, you're on your own, this predicate will return false. # # Note also that destroying a record preserves its ID in the model instance, so deleted # models are still comparable. def ==(comparison_object) super || comparison_object.instance_of?(self.class) && !id.nil? && comparison_object.id == id end alias :eql? :== # Delegates to id in order to allow two records of the same type and id to work with something like: # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] def hash if id id.hash else super end end # Clone and freeze the attributes hash such that associations are still # accessible, even on destroyed records, but cloned models will not be # frozen. def freeze @attributes = @attributes.clone.freeze self end # Returns +true+ if the attributes hash has been frozen. def frozen? @attributes.frozen? end # Allows sort on objects def <=>(other_object) if other_object.is_a?(self.class) self.to_key <=> other_object.to_key else super end end # Returns +true+ if the record is read only. Records loaded through joins with piggy-back # attributes will be marked as read only since they cannot be saved. def readonly? @readonly end # Marks this record as read only. def readonly! @readonly = true end def connection_handler self.class.connection_handler end # Returns the contents of the record as a nicely formatted string. def inspect # We check defined?(@attributes) not to issue warnings if the object is # allocated but not initialized. inspection = if defined?(@attributes) && @attributes self.class.column_names.collect { |name| if has_attribute?(name) "#{name}: #{attribute_for_inspect(name)}" end }.compact.join(", ") else "not initialized" end "#<#{self.class} #{inspection}>" end # Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record` # when pp is required. def pretty_print(pp) return super if custom_inspect_method_defined? pp.object_address_group(self) do if defined?(@attributes) && @attributes column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } pp.seplist(column_names, proc { pp.text ',' }) do |column_name| column_value = read_attribute(column_name) pp.breakable ' ' pp.group(1) do pp.text column_name pp.text ':' pp.breakable pp.pp column_value end end else pp.breakable ' ' pp.text 'not initialized' end end end # Returns a hash of the given methods with their names as keys and returned values as values. def slice(*methods) Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access end private def set_transaction_state(state) # :nodoc: @transaction_state = state end def has_transactional_callbacks? # :nodoc: !_rollback_callbacks.empty? || !_commit_callbacks.empty? end # Updates the attributes on this particular ActiveRecord object so that # if it is associated with a transaction, then the state of the AR object # will be updated to reflect the current state of the transaction # # The @transaction_state variable stores the states of the associated # transaction. This relies on the fact that a transaction can only be in # one rollback or commit (otherwise a list of states would be required) # Each AR object inside of a transaction carries that transaction's # TransactionState. # # This method checks to see if the ActiveRecord object's state reflects # the TransactionState, and rolls back or commits the ActiveRecord object # as appropriate. # # Since ActiveRecord objects can be inside multiple transactions, this # method recursively goes through the parent of the TransactionState and # checks if the ActiveRecord object reflects the state of the object. def sync_with_transaction_state update_attributes_from_transaction_state(@transaction_state, 0) end def update_attributes_from_transaction_state(transaction_state, depth) @reflects_state = [false] if depth == 0 if transaction_state && transaction_state.finalized? && !has_transactional_callbacks? unless @reflects_state[depth] restore_transaction_record_state if transaction_state.rolledback? clear_transaction_record_state @reflects_state[depth] = true end if transaction_state.parent && !@reflects_state[depth+1] update_attributes_from_transaction_state(transaction_state.parent, depth+1) end end end # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements # of the array, and then rescues from the possible NoMethodError. If those elements are # ActiveRecord::Base's, then this triggers the various method_missing's that we have, # which significantly impacts upon performance. # # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. # # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html def to_ary # :nodoc: nil end def init_internals @aggregation_cache = {} @association_cache = {} @readonly = false @destroyed = false @marked_for_destruction = false @destroyed_by_association = nil @new_record = true @txn = nil @_start_transaction_state = {} @transaction_state = nil end def initialize_internals_callback end # This method is needed to make protected_attributes gem easier to hook. # Remove it when we drop support to this gem. def init_attributes(attributes, options) assign_attributes(attributes) end def thaw if frozen? @attributes = @attributes.dup end end def custom_inspect_method_defined? self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner end end end rails-4.2.6/activerecord/lib/active_record/counter_cache.rb000066400000000000000000000151331266740050600240260ustar00rootroot00000000000000module ActiveRecord # = Active Record Counter Cache module CounterCache extend ActiveSupport::Concern module ClassMethods # Resets one or more counter caches to their correct value using an SQL # count query. This is useful when adding new counter caches, or if the # counter has been corrupted or modified directly by SQL. # # ==== Parameters # # * +id+ - The id of the object you wish to reset a counter on. # * +counters+ - One or more association counters to reset. Association name or counter name can be given. # # ==== Examples # # # For Post with id #1 records reset the comments_count # Post.reset_counters(1, :comments) def reset_counters(id, *counters) object = find(id) counters.each do |counter_association| has_many_association = _reflect_on_association(counter_association) unless has_many_association has_many = reflect_on_all_associations(:has_many) has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym } counter_association = has_many_association.plural_name if has_many_association end raise ArgumentError, "'#{self.name}' has no association called '#{counter_association}'" unless has_many_association if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection has_many_association = has_many_association.through_reflection end foreign_key = has_many_association.foreign_key.to_s child_class = has_many_association.klass reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? } counter_name = reflection.counter_cache_column stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ arel_table[counter_name] => object.send(counter_association).count(:all) }, primary_key) connection.update stmt end return true end # A generic "counter updater" implementation, intended primarily to be # used by increment_counter and decrement_counter, but which may also # be useful on its own. It simply does a direct SQL update for the record # with the given ID, altering the given hash of counters by the amount # given by the corresponding value: # # ==== Parameters # # * +id+ - The id of the object you wish to update a counter on or an Array of ids. # * +counters+ - A Hash containing the names of the fields # to update as keys and the amount to update the field by as values. # # ==== Examples # # # For the Post with id of 5, decrement the comment_count by 1, and # # increment the action_count by 1 # Post.update_counters 5, comment_count: -1, action_count: 1 # # Executes the following SQL: # # UPDATE posts # # SET comment_count = COALESCE(comment_count, 0) - 1, # # action_count = COALESCE(action_count, 0) + 1 # # WHERE id = 5 # # # For the Posts with id of 10 and 15, increment the comment_count by 1 # Post.update_counters [10, 15], comment_count: 1 # # Executes the following SQL: # # UPDATE posts # # SET comment_count = COALESCE(comment_count, 0) + 1 # # WHERE id IN (10, 15) def update_counters(id, counters) updates = counters.map do |counter_name, value| operator = value < 0 ? '-' : '+' quoted_column = connection.quote_column_name(counter_name) "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" end unscoped.where(primary_key => id).update_all updates.join(', ') end # Increment a numeric field by one, via a direct SQL update. # # This method is used primarily for maintaining counter_cache columns that are # used to store aggregate values. For example, a DiscussionBoard may cache # posts_count and comments_count to avoid running an SQL query to calculate the # number of posts and comments there are, each time it is displayed. # # ==== Parameters # # * +counter_name+ - The name of the field that should be incremented. # * +id+ - The id of the object that should be incremented or an Array of ids. # # ==== Examples # # # Increment the post_count column for the record with an id of 5 # DiscussionBoard.increment_counter(:post_count, 5) def increment_counter(counter_name, id) update_counters(id, counter_name => 1) end # Decrement a numeric field by one, via a direct SQL update. # # This works the same as increment_counter but reduces the column value by # 1 instead of increasing it. # # ==== Parameters # # * +counter_name+ - The name of the field that should be decremented. # * +id+ - The id of the object that should be decremented or an Array of ids. # # ==== Examples # # # Decrement the post_count column for the record with an id of 5 # DiscussionBoard.decrement_counter(:post_count, 5) def decrement_counter(counter_name, id) update_counters(id, counter_name => -1) end end protected def actually_destroyed? @_actually_destroyed end def clear_destroy_state @_actually_destroyed = nil end private def _create_record(*) id = super each_counter_cached_associations do |association| if send(association.reflection.name) association.increment_counters @_after_create_counter_called = true end end id end def destroy_row affected_rows = super if affected_rows > 0 each_counter_cached_associations do |association| foreign_key = association.reflection.foreign_key.to_sym unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key if send(association.reflection.name) association.decrement_counters end end end end affected_rows end def each_counter_cached_associations _reflections.each do |name, reflection| yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column end end end end rails-4.2.6/activerecord/lib/active_record/dynamic_matchers.rb000066400000000000000000000061501266740050600245350ustar00rootroot00000000000000module ActiveRecord module DynamicMatchers #:nodoc: # This code in this file seems to have a lot of indirection, but the indirection # is there to provide extension points for the activerecord-deprecated_finders # gem. When we stop supporting activerecord-deprecated_finders (from Rails 5), # then we can remove the indirection. def respond_to?(name, include_private = false) if self == Base super else match = Method.match(self, name) match && match.valid? || super end end private def method_missing(name, *arguments, &block) match = Method.match(self, name) if match && match.valid? match.define send(name, *arguments, &block) else super end end class Method @matchers = [] class << self attr_reader :matchers def match(model, name) klass = matchers.find { |k| name =~ k.pattern } klass.new(model, name) if klass end def pattern @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ end def prefix raise NotImplementedError end def suffix '' end end attr_reader :model, :name, :attribute_names def initialize(model, name) @model = model @name = name.to_s @attribute_names = @name.match(self.class.pattern)[1].split('_and_') @attribute_names.map! { |n| @model.attribute_aliases[n] || n } end def valid? attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) } end def define model.class_eval <<-CODE, __FILE__, __LINE__ + 1 def self.#{name}(#{signature}) #{body} end CODE end def body raise NotImplementedError end end module Finder # Extended in activerecord-deprecated_finders def body result end # Extended in activerecord-deprecated_finders def result "#{finder}(#{attributes_hash})" end # The parameters in the signature may have reserved Ruby words, in order # to prevent errors, we start each param name with `_`. # # Extended in activerecord-deprecated_finders def signature attribute_names.map { |name| "_#{name}" }.join(', ') end # Given that the parameters starts with `_`, the finder needs to use the # same parameter name. def attributes_hash "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(',') + "}" end def finder raise NotImplementedError end end class FindBy < Method Method.matchers << self include Finder def self.prefix "find_by" end def finder "find_by" end end class FindByBang < Method Method.matchers << self include Finder def self.prefix "find_by" end def self.suffix "!" end def finder "find_by!" end end end end rails-4.2.6/activerecord/lib/active_record/enum.rb000066400000000000000000000162071266740050600221730ustar00rootroot00000000000000require 'active_support/core_ext/object/deep_dup' module ActiveRecord # Declare an enum attribute where the values map to integers in the database, # but can be queried by name. Example: # # class Conversation < ActiveRecord::Base # enum status: [ :active, :archived ] # end # # # conversation.update! status: 0 # conversation.active! # conversation.active? # => true # conversation.status # => "active" # # # conversation.update! status: 1 # conversation.archived! # conversation.archived? # => true # conversation.status # => "archived" # # # conversation.status = 1 # conversation.status = "archived" # # conversation.status = nil # conversation.status.nil? # => true # conversation.status # => nil # # Scopes based on the allowed values of the enum field will be provided # as well. With the above example: # # Conversation.active # Conversation.archived # # You can set the default value from the database declaration, like: # # create_table :conversations do |t| # t.column :status, :integer, default: 0 # end # # Good practice is to let the first declared status be the default. # # Finally, it's also possible to explicitly map the relation between attribute and # database integer with a +Hash+: # # class Conversation < ActiveRecord::Base # enum status: { active: 0, archived: 1 } # end # # Note that when an +Array+ is used, the implicit mapping from the values to database # integers is derived from the order the values appear in the array. In the example, # :active is mapped to +0+ as it's the first element, and :archived # is mapped to +1+. In general, the +i+-th element is mapped to i-1 in the # database. # # Therefore, once a value is added to the enum array, its position in the array must # be maintained, and new values should only be added to the end of the array. To # remove unused values, the explicit +Hash+ syntax should be used. # # In rare circumstances you might need to access the mapping directly. # The mappings are exposed through a class method with the pluralized attribute # name: # # Conversation.statuses # => { "active" => 0, "archived" => 1 } # # Use that class method when you need to know the ordinal value of an enum: # # Conversation.where("status <> ?", Conversation.statuses[:archived]) # # Where conditions on an enum attribute must use the ordinal value of an enum. module Enum def self.extended(base) # :nodoc: base.class_attribute(:defined_enums, instance_writer: false) base.defined_enums = {} end def inherited(base) # :nodoc: base.defined_enums = defined_enums.deep_dup super end def enum(definitions) klass = self definitions.each do |name, values| # statuses = { } enum_values = ActiveSupport::HashWithIndifferentAccess.new name = name.to_sym # def self.statuses statuses end detect_enum_conflict!(name, name.to_s.pluralize, true) klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values } _enum_methods_module.module_eval do # def status=(value) self[:status] = statuses[value] end klass.send(:detect_enum_conflict!, name, "#{name}=") define_method("#{name}=") { |value| if enum_values.has_key?(value) || value.blank? self[name] = enum_values[value] elsif enum_values.has_value?(value) # Assigning a value directly is not a end-user feature, hence it's not documented. # This is used internally to make building objects from the generated scopes work # as expected, i.e. +Conversation.archived.build.archived?+ should be true. self[name] = value else raise ArgumentError, "'#{value}' is not a valid #{name}" end } # def status() statuses.key self[:status] end klass.send(:detect_enum_conflict!, name, name) define_method(name) { enum_values.key self[name] } # def status_before_type_cast() statuses.key self[:status] end klass.send(:detect_enum_conflict!, name, "#{name}_before_type_cast") define_method("#{name}_before_type_cast") { enum_values.key self[name] } pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index pairs.each do |value, i| enum_values[value] = i # def active?() status == 0 end klass.send(:detect_enum_conflict!, name, "#{value}?") define_method("#{value}?") { self[name] == i } # def active!() update! status: :active end klass.send(:detect_enum_conflict!, name, "#{value}!") define_method("#{value}!") { update! name => value } # scope :active, -> { where status: 0 } klass.send(:detect_enum_conflict!, name, value, true) klass.scope value, -> { klass.where name => i } end end defined_enums[name.to_s] = enum_values end end private def _enum_methods_module @_enum_methods_module ||= begin mod = Module.new do private def save_changed_attribute(attr_name, old) if (mapping = self.class.defined_enums[attr_name.to_s]) value = _read_attribute(attr_name) if attribute_changed?(attr_name) if mapping[old] == value clear_attribute_changes([attr_name]) end else if old != value set_attribute_was(attr_name, mapping.key(old)) end end else super end end end include mod mod end end ENUM_CONFLICT_MESSAGE = \ "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \ "this will generate a %{type} method \"%{method}\", which is already defined " \ "by %{source}." def detect_enum_conflict!(enum_name, method_name, klass_method = false) if klass_method && dangerous_class_method?(method_name) raise ArgumentError, ENUM_CONFLICT_MESSAGE % { enum: enum_name, klass: self.name, type: 'class', method: method_name, source: 'Active Record' } elsif !klass_method && dangerous_attribute_method?(method_name) raise ArgumentError, ENUM_CONFLICT_MESSAGE % { enum: enum_name, klass: self.name, type: 'instance', method: method_name, source: 'Active Record' } elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module) raise ArgumentError, ENUM_CONFLICT_MESSAGE % { enum: enum_name, klass: self.name, type: 'instance', method: method_name, source: 'another enum' } end end end end rails-4.2.6/activerecord/lib/active_record/errors.rb000066400000000000000000000207151266740050600225420ustar00rootroot00000000000000module ActiveRecord # = Active Record Errors # # Generic Active Record exception class. class ActiveRecordError < StandardError end # Raised when the single-table inheritance mechanism fails to locate the subclass # (for example due to improper usage of column that +inheritance_column+ points to). class SubclassNotFound < ActiveRecordError #:nodoc: end # Raised when an object assigned to an association has an incorrect type. # # class Ticket < ActiveRecord::Base # has_many :patches # end # # class Patch < ActiveRecord::Base # belongs_to :ticket # end # # # Comments are not patches, this assignment raises AssociationTypeMismatch. # @ticket.patches << Comment.new(content: "Please attach tests to your patch.") class AssociationTypeMismatch < ActiveRecordError end # Raised when unserialized object's type mismatches one specified for serializable field. class SerializationTypeMismatch < ActiveRecordError end # Raised when adapter not specified on connection (or configuration file # +config/database.yml+ misses adapter field). class AdapterNotSpecified < ActiveRecordError end # Raised when Active Record cannot find database adapter specified in # +config/database.yml+ or programmatically. class AdapterNotFound < ActiveRecordError end # Raised when connection to the database could not been established (for # example when +connection=+ is given a nil object). class ConnectionNotEstablished < ActiveRecordError end # Raised when Active Record cannot find record by given id or set of ids. class RecordNotFound < ActiveRecordError end # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be # saved because record is invalid. class RecordNotSaved < ActiveRecordError attr_reader :record def initialize(message, record = nil) @record = record super(message) end end # Raised by ActiveRecord::Base.destroy! when a call to destroy would return false. # # begin # complex_operation_that_internally_calls_destroy! # rescue ActiveRecord::RecordNotDestroyed => invalid # puts invalid.record.errors # end # class RecordNotDestroyed < ActiveRecordError attr_reader :record def initialize(message, record = nil) @record = record super(message) end end # Superclass for all database execution errors. # # Wraps the underlying database error as +original_exception+. class StatementInvalid < ActiveRecordError attr_reader :original_exception def initialize(message, original_exception = nil) super(message) @original_exception = original_exception end end # Defunct wrapper class kept for compatibility. # +StatementInvalid+ wraps the original exception now. class WrappedDatabaseException < StatementInvalid end # Raised when a record cannot be inserted because it would violate a uniqueness constraint. class RecordNotUnique < WrappedDatabaseException end # Raised when a record cannot be inserted or updated because it references a non-existent record. class InvalidForeignKey < WrappedDatabaseException end # Raised when number of bind variables in statement given to +:condition+ key # (for example, when using +find+ method) does not match number of expected # values supplied. # # For example, when there are two placeholders with only one value supplied: # # Location.where("lat = ? AND lng = ?", 53.7362) class PreparedStatementInvalid < ActiveRecordError end # Raised when a given database does not exist. class NoDatabaseError < StatementInvalid end # Raised on attempt to save stale record. Record is stale when it's being saved in another query after # instantiation, for example, when two users edit the same wiki page and one starts editing and saves # the page before the other. # # Read more about optimistic locking in ActiveRecord::Locking module # documentation. class StaleObjectError < ActiveRecordError attr_reader :record, :attempted_action def initialize(record, attempted_action) super("Attempted to #{attempted_action} a stale object: #{record.class.name}") @record = record @attempted_action = attempted_action end end # Raised when association is being configured improperly or user tries to use # offset and limit together with +has_many+ or +has_and_belongs_to_many+ # associations. class ConfigurationError < ActiveRecordError end # Raised on attempt to update record that is instantiated as read only. class ReadOnlyRecord < ActiveRecordError end # ActiveRecord::Transactions::ClassMethods.transaction uses this exception # to distinguish a deliberate rollback from other exceptional situations. # Normally, raising an exception will cause the +transaction+ method to rollback # the database transaction *and* pass on the exception. But if you raise an # ActiveRecord::Rollback exception, then the database transaction will be rolled back, # without passing on the exception. # # For example, you could do this in your controller to rollback a transaction: # # class BooksController < ActionController::Base # def create # Book.transaction do # book = Book.new(params[:book]) # book.save! # if today_is_friday? # # The system must fail on Friday so that our support department # # won't be out of job. We silently rollback this transaction # # without telling the user. # raise ActiveRecord::Rollback, "Call tech support!" # end # end # # ActiveRecord::Rollback is the only exception that won't be passed on # # by ActiveRecord::Base.transaction, so this line will still be reached # # even on Friday. # redirect_to root_url # end # end class Rollback < ActiveRecordError end # Raised when attribute has a name reserved by Active Record (when attribute # has name of one of Active Record instance methods). class DangerousAttributeError < ActiveRecordError end # Raised when unknown attributes are supplied via mass assignment. class UnknownAttributeError < NoMethodError attr_reader :record, :attribute def initialize(record, attribute) @record = record @attribute = attribute.to_s super("unknown attribute '#{attribute}' for #{@record.class}.") end end # Raised when an error occurred while doing a mass assignment to an attribute through the # +attributes=+ method. The exception has an +attribute+ property that is the name of the # offending attribute. class AttributeAssignmentError < ActiveRecordError attr_reader :exception, :attribute def initialize(message, exception, attribute) super(message) @exception = exception @attribute = attribute end end # Raised when there are multiple errors while doing a mass assignment through the +attributes+ # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError # objects, each corresponding to the error while assigning to an attribute. class MultiparameterAssignmentErrors < ActiveRecordError attr_reader :errors def initialize(errors) @errors = errors end end # Raised when a primary key is needed, but not specified in the schema or model. class UnknownPrimaryKey < ActiveRecordError attr_reader :model def initialize(model, description = nil) message = "Unknown primary key for table #{model.table_name} in model #{model}." message += "\n#{description}" if description super(message) @model = model end end # Raised when a relation cannot be mutated because it's already loaded. # # class Task < ActiveRecord::Base # end # # relation = Task.all # relation.loaded? # => true # # # Methods which try to mutate a loaded relation fail. # relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation # relation.limit!(5) # => ActiveRecord::ImmutableRelation class ImmutableRelation < ActiveRecordError end # TransactionIsolationError will be raised under the following conditions: # # * The adapter does not support setting the isolation level # * You are joining an existing open transaction # * You are creating a nested (savepoint) transaction # # The mysql, mysql2 and postgresql adapters support setting the transaction isolation level. class TransactionIsolationError < ActiveRecordError end end rails-4.2.6/activerecord/lib/active_record/explain.rb000066400000000000000000000021141266740050600226570ustar00rootroot00000000000000require 'active_support/lazy_load_hooks' require 'active_record/explain_registry' module ActiveRecord module Explain # Executes the block with the collect flag enabled. Queries are collected # asynchronously by the subscriber and returned. def collecting_queries_for_explain # :nodoc: ExplainRegistry.collect = true yield ExplainRegistry.queries ensure ExplainRegistry.reset end # Makes the adapter execute EXPLAIN for the tuples of queries and bindings. # Returns a formatted string ready to be logged. def exec_explain(queries) # :nodoc: str = queries.map do |sql, bind| [].tap do |msg| msg << "EXPLAIN for: #{sql}" unless bind.empty? bind_msg = bind.map {|col, val| [col.name, val]}.inspect msg.last << " #{bind_msg}" end msg << connection.explain(sql, bind) end.join("\n") end.join("\n") # Overriding inspect to be more human readable, especially in the console. def str.inspect self end str end end end rails-4.2.6/activerecord/lib/active_record/explain_registry.rb000066400000000000000000000011501266740050600246060ustar00rootroot00000000000000require 'active_support/per_thread_registry' module ActiveRecord # This is a thread locals registry for EXPLAIN. For example # # ActiveRecord::ExplainRegistry.queries # # returns the collected queries local to the current thread. # # See the documentation of ActiveSupport::PerThreadRegistry # for further details. class ExplainRegistry # :nodoc: extend ActiveSupport::PerThreadRegistry attr_accessor :queries, :collect def initialize reset end def collect? @collect end def reset @collect = false @queries = [] end end end rails-4.2.6/activerecord/lib/active_record/explain_subscriber.rb000066400000000000000000000020031266740050600250770ustar00rootroot00000000000000require 'active_support/notifications' require 'active_record/explain_registry' module ActiveRecord class ExplainSubscriber # :nodoc: def start(name, id, payload) # unused end def finish(name, id, payload) if ExplainRegistry.collect? && !ignore_payload?(payload) ExplainRegistry.queries << payload.values_at(:sql, :binds) end end # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on # our own EXPLAINs now matter how loopingly beautiful that would be. # # On the other hand, we want to monitor the performance of our real database # queries, not the performance of the access to the query cache. IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE) EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i def ignore_payload?(payload) payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS end ActiveSupport::Notifications.subscribe("sql.active_record", new) end end rails-4.2.6/activerecord/lib/active_record/fixture_set/000077500000000000000000000000001266740050600232355ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/fixture_set/file.rb000066400000000000000000000031241266740050600245010ustar00rootroot00000000000000require 'erb' require 'yaml' module ActiveRecord class FixtureSet class File # :nodoc: include Enumerable ## # Open a fixture file named +file+. When called with a block, the block # is called with the filehandle and the filehandle is automatically closed # when the block finishes. def self.open(file) x = new file block_given? ? yield(x) : x end def initialize(file) @file = file @rows = nil end def each(&block) rows.each(&block) end private def rows return @rows if @rows begin data = YAML.load(render(IO.read(@file))) rescue ArgumentError, Psych::SyntaxError => error raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace end @rows = data ? validate(data).to_a : [] end def render(content) context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new ERB.new(content).result(context.get_binding) end # Validate our unmarshalled data. def validate(data) unless Hash === data || YAML::Omap === data raise Fixture::FormatError, 'fixture is not a hash' end raise Fixture::FormatError unless data.all? { |name, row| Hash === row } data end end end end rails-4.2.6/activerecord/lib/active_record/fixtures.rb000066400000000000000000000766551266740050600231150ustar00rootroot00000000000000require 'erb' require 'yaml' require 'zlib' require 'active_support/dependencies' require 'active_support/core_ext/digest/uuid' require 'active_record/fixture_set/file' require 'active_record/errors' module ActiveRecord class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: end # \Fixtures are a way of organizing data that you want to test against; in short, sample data. # # They are stored in YAML files, one file per model, which are placed in the directory # appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically # configured for Rails, so you can just put your files in /test/fixtures/). # The fixture file ends with the +.yml+ file extension, for example: # /test/fixtures/web_sites.yml). # # The format of a fixture file looks like this: # # rubyonrails: # id: 1 # name: Ruby on Rails # url: http://www.rubyonrails.org # # google: # id: 2 # name: Google # url: http://www.google.com # # This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and # is followed by an indented list of key/value pairs in the "key: value" format. Records are # separated by a blank line for your viewing pleasure. # # Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type. # See http://yaml.org/type/omap.html # for the specification. You will need ordered fixtures when you have foreign key constraints # on keys in the same table. This is commonly needed for tree structures. Example: # # --- !omap # - parent: # id: 1 # parent_id: NULL # title: Parent # - child: # id: 2 # parent_id: 1 # title: Child # # = Using Fixtures in Test Cases # # Since fixtures are a testing construct, we use them in our unit and functional tests. There # are two ways to use the fixtures, but first let's take a look at a sample unit test: # # require 'test_helper' # # class WebSiteTest < ActiveSupport::TestCase # test "web_site_count" do # assert_equal 2, WebSite.count # end # end # # By default, +test_helper.rb+ will load all of your fixtures into your test # database, so this test will succeed. # # The testing environment will automatically load the all fixtures into the database before each # test. To ensure consistent data, the environment deletes the fixtures before running the load. # # In addition to being available in the database, the fixture's data may also be accessed by # using a special dynamic method, which has the same name as the model, and accepts the # name of the fixture to instantiate: # # test "find" do # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name # end # # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the # following tests: # # test "find_alt_method_1" do # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name'] # end # # test "find_alt_method_2" do # assert_equal "Ruby on Rails", @rubyonrails.name # end # # In order to use these methods to access fixtured data within your testcases, you must specify one of the # following in your ActiveSupport::TestCase-derived class: # # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above) # self.use_instantiated_fixtures = true # # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only) # self.use_instantiated_fixtures = :no_instances # # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully # traversed in the database to create the fixture hash and/or instance variables. This is expensive for # large sets of fixtured data. # # = Dynamic fixtures with ERB # # Some times you don't care about the content of the fixtures as much as you care about the volume. # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load # testing, like: # # <% 1.upto(1000) do |i| %> # fix_<%= i %>: # id: <%= i %> # name: guy_<%= 1 %> # <% end %> # # This will create 1000 very simple fixtures. # # Using ERB, you can also inject dynamic values into your fixtures with inserts like # <%= Date.today.strftime("%Y-%m-%d") %>. # This is however a feature to be used with some caution. The point of fixtures are that they're # stable units of predictable sample data. If you feel that you need to inject dynamic values, then # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values # in fixtures are to be considered a code smell. # # Helper methods defined in a fixture will not be available in other fixtures, to prevent against # unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module # that is included in ActiveRecord::FixtureSet.context_class. # # - define a helper method in `test_helper.rb` # module FixtureFileHelpers # def file_sha(path) # Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path))) # end # end # ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers # # - use the helper method in a fixture # photo: # name: kitten.png # sha: <%= file_sha 'files/kitten.png' %> # # = Transactional Fixtures # # Test cases can use begin+rollback to isolate their changes to the database instead of having to # delete+insert for every test case. # # class FooTest < ActiveSupport::TestCase # self.use_transactional_fixtures = true # # test "godzilla" do # assert !Foo.all.empty? # Foo.destroy_all # assert Foo.all.empty? # end # # test "godzilla aftermath" do # assert !Foo.all.empty? # end # end # # If you preload your test database with all fixture data (probably in the rake task) and use # transactional fixtures, then you may omit all fixtures declarations in your test cases since # all the data's already there and every case rolls back its changes. # # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to # true. This will provide access to fixture data for every table that has been loaded through # fixtures (depending on the value of +use_instantiated_fixtures+). # # When *not* to use transactional fixtures: # # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until # all parent transactions commit, particularly, the fixtures transaction which is begun in setup # and rolled back in teardown. Thus, you won't be able to verify # the results of your transaction until Active Record supports nested transactions or savepoints (in progress). # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. # Use InnoDB, MaxDB, or NDB instead. # # = Advanced Fixtures # # Fixtures that don't specify an ID get some extra features: # # * Stable, autogenerated IDs # * Label references for associations (belongs_to, has_one, has_many) # * HABTM associations as inline lists # # There are some more advanced features available even if the id is specified: # # * Autofilled timestamp columns # * Fixture label interpolation # * Support for YAML defaults # # == Stable, Autogenerated IDs # # Here, have a monkey fixture: # # george: # id: 1 # name: George the Monkey # # reginald: # id: 2 # name: Reginald the Pirate # # Each of these fixtures has two unique identifiers: one for the database # and one for the humans. Why don't we generate the primary key instead? # Hashing each fixture's label yields a consistent ID: # # george: # generated id: 503576764 # name: George the Monkey # # reginald: # generated id: 324201669 # name: Reginald the Pirate # # Active Record looks at the fixture's model class, discovers the correct # primary key, and generates it right before inserting the fixture # into the database. # # The generated ID for a given label is constant, so we can discover # any fixture's ID without loading anything, as long as we know the label. # # == Label references for associations (belongs_to, has_one, has_many) # # Specifying foreign keys in fixtures can be very fragile, not to # mention difficult to read. Since Active Record can figure out the ID of # any fixture from its label, you can specify FK's by label instead of ID. # # === belongs_to # # Let's break out some more monkeys and pirates. # # ### in pirates.yml # # reginald: # id: 1 # name: Reginald the Pirate # monkey_id: 1 # # ### in monkeys.yml # # george: # id: 1 # name: George the Monkey # pirate_id: 1 # # Add a few more monkeys and pirates and break this into multiple files, # and it gets pretty hard to keep track of what's going on. Let's # use labels instead of IDs: # # ### in pirates.yml # # reginald: # name: Reginald the Pirate # monkey: george # # ### in monkeys.yml # # george: # name: George the Monkey # pirate: reginald # # Pow! All is made clear. Active Record reflects on the fixture's model class, # finds all the +belongs_to+ associations, and allows you to specify # a target *label* for the *association* (monkey: george) rather than # a target *id* for the *FK* (monkey_id: 1). # # ==== Polymorphic belongs_to # # Supporting polymorphic relationships is a little bit more complicated, since # Active Record needs to know what type your association is pointing at. Something # like this should look familiar: # # ### in fruit.rb # # belongs_to :eater, polymorphic: true # # ### in fruits.yml # # apple: # id: 1 # name: apple # eater_id: 1 # eater_type: Monkey # # Can we do better? You bet! # # apple: # eater: george (Monkey) # # Just provide the polymorphic target type and Active Record will take care of the rest. # # === has_and_belongs_to_many # # Time to give our monkey some fruit. # # ### in monkeys.yml # # george: # id: 1 # name: George the Monkey # # ### in fruits.yml # # apple: # id: 1 # name: apple # # orange: # id: 2 # name: orange # # grape: # id: 3 # name: grape # # ### in fruits_monkeys.yml # # apple_george: # fruit_id: 1 # monkey_id: 1 # # orange_george: # fruit_id: 2 # monkey_id: 1 # # grape_george: # fruit_id: 3 # monkey_id: 1 # # Let's make the HABTM fixture go away. # # ### in monkeys.yml # # george: # id: 1 # name: George the Monkey # fruits: apple, orange, grape # # ### in fruits.yml # # apple: # name: apple # # orange: # name: orange # # grape: # name: grape # # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits # on George's fixture, but we could've just as easily specified a list # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on # the fixture's model class and discovers the +has_and_belongs_to_many+ # associations. # # == Autofilled Timestamp Columns # # If your table/model specifies any of Active Record's # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+), # they will automatically be set to Time.now. # # If you've set specific values, they'll be left alone. # # == Fixture label interpolation # # The label of the current fixture is always available as a column value: # # geeksomnia: # name: Geeksomnia's Account # subdomain: $LABEL # email: $LABEL@email.com # # Also, sometimes (like when porting older join table fixtures) you'll need # to be able to get a hold of the identifier for a given label. ERB # to the rescue: # # george_reginald: # monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %> # pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %> # # == Support for YAML defaults # # You can set and reuse defaults in your fixtures YAML file. # This is the same technique used in the +database.yml+ file to specify # defaults: # # DEFAULTS: &DEFAULTS # created_on: <%= 3.weeks.ago.to_s(:db) %> # # first: # name: Smurf # <<: *DEFAULTS # # second: # name: Fraggle # <<: *DEFAULTS # # Any fixture labeled "DEFAULTS" is safely ignored. class FixtureSet #-- # An instance of FixtureSet is normally stored in a single YAML file and # possibly in a folder with the same name. #++ MAX_ID = 2 ** 30 - 1 @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: config.pluralize_table_names ? fixture_set_name.singularize.camelize : fixture_set_name.camelize end def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: "#{ config.table_name_prefix }"\ "#{ fixture_set_name.tr('/', '_') }"\ "#{ config.table_name_suffix }".to_sym end def self.reset_cache @@all_cached_fixtures.clear end def self.cache_for_connection(connection) @@all_cached_fixtures[connection] end def self.fixture_is_cached?(connection, table_name) cache_for_connection(connection)[table_name] end def self.cached_fixtures(connection, keys_to_fetch = nil) if keys_to_fetch cache_for_connection(connection).values_at(*keys_to_fetch) else cache_for_connection(connection).values end end def self.cache_fixtures(connection, fixtures_map) cache_for_connection(connection).update(fixtures_map) end def self.instantiate_fixtures(object, fixture_set, load_instances = true) if load_instances fixture_set.each do |fixture_name, fixture| begin object.instance_variable_set "@#{fixture_name}", fixture.find rescue FixtureClassNotFound nil end end end end def self.instantiate_all_loaded_fixtures(object, load_instances = true) all_loaded_fixtures.each_value do |fixture_set| instantiate_fixtures(object, fixture_set, load_instances) end end cattr_accessor :all_loaded_fixtures self.all_loaded_fixtures = {} class ClassCache def initialize(class_names, config) @class_names = class_names.stringify_keys @config = config # Remove string values that aren't constants or subclasses of AR @class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) } end def [](fs_name) @class_names.fetch(fs_name) { klass = default_fixture_model(fs_name, @config).safe_constantize insert_class(@class_names, fs_name, klass) } end private def insert_class(class_names, name, klass) # We only want to deal with AR objects. if klass && klass < ActiveRecord::Base class_names[name] = klass else class_names[name] = nil end end def default_fixture_model(fs_name, config) ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) end end def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base) fixture_set_names = Array(fixture_set_names).map(&:to_s) class_names = ClassCache.new class_names, config # FIXME: Apparently JK uses this. connection = block_given? ? yield : ActiveRecord::Base.connection files_to_read = fixture_set_names.reject { |fs_name| fixture_is_cached?(connection, fs_name) } unless files_to_read.empty? connection.disable_referential_integrity do fixtures_map = {} fixture_sets = files_to_read.map do |fs_name| klass = class_names[fs_name] conn = klass ? klass.connection : connection fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new conn, fs_name, klass, ::File.join(fixtures_directory, fs_name)) end update_all_loaded_fixtures fixtures_map connection.transaction(:requires_new => true) do fixture_sets.each do |fs| conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection table_rows = fs.table_rows table_rows.each_key do |table| conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' end table_rows.each do |fixture_set_name, rows| rows.each do |row| conn.insert_fixture(row, fixture_set_name) end end # Cap primary key sequences to max(pk). if conn.respond_to?(:reset_pk_sequence!) conn.reset_pk_sequence!(fs.table_name) end end end cache_fixtures(connection, fixtures_map) end end cached_fixtures(connection, fixture_set_names) end # Returns a consistent, platform-independent identifier for +label+. # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes. def self.identify(label, column_type = :integer) if column_type == :uuid Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s) else Zlib.crc32(label.to_s) % MAX_ID end end # Superclass for the evaluation contexts used by ERB fixtures. def self.context_class @context_class ||= Class.new end def self.update_all_loaded_fixtures(fixtures_map) # :nodoc: all_loaded_fixtures.update(fixtures_map) end attr_reader :table_name, :name, :fixtures, :model_class, :config def initialize(connection, name, class_name, path, config = ActiveRecord::Base) @name = name @path = path @config = config @model_class = nil if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any? @model_class = class_name else @model_class = class_name.safe_constantize if class_name end @connection = connection @table_name = ( model_class.respond_to?(:table_name) ? model_class.table_name : self.class.default_fixture_table_name(name, config) ) @fixtures = read_fixture_files path, @model_class end def [](x) fixtures[x] end def []=(k,v) fixtures[k] = v end def each(&block) fixtures.each(&block) end def size fixtures.size end # Returns a hash of rows to be inserted. The key is the table, the value is # a list of rows to insert to that table. def table_rows now = config.default_timezone == :utc ? Time.now.utc : Time.now now = now.to_s(:db) # allow a standard key to be used for doing defaults in YAML fixtures.delete('DEFAULTS') # track any join tables we need to insert later rows = Hash.new { |h,table| h[table] = [] } rows[table_name] = fixtures.map do |label, fixture| row = fixture.to_hash if model_class # fill in timestamp columns if they aren't specified and the model is set to record_timestamps if model_class.record_timestamps timestamp_column_names.each do |c_name| row[c_name] = now unless row.key?(c_name) end end # interpolate the fixture label row.each do |key, value| row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String) end # generate a primary key if necessary if has_primary_key_column? && !row.include?(primary_key_name) row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type) end # If STI is used, find the correct subclass for association reflection reflection_class = if row.include?(inheritance_column_name) row[inheritance_column_name].constantize rescue model_class else model_class end reflection_class._reflections.each_value do |association| case association.macro when :belongs_to # Do not replace association name with association foreign key if they are named the same fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s if association.name.to_s != fk_name && value = row.delete(association.name.to_s) if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") # support polymorphic belongs_to as "label (Type)" row[association.foreign_type] = $1 end fk_type = reflection_class.columns_hash[fk_name].type row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) end when :has_many if association.options[:through] add_join_records(rows, row, HasManyThroughProxy.new(association)) end end end end row end rows end class ReflectionProxy # :nodoc: def initialize(association) @association = association end def join_table @association.join_table end def name @association.name end def primary_key_type @association.klass.column_types[@association.klass.primary_key].type end end class HasManyThroughProxy < ReflectionProxy # :nodoc: def rhs_key @association.foreign_key end def lhs_key @association.through_reflection.foreign_key end def join_table @association.through_reflection.table_name end end private def primary_key_name @primary_key_name ||= model_class && model_class.primary_key end def primary_key_type @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type end def add_join_records(rows, row, association) # This is the case when the join table has no fixtures file if (targets = row.delete(association.name.to_s)) table_name = association.join_table column_type = association.primary_key_type lhs_key = association.lhs_key rhs_key = association.rhs_key targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) rows[table_name].concat targets.map { |target| { lhs_key => row[primary_key_name], rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) } } end end def has_primary_key_column? @has_primary_key_column ||= primary_key_name && model_class.columns.any? { |c| c.name == primary_key_name } end def timestamp_column_names @timestamp_column_names ||= %w(created_at created_on updated_at updated_on) & column_names end def inheritance_column_name @inheritance_column_name ||= model_class && model_class.inheritance_column end def column_names @column_names ||= @connection.columns(@table_name).collect { |c| c.name } end def read_fixture_files(path, model_class) yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f| ::File.file?(f) } + [yaml_file_path(path)] yaml_files.each_with_object({}) do |file, fixtures| FixtureSet::File.open(file) do |fh| fh.each do |fixture_name, row| fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class) end end end end def yaml_file_path(path) "#{path}.yml" end end #-- # Deprecate 'Fixtures' in favor of 'FixtureSet'. #++ # :nodoc: Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet') class Fixture #:nodoc: include Enumerable class FixtureError < StandardError #:nodoc: end class FormatError < FixtureError #:nodoc: end attr_reader :model_class, :fixture def initialize(fixture, model_class) @fixture = fixture @model_class = model_class end def class_name model_class.name if model_class end def each fixture.each { |item| yield item } end def [](key) fixture[key] end alias :to_hash :fixture def find if model_class model_class.unscoped do model_class.find(fixture[model_class.primary_key]) end else raise FixtureClassNotFound, "No class attached to find." end end end end module ActiveRecord module TestFixtures extend ActiveSupport::Concern def before_setup setup_fixtures super end def after_teardown super teardown_fixtures end included do class_attribute :fixture_path, :instance_writer => false class_attribute :fixture_table_names class_attribute :fixture_class_names class_attribute :use_transactional_fixtures class_attribute :use_instantiated_fixtures # true, false, or :no_instances class_attribute :pre_loaded_fixtures class_attribute :config self.fixture_table_names = [] self.use_transactional_fixtures = true self.use_instantiated_fixtures = false self.pre_loaded_fixtures = false self.config = ActiveRecord::Base self.fixture_class_names = Hash.new do |h, fixture_set_name| h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config) end end module ClassMethods # Sets the model class for a fixture when the class name cannot be inferred from the fixture name. # # Examples: # # set_fixture_class some_fixture: SomeModel, # 'namespaced/fixture' => Another::Model # # The keys must be the fixture names, that coincide with the short paths to the fixture files. def set_fixture_class(class_names = {}) self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys) end def fixtures(*fixture_set_names) if fixture_set_names.first == :all fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"] fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] } else fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s } end self.fixture_table_names |= fixture_set_names setup_fixture_accessors(fixture_set_names) end def setup_fixture_accessors(fixture_set_names = nil) fixture_set_names = Array(fixture_set_names || fixture_table_names) methods = Module.new do fixture_set_names.each do |fs_name| fs_name = fs_name.to_s accessor_name = fs_name.tr('/', '_').to_sym define_method(accessor_name) do |*fixture_names| force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload @fixture_cache[fs_name] ||= {} instances = fixture_names.map do |f_name| f_name = f_name.to_s @fixture_cache[fs_name].delete(f_name) if force_reload if @loaded_fixtures[fs_name][f_name] @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find else raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'" end end instances.size == 1 ? instances.first : instances end private accessor_name end end include methods end def uses_transaction(*methods) @uses_transaction = [] unless defined?(@uses_transaction) @uses_transaction.concat methods.map { |m| m.to_s } end def uses_transaction?(method) @uses_transaction = [] unless defined?(@uses_transaction) @uses_transaction.include?(method.to_s) end end def run_in_transaction? use_transactional_fixtures && !self.class.uses_transaction?(method_name) end def setup_fixtures(config = ActiveRecord::Base) if pre_loaded_fixtures && !use_transactional_fixtures raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' end @fixture_cache = {} @fixture_connections = [] @@already_loaded_fixtures ||= {} # Load fixtures once and begin transaction. if run_in_transaction? if @@already_loaded_fixtures[self.class] @loaded_fixtures = @@already_loaded_fixtures[self.class] else @loaded_fixtures = load_fixtures(config) @@already_loaded_fixtures[self.class] = @loaded_fixtures end @fixture_connections = enlist_fixture_connections @fixture_connections.each do |connection| connection.begin_transaction joinable: false end # Load fixtures for every test. else ActiveRecord::FixtureSet.reset_cache @@already_loaded_fixtures[self.class] = nil @loaded_fixtures = load_fixtures(config) end # Instantiate fixtures for every test if requested. instantiate_fixtures if use_instantiated_fixtures end def teardown_fixtures # Rollback changes if a transaction is active. if run_in_transaction? @fixture_connections.each do |connection| connection.rollback_transaction if connection.transaction_open? end @fixture_connections.clear else ActiveRecord::FixtureSet.reset_cache end ActiveRecord::Base.clear_active_connections! end def enlist_fixture_connections ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) end private def load_fixtures(config) fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config) Hash[fixtures.map { |f| [f.name, f] }] end def instantiate_fixtures if pre_loaded_fixtures raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) else raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil? @loaded_fixtures.each_value do |fixture_set| ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?) end end end def load_instances? use_instantiated_fixtures != :no_instances end end end class ActiveRecord::FixtureSet::RenderContext # :nodoc: def self.create_subclass Class.new ActiveRecord::FixtureSet.context_class do def get_binding binding() end end end end rails-4.2.6/activerecord/lib/active_record/gem_version.rb000066400000000000000000000004771266740050600235460ustar00rootroot00000000000000module ActiveRecord # Returns the version of the currently loaded Active Record as a Gem::Version def self.gem_version Gem::Version.new VERSION::STRING end module VERSION MAJOR = 4 MINOR = 2 TINY = 6 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end end rails-4.2.6/activerecord/lib/active_record/inheritance.rb000066400000000000000000000227751266740050600235270ustar00rootroot00000000000000require 'active_support/core_ext/hash/indifferent_access' module ActiveRecord # == Single table inheritance # # Active Record allows inheritance by storing the name of the class in a column that by # default is named "type" (can be changed by overwriting Base.inheritance_column). # This means that an inheritance looking like this: # # class Company < ActiveRecord::Base; end # class Firm < Company; end # class Client < Company; end # class PriorityClient < Client; end # # When you do Firm.create(name: "37signals"), this record will be saved in # the companies table with type = "Firm". You can then fetch this row again using # Company.where(name: '37signals').first and it will return a Firm object. # # Be aware that because the type column is an attribute on the record every new # subclass will instantly be marked as dirty and the type column will be included # in the list of changed attributes on the record. This is different from non # STI classes: # # Company.new.changed? # => false # Firm.new.changed? # => true # Firm.new.changes # => {"type"=>["","Firm"]} # # If you don't have a type column defined in your table, single-table inheritance won't # be triggered. In that case, it'll work just like normal subclasses with no special magic # for differentiating between them or reloading the right type with find. # # Note, all the attributes for all the cases are kept in the same table. Read more: # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html # module Inheritance extend ActiveSupport::Concern included do # Determines whether to store the full constant name including namespace when using STI. class_attribute :store_full_sti_class, instance_writer: false self.store_full_sti_class = true end module ClassMethods # Determines if one of the attributes passed in is the inheritance column, # and if the inheritance column is attr accessible, it initializes an # instance of the given subclass instead of the base class. def new(*args, &block) if abstract_class? || self == Base raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated." end attrs = args.first if subclass_from_attributes?(attrs) subclass = subclass_from_attributes(attrs) end if subclass subclass.new(*args, &block) else super end end # Returns +true+ if this does not need STI type condition. Returns # +false+ if STI type condition needs to be applied. def descends_from_active_record? if self == Base false elsif superclass.abstract_class? superclass.descends_from_active_record? else superclass == Base || !columns_hash.include?(inheritance_column) end end def finder_needs_type_condition? #:nodoc: # This is like this because benchmarking justifies the strange :false stuff :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true) end def symbolized_base_class ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement.') @symbolized_base_class ||= base_class.to_s.to_sym end def symbolized_sti_name ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_sti_name` is deprecated and will be removed without replacement.') @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class end # Returns the class descending directly from ActiveRecord::Base, or # an abstract class, if any, in the inheritance hierarchy. # # If A extends AR::Base, A.base_class will return A. If B descends from A # through some arbitrarily deep hierarchy, B.base_class will return A. # # If B < A and C < B and if A is an abstract_class then both B.base_class # and C.base_class would return B as the answer since A is an abstract_class. def base_class unless self < Base raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" end if superclass == Base || superclass.abstract_class? self else superclass.base_class end end # Set this to true if this is an abstract class (see abstract_class?). # If you are using inheritance with ActiveRecord and don't want child classes # to utilize the implied STI table name of the parent class, this will need to be true. # For example, given the following: # # class SuperClass < ActiveRecord::Base # self.abstract_class = true # end # class Child < SuperClass # self.table_name = 'the_table_i_really_want' # end # # # self.abstract_class = true is required to make Child<.find,.create, or any Arel method> use the_table_i_really_want instead of a table called super_classes # attr_accessor :abstract_class # Returns whether this class is an abstract class or not. def abstract_class? defined?(@abstract_class) && @abstract_class == true end def sti_name store_full_sti_class ? name : name.demodulize end protected # Returns the class type of the record using the current module as a prefix. So descendants of # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. def compute_type(type_name) if type_name.match(/^::/) # If the type is prefixed with a scope operator then we assume that # the type_name is an absolute reference. ActiveSupport::Dependencies.constantize(type_name) else # Build a list of candidates to search for candidates = [] name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" } candidates << type_name candidates.each do |candidate| constant = ActiveSupport::Dependencies.safe_constantize(candidate) return constant if candidate == constant.to_s end raise NameError.new("uninitialized constant #{candidates.first}", candidates.first) end end private # Called by +instantiate+ to decide which class to use for a new # record instance. For single-table inheritance, we check the record # for a +type+ column and return the corresponding class. def discriminate_class_for_record(record) if using_single_table_inheritance?(record) find_sti_class(record[inheritance_column]) else super end end def using_single_table_inheritance?(record) record[inheritance_column].present? && columns_hash.include?(inheritance_column) end def find_sti_class(type_name) if store_full_sti_class ActiveSupport::Dependencies.constantize(type_name) else compute_type(type_name) end rescue NameError raise SubclassNotFound, "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " + "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " + "Please rename this column if you didn't intend it to be used for storing the inheritance class " + "or overwrite #{name}.inheritance_column to use another column for that information." end def type_condition(table = arel_table) sti_column = table[inheritance_column] sti_names = ([self] + descendants).map { |model| model.sti_name } sti_column.in(sti_names) end # Detect the subclass from the inheritance column of attrs. If the inheritance column value # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound # If this is a StrongParameters hash, and access to inheritance_column is not permitted, # this will ignore the inheritance column and return nil def subclass_from_attributes?(attrs) columns_hash.include?(inheritance_column) && attrs.is_a?(Hash) end def subclass_from_attributes(attrs) subclass_name = attrs.with_indifferent_access[inheritance_column] if subclass_name.present? && subclass_name != self.name subclass = subclass_name.safe_constantize unless descendants.include?(subclass) raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}") end subclass end end end def initialize_dup(other) super ensure_proper_type end private def initialize_internals_callback super ensure_proper_type end # Sets the attribute used for single table inheritance to this class name if this is not the # ActiveRecord::Base descendant. # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to # do Reply.new without having to set Reply[Reply.inheritance_column] = "Reply" yourself. # No such attribute would be set for objects of the Message class in that example. def ensure_proper_type klass = self.class if klass.finder_needs_type_condition? write_attribute(klass.inheritance_column, klass.sti_name) end end end end rails-4.2.6/activerecord/lib/active_record/integration.rb000066400000000000000000000077111266740050600235520ustar00rootroot00000000000000require 'active_support/core_ext/string/filters' module ActiveRecord module Integration extend ActiveSupport::Concern included do ## # :singleton-method: # Indicates the format used to generate the timestamp in the cache key. # Accepts any of the symbols in Time::DATE_FORMATS. # # This is +:nsec+, by default. class_attribute :cache_timestamp_format, :instance_writer => false self.cache_timestamp_format = :nsec end # Returns a String, which Action Pack uses for constructing an URL to this # object. The default implementation returns this record's id as a String, # or nil if this record's unsaved. # # For example, suppose that you have a User model, and that you have a # resources :users route. Normally, +user_path+ will # construct a path with the user object's 'id' in it: # # user = User.find_by(name: 'Phusion') # user_path(user) # => "/users/1" # # You can override +to_param+ in your model to make +user_path+ construct # a path using the user's name instead of the user's id: # # class User < ActiveRecord::Base # def to_param # overridden # name # end # end # # user = User.find_by(name: 'Phusion') # user_path(user) # => "/users/Phusion" def to_param # We can't use alias_method here, because method 'id' optimizes itself on the fly. id && id.to_s # Be sure to stringify the id for routes end # Returns a cache key that can be used to identify this record. # # Product.new.cache_key # => "products/new" # Product.find(5).cache_key # => "products/5" (updated_at not available) # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available) # # You can also pass a list of named timestamps, and the newest in the list will be # used to generate the key: # # Person.find(5).cache_key(:updated_at, :last_reviewed_at) def cache_key(*timestamp_names) case when new_record? "#{model_name.cache_key}/new" when timestamp_names.any? timestamp = max_updated_column_timestamp(timestamp_names) timestamp = timestamp.utc.to_s(cache_timestamp_format) "#{model_name.cache_key}/#{id}-#{timestamp}" when timestamp = max_updated_column_timestamp timestamp = timestamp.utc.to_s(cache_timestamp_format) "#{model_name.cache_key}/#{id}-#{timestamp}" else "#{model_name.cache_key}/#{id}" end end module ClassMethods # Defines your model's +to_param+ method to generate "pretty" URLs # using +method_name+, which can be any attribute or method that # responds to +to_s+. # # class User < ActiveRecord::Base # to_param :name # end # # user = User.find_by(name: 'Fancy Pants') # user.id # => 123 # user_path(user) # => "/users/123-fancy-pants" # # Values longer than 20 characters will be truncated. The value # is truncated word by word. # # user = User.find_by(name: 'David HeinemeierHansson') # user.id # => 125 # user_path(user) # => "/users/125-david" # # Because the generated param begins with the record's +id+, it is # suitable for passing to +find+. In a controller, for example: # # params[:id] # => "123-fancy-pants" # User.find(params[:id]).id # => 123 def to_param(method_name = nil) if method_name.nil? super() else define_method :to_param do if (default = super()) && (result = send(method_name).to_s).present? && (param = result.squish.truncate(20, separator: /\s/, omission: nil).parameterize).present? "#{default}-#{param}" else default end end end end end end end rails-4.2.6/activerecord/lib/active_record/legacy_yaml_adapter.rb000066400000000000000000000013121266740050600252040ustar00rootroot00000000000000module ActiveRecord module LegacyYamlAdapter def self.convert(klass, coder) return coder unless coder.is_a?(Psych::Coder) case coder["active_record_yaml_version"] when 0 then coder else if coder["attributes"].is_a?(AttributeSet) coder else Rails41.convert(klass, coder) end end end module Rails41 def self.convert(klass, coder) attributes = klass.attributes_builder .build_from_database(coder["attributes"]) new_record = coder["attributes"][klass.primary_key].blank? { "attributes" => attributes, "new_record" => new_record, } end end end end rails-4.2.6/activerecord/lib/active_record/locale/000077500000000000000000000000001266740050600221335ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/locale/en.yml000066400000000000000000000030571266740050600232650ustar00rootroot00000000000000en: # Attributes names common to most models #attributes: #created_at: "Created at" #updated_at: "Updated at" # Default error messages errors: messages: taken: "has already been taken" # Active Record models configuration activerecord: errors: messages: record_invalid: "Validation failed: %{errors}" restrict_dependent_destroy: one: "Cannot delete record because a dependent %{record} exists" many: "Cannot delete record because dependent %{record} exist" # Append your own errors here or at the model/attributes scope. # You can define own errors for models or model attributes. # The values :model, :attribute and :value are always available for interpolation. # # For example, # models: # user: # blank: "This is a custom blank message for %{model}: %{attribute}" # attributes: # login: # blank: "This is a custom blank message for User login" # Will define custom blank validation message for User model and # custom blank validation message for login attribute of User model. #models: # Translate model names. Used in Model.human_name(). #models: # For example, # user: "Dude" # will translate User model name to "Dude" # Translate model attribute names. Used in Model.human_attribute_name(attribute). #attributes: # For example, # user: # login: "Handle" # will translate User attribute "login" as "Handle" rails-4.2.6/activerecord/lib/active_record/locking/000077500000000000000000000000001266740050600223225ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/locking/optimistic.rb000066400000000000000000000156171266740050600250450ustar00rootroot00000000000000module ActiveRecord module Locking # == What is Optimistic Locking # # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of # conflicts with the data. It does this by checking whether another process has made changes to a record since # it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred # and the update is ignored. # # Check out ActiveRecord::Locking::Pessimistic for an alternative. # # == Usage # # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example: # # p1 = Person.find(1) # p2 = Person.find(1) # # p1.first_name = "Michael" # p1.save # # p2.first_name = "should fail" # p2.save # Raises a ActiveRecord::StaleObjectError # # Optimistic locking will also check for stale data when objects are destroyed. Example: # # p1 = Person.find(1) # p2 = Person.find(1) # # p1.first_name = "Michael" # p1.save # # p2.destroy # Raises a ActiveRecord::StaleObjectError # # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, # or otherwise apply the business logic needed to resolve the conflict. # # This locking mechanism will function inside a single Ruby process. To make it work across all # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form. # # This behavior can be turned off by setting ActiveRecord::Base.lock_optimistically = false. # To override the name of the +lock_version+ column, set the locking_column class attribute: # # class Person < ActiveRecord::Base # self.locking_column = :lock_person # end # module Optimistic extend ActiveSupport::Concern included do class_attribute :lock_optimistically, instance_writer: false self.lock_optimistically = true end def locking_enabled? #:nodoc: self.class.locking_enabled? end private def increment_lock lock_col = self.class.locking_column previous_lock_value = send(lock_col).to_i send(lock_col + '=', previous_lock_value + 1) end def _create_record(attribute_names = self.attribute_names, *) # :nodoc: if locking_enabled? # We always want to persist the locking version, even if we don't detect # a change from the default, since the database might have no default attribute_names |= [self.class.locking_column] end super end def _update_record(attribute_names = self.attribute_names) #:nodoc: return super unless locking_enabled? return 0 if attribute_names.empty? lock_col = self.class.locking_column previous_lock_value = send(lock_col).to_i increment_lock attribute_names += [lock_col] attribute_names.uniq! begin relation = self.class.unscoped affected_rows = relation.where( self.class.primary_key => id, lock_col => previous_lock_value, ).update_all( Hash[attributes_for_update(attribute_names).map do |name| [name, _read_attribute(name)] end] ) unless affected_rows == 1 raise ActiveRecord::StaleObjectError.new(self, "update") end affected_rows # If something went wrong, revert the version. rescue Exception send(lock_col + '=', previous_lock_value) raise end end def destroy_row affected_rows = super if locking_enabled? && affected_rows != 1 raise ActiveRecord::StaleObjectError.new(self, "destroy") end affected_rows end def relation_for_destroy relation = super if locking_enabled? column_name = self.class.locking_column column = self.class.columns_hash[column_name] substitute = self.class.connection.substitute_at(column) relation = relation.where(self.class.arel_table[column_name].eq(substitute)) relation.bind_values << [column, self[column_name].to_i] end relation end module ClassMethods DEFAULT_LOCKING_COLUMN = 'lock_version' # Returns true if the +lock_optimistically+ flag is set to true # (which it is, by default) and the table includes the # +locking_column+ column (defaults to +lock_version+). def locking_enabled? lock_optimistically && columns_hash[locking_column] end # Set the column to use for optimistic locking. Defaults to +lock_version+. def locking_column=(value) clear_caches_calculated_from_columns @locking_column = value.to_s end # The version column used for optimistic locking. Defaults to +lock_version+. def locking_column reset_locking_column unless defined?(@locking_column) @locking_column end # Reset the column used for optimistic locking back to the +lock_version+ default. def reset_locking_column self.locking_column = DEFAULT_LOCKING_COLUMN end # Make sure the lock version column gets updated when counters are # updated. def update_counters(id, counters) counters = counters.merge(locking_column => 1) if locking_enabled? super end private # We need to apply this decorator here, rather than on module inclusion. The closure # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the # sub class being decorated. As such, changes to `lock_optimistically`, or # `locking_column` would not be picked up. def inherited(subclass) subclass.class_eval do is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type| LockingType.new(type) end end super end end end class LockingType < SimpleDelegator # :nodoc: def type_cast_from_database(value) # `nil` *should* be changed to 0 super.to_i end def init_with(coder) __setobj__(coder['subtype']) end def encode_with(coder) coder['subtype'] = __getobj__ end end end end rails-4.2.6/activerecord/lib/active_record/locking/pessimistic.rb000066400000000000000000000056171266740050600252140ustar00rootroot00000000000000module ActiveRecord module Locking # Locking::Pessimistic provides support for row-level locking using # SELECT ... FOR UPDATE and other lock types. # # Chain ActiveRecord::Base#find to ActiveRecord::QueryMethods#lock to obtain an exclusive # lock on the selected rows: # # select * from accounts where id=1 for update # Account.lock.find(1) # # Call lock('some locking clause') to use a database-specific locking clause # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example: # # Account.transaction do # # select * from accounts where name = 'shugo' limit 1 for update # shugo = Account.where("name = 'shugo'").lock(true).first # yuko = Account.where("name = 'yuko'").lock(true).first # shugo.balance -= 100 # shugo.save! # yuko.balance += 100 # yuko.save! # end # # You can also use ActiveRecord::Base#lock! method to lock one record by id. # This may be better if you don't need to lock every row. Example: # # Account.transaction do # # select * from accounts where ... # accounts = Account.where(...) # account1 = accounts.detect { |account| ... } # account2 = accounts.detect { |account| ... } # # select * from accounts where id=? for update # account1.lock! # account2.lock! # account1.balance -= 100 # account1.save! # account2.balance += 100 # account2.save! # end # # You can start a transaction and acquire the lock in one go by calling # with_lock with a block. The block is called from within # a transaction, the object is already locked. Example: # # account = Account.first # account.with_lock do # # This block is called within a transaction, # # account is already locked. # account.balance -= 100 # account.save! # end # # Database-specific information on row locking: # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html # PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE module Pessimistic # Obtain a row lock on this record. Reloads the record to obtain the requested # lock. Pass an SQL locking clause to append the end of the SELECT statement # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns # the locked record. def lock!(lock = true) reload(:lock => lock) if persisted? self end # Wraps the passed block in a transaction, locking the object # before yielding. You can pass the SQL locking clause # as argument (see lock!). def with_lock(lock = true) transaction do lock!(lock) yield end end end end end rails-4.2.6/activerecord/lib/active_record/log_subscriber.rb000066400000000000000000000032101266740050600242210ustar00rootroot00000000000000module ActiveRecord class LogSubscriber < ActiveSupport::LogSubscriber IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] def self.runtime=(value) ActiveRecord::RuntimeRegistry.sql_runtime = value end def self.runtime ActiveRecord::RuntimeRegistry.sql_runtime ||= 0 end def self.reset_runtime rt, self.runtime = runtime, 0 rt end def initialize super @odd = false end def render_bind(column, value) if column if column.binary? # This specifically deals with the PG adapter that casts bytea columns into a Hash. value = value[:value] if value.is_a?(Hash) value = value ? "<#{value.bytesize} bytes of binary data>" : "" end [column.name, value] else [nil, value] end end def sql(event) self.class.runtime += event.duration return unless logger.debug? payload = event.payload return if IGNORE_PAYLOAD_NAMES.include?(payload[:name]) name = "#{payload[:name]} (#{event.duration.round(1)}ms)" sql = payload[:sql] binds = nil unless (payload[:binds] || []).empty? binds = " " + payload[:binds].map { |col,v| render_bind(col, v) }.inspect end if odd? name = color(name, CYAN, true) sql = color(sql, nil, true) else name = color(name, MAGENTA, true) end debug " #{name} #{sql}#{binds}" end def odd? @odd = !@odd end def logger ActiveRecord::Base.logger end end end ActiveRecord::LogSubscriber.attach_to :active_record rails-4.2.6/activerecord/lib/active_record/migration.rb000066400000000000000000001037071266740050600232220ustar00rootroot00000000000000require "active_support/core_ext/module/attribute_accessors" require 'set' module ActiveRecord class MigrationError < ActiveRecordError#:nodoc: def initialize(message = nil) message = "\n\n#{message}\n\n" if message super end end # Exception that can be raised to stop migrations from going backwards. class IrreversibleMigration < MigrationError end class DuplicateMigrationVersionError < MigrationError#:nodoc: def initialize(version) super("Multiple migrations have the version number #{version}") end end class DuplicateMigrationNameError < MigrationError#:nodoc: def initialize(name) super("Multiple migrations have the name #{name}") end end class UnknownMigrationVersionError < MigrationError #:nodoc: def initialize(version) super("No migration with version number #{version}") end end class IllegalMigrationNameError < MigrationError#:nodoc: def initialize(name) super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)") end end class PendingMigrationError < MigrationError#:nodoc: def initialize if defined?(Rails.env) super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}") else super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate") end end end # = Active Record Migrations # # Migrations can manage the evolution of a schema used by several physical # databases. It's a solution to the common problem of adding a field to make # a new feature work in your local database, but being unsure of how to # push that change to other developers and to the production server. With # migrations, you can describe the transformations in self-contained classes # that can be checked into version control systems and executed against # another database that might be one, two, or five versions behind. # # Example of a simple migration: # # class AddSsl < ActiveRecord::Migration # def up # add_column :accounts, :ssl_enabled, :boolean, default: true # end # # def down # remove_column :accounts, :ssl_enabled # end # end # # This migration will add a boolean flag to the accounts table and remove it # if you're backing out of the migration. It shows how all migrations have # two methods +up+ and +down+ that describes the transformations # required to implement or remove the migration. These methods can consist # of both the migration specific methods like +add_column+ and +remove_column+, # but may also contain regular Ruby code for generating data needed for the # transformations. # # Example of a more complex migration that also needs to initialize data: # # class AddSystemSettings < ActiveRecord::Migration # def up # create_table :system_settings do |t| # t.string :name # t.string :label # t.text :value # t.string :type # t.integer :position # end # # SystemSetting.create name: 'notice', # label: 'Use notice?', # value: 1 # end # # def down # drop_table :system_settings # end # end # # This migration first adds the +system_settings+ table, then creates the very # first row in it using the Active Record model that relies on the table. It # also uses the more advanced +create_table+ syntax where you can specify a # complete table schema in one block call. # # == Available transformations # # * create_table(name, options): Creates a table called +name+ and # makes the table object available to a block that can then add columns to it, # following the same format as +add_column+. See example above. The options hash # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create # table definition. # * drop_table(name): Drops the table called +name+. # * change_table(name, options): Allows to make column alterations to # the table called +name+. It makes the table object available to a block that # can then add/remove columns, indexes or foreign keys to it. # * rename_table(old_name, new_name): Renames the table called +old_name+ # to +new_name+. # * add_column(table_name, column_name, type, options): Adds a new column # to the table called +table_name+ # named +column_name+ specified to be one of the following types: # :string, :text, :integer, :float, # :decimal, :datetime, :timestamp, :time, # :date, :binary, :boolean. A default value can be # specified by passing an +options+ hash like { default: 11 }. # Other options include :limit and :null (e.g. # { limit: 50, null: false }) -- see # ActiveRecord::ConnectionAdapters::TableDefinition#column for details. # * rename_column(table_name, column_name, new_column_name): Renames # a column but keeps the type and content. # * change_column(table_name, column_name, type, options): Changes # the column to a different type using the same parameters as add_column. # * remove_column(table_name, column_name, type, options): Removes the column # named +column_name+ from the table called +table_name+. # * add_index(table_name, column_names, options): Adds a new index # with the name of the column. Other options include # :name, :unique (e.g. # { name: 'users_name_index', unique: true }) and :order # (e.g. { order: { name: :desc } }). # * remove_index(table_name, column: column_name): Removes the index # specified by +column_name+. # * remove_index(table_name, name: index_name): Removes the index # specified by +index_name+. # # == Irreversible transformations # # Some transformations are destructive in a manner that cannot be reversed. # Migrations of that kind should raise an ActiveRecord::IrreversibleMigration # exception in their +down+ method. # # == Running migrations from within Rails # # The Rails package has several tools to help create and apply migrations. # # To generate a new migration, you can use # rails generate migration MyNewMigration # # where MyNewMigration is the name of your migration. The generator will # create an empty migration file timestamp_my_new_migration.rb # in the db/migrate/ directory where timestamp is the # UTC formatted date and time that the migration was generated. # # There is a special syntactic shortcut to generate migrations that add fields to a table. # # rails generate migration add_fieldname_to_tablename fieldname:string # # This will generate the file timestamp_add_fieldname_to_tablename, which will look like this: # class AddFieldnameToTablename < ActiveRecord::Migration # def change # add_column :tablenames, :field, :string # end # end # # To run migrations against the currently configured database, use # rake db:migrate. This will update the database by running all of the # pending migrations, creating the schema_migrations table # (see "About the schema_migrations table" section below) if missing. It will also # invoke the db:schema:dump task, which will update your db/schema.rb file # to match the structure of your database. # # To roll the database back to a previous migration version, use # rake db:migrate VERSION=X where X is the version to which # you wish to downgrade. Alternatively, you can also use the STEP option if you # wish to rollback last few migrations. rake db:migrate STEP=2 will rollback # the latest two migrations. # # If any of the migrations throw an ActiveRecord::IrreversibleMigration exception, # that step will fail and you'll have some manual work to do. # # == Database support # # Migrations are currently supported in MySQL, PostgreSQL, SQLite, # SQL Server, and Oracle (all supported databases except DB2). # # == More examples # # Not all migrations change the schema. Some just fix the data: # # class RemoveEmptyTags < ActiveRecord::Migration # def up # Tag.all.each { |tag| tag.destroy if tag.pages.empty? } # end # # def down # # not much we can do to restore deleted data # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags" # end # end # # Others remove columns when they migrate up instead of down: # # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration # def up # remove_column :items, :incomplete_items_count # remove_column :items, :completed_items_count # end # # def down # add_column :items, :incomplete_items_count # add_column :items, :completed_items_count # end # end # # And sometimes you need to do something in SQL not abstracted directly by migrations: # # class MakeJoinUnique < ActiveRecord::Migration # def up # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)" # end # # def down # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`" # end # end # # == Using a model after changing its table # # Sometimes you'll want to add a column in a migration and populate it # immediately after. In that case, you'll need to make a call to # Base#reset_column_information in order to ensure that the model has the # latest column data from after the new column was added. Example: # # class AddPeopleSalary < ActiveRecord::Migration # def up # add_column :people, :salary, :integer # Person.reset_column_information # Person.all.each do |p| # p.update_attribute :salary, SalaryCalculator.compute(p) # end # end # end # # == Controlling verbosity # # By default, migrations will describe the actions they are taking, writing # them to the console as they happen, along with benchmarks describing how # long each step took. # # You can quiet them down by setting ActiveRecord::Migration.verbose = false. # # You can also insert your own messages and benchmarks by using the +say_with_time+ # method: # # def up # ... # say_with_time "Updating salaries..." do # Person.all.each do |p| # p.update_attribute :salary, SalaryCalculator.compute(p) # end # end # ... # end # # The phrase "Updating salaries..." would then be printed, along with the # benchmark for the block when the block completes. # # == About the schema_migrations table # # Rails versions 2.0 and prior used to create a table called # schema_info when using migrations. This table contained the # version of the schema as of the last applied migration. # # Starting with Rails 2.1, the schema_info table is # (automatically) replaced by the schema_migrations table, which # contains the version numbers of all the migrations applied. # # As a result, it is now possible to add migration files that are numbered # lower than the current schema version: when migrating up, those # never-applied "interleaved" migrations will be automatically applied, and # when migrating down, never-applied "interleaved" migrations will be skipped. # # == Timestamped Migrations # # By default, Rails generates migrations that look like: # # 20080717013526_your_migration_name.rb # # The prefix is a generation timestamp (in UTC). # # If you'd prefer to use numeric prefixes, you can turn timestamped migrations # off by setting: # # config.active_record.timestamped_migrations = false # # In application.rb. # # == Reversible Migrations # # Reversible migrations are migrations that know how to go +down+ for you. # You simply supply the +up+ logic, and the Migration system figures out # how to execute the down commands for you. # # To define a reversible migration, define the +change+ method in your # migration like this: # # class TenderloveMigration < ActiveRecord::Migration # def change # create_table(:horses) do |t| # t.column :content, :text # t.column :remind_at, :datetime # end # end # end # # This migration will create the horses table for you on the way up, and # automatically figure out how to drop the table on the way down. # # Some commands like +remove_column+ cannot be reversed. If you care to # define how to move up and down in these cases, you should define the +up+ # and +down+ methods as before. # # If a command cannot be reversed, an # ActiveRecord::IrreversibleMigration exception will be raised when # the migration is moving down. # # For a list of commands that are reversible, please see # ActiveRecord::Migration::CommandRecorder. # # == Transactional Migrations # # If the database adapter supports DDL transactions, all migrations will # automatically be wrapped in a transaction. There are queries that you # can't execute inside a transaction though, and for these situations # you can turn the automatic transactions off. # # class ChangeEnum < ActiveRecord::Migration # disable_ddl_transaction! # # def up # execute "ALTER TYPE model_size ADD VALUE 'new_value'" # end # end # # Remember that you can still open your own transactions, even if you # are in a Migration with self.disable_ddl_transaction!. class Migration autoload :CommandRecorder, 'active_record/migration/command_recorder' # This class is used to verify that all migrations have been run before # loading a web page if config.active_record.migration_error is set to :page_load class CheckPending def initialize(app) @app = app @last_check = 0 end def call(env) if connection.supports_migrations? mtime = ActiveRecord::Migrator.last_migration.mtime.to_i if @last_check < mtime ActiveRecord::Migration.check_pending!(connection) @last_check = mtime end end @app.call(env) end private def connection ActiveRecord::Base.connection end end class << self attr_accessor :delegate # :nodoc: attr_accessor :disable_ddl_transaction # :nodoc: def check_pending!(connection = Base.connection) raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection) end def load_schema_if_pending! if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations? # Roundrip to Rake to allow plugins to hook into database initialization. FileUtils.cd Rails.root do current_config = Base.connection_config Base.clear_all_connections! system("bin/rake db:test:prepare") # Establish a new connection, the old database may be gone (db:test:prepare uses purge) Base.establish_connection(current_config) end check_pending! end end def maintain_test_schema! # :nodoc: if ActiveRecord::Base.maintain_test_schema suppress_messages { load_schema_if_pending! } end end def method_missing(name, *args, &block) # :nodoc: (delegate || superclass.delegate).send(name, *args, &block) end def migrate(direction) new.migrate direction end # Disable the transaction wrapping this migration. # You can still create your own transactions even after calling #disable_ddl_transaction! # # For more details read the {"Transactional Migrations" section above}[rdoc-ref:Migration]. def disable_ddl_transaction! @disable_ddl_transaction = true end end def disable_ddl_transaction # :nodoc: self.class.disable_ddl_transaction end cattr_accessor :verbose attr_accessor :name, :version def initialize(name = self.class.name, version = nil) @name = name @version = version @connection = nil end self.verbose = true # instantiate the delegate object after initialize is defined self.delegate = new # Reverses the migration commands for the given block and # the given migrations. # # The following migration will remove the table 'horses' # and create the table 'apples' on the way up, and the reverse # on the way down. # # class FixTLMigration < ActiveRecord::Migration # def change # revert do # create_table(:horses) do |t| # t.text :content # t.datetime :remind_at # end # end # create_table(:apples) do |t| # t.string :variety # end # end # end # # Or equivalently, if +TenderloveMigration+ is defined as in the # documentation for Migration: # # require_relative '2012121212_tenderlove_migration' # # class FixupTLMigration < ActiveRecord::Migration # def change # revert TenderloveMigration # # create_table(:apples) do |t| # t.string :variety # end # end # end # # This command can be nested. def revert(*migration_classes) run(*migration_classes.reverse, revert: true) unless migration_classes.empty? if block_given? if @connection.respond_to? :revert @connection.revert { yield } else recorder = CommandRecorder.new(@connection) @connection = recorder suppress_messages do @connection.revert { yield } end @connection = recorder.delegate recorder.commands.each do |cmd, args, block| send(cmd, *args, &block) end end end end def reverting? @connection.respond_to?(:reverting) && @connection.reverting end class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc: def up yield unless reverting end def down yield if reverting end end # Used to specify an operation that can be run in one direction or another. # Call the methods +up+ and +down+ of the yielded object to run a block # only in one given direction. # The whole block will be called in the right order within the migration. # # In the following example, the looping on users will always be done # when the three columns 'first_name', 'last_name' and 'full_name' exist, # even when migrating down: # # class SplitNameMigration < ActiveRecord::Migration # def change # add_column :users, :first_name, :string # add_column :users, :last_name, :string # # reversible do |dir| # User.reset_column_information # User.all.each do |u| # dir.up { u.first_name, u.last_name = u.full_name.split(' ') } # dir.down { u.full_name = "#{u.first_name} #{u.last_name}" } # u.save # end # end # # revert { add_column :users, :full_name, :string } # end # end def reversible helper = ReversibleBlockHelper.new(reverting?) execute_block{ yield helper } end # Runs the given migration classes. # Last argument can specify options: # - :direction (default is :up) # - :revert (default is false) def run(*migration_classes) opts = migration_classes.extract_options! dir = opts[:direction] || :up dir = (dir == :down ? :up : :down) if opts[:revert] if reverting? # If in revert and going :up, say, we want to execute :down without reverting, so revert { run(*migration_classes, direction: dir, revert: true) } else migration_classes.each do |migration_class| migration_class.new.exec_migration(@connection, dir) end end end def up self.class.delegate = self return unless self.class.respond_to?(:up) self.class.up end def down self.class.delegate = self return unless self.class.respond_to?(:down) self.class.down end # Execute this migration in the named direction def migrate(direction) return unless respond_to?(direction) case direction when :up then announce "migrating" when :down then announce "reverting" end time = nil ActiveRecord::Base.connection_pool.with_connection do |conn| time = Benchmark.measure do exec_migration(conn, direction) end end case direction when :up then announce "migrated (%.4fs)" % time.real; write when :down then announce "reverted (%.4fs)" % time.real; write end end def exec_migration(conn, direction) @connection = conn if respond_to?(:change) if direction == :down revert { change } else change end else send(direction) end ensure @connection = nil end def write(text="") puts(text) if verbose end def announce(message) text = "#{version} #{name}: #{message}" length = [0, 75 - text.length].max write "== %s %s" % [text, "=" * length] end def say(message, subitem=false) write "#{subitem ? " ->" : "--"} #{message}" end def say_with_time(message) say(message) result = nil time = Benchmark.measure { result = yield } say "%.4fs" % time.real, :subitem say("#{result} rows", :subitem) if result.is_a?(Integer) result end def suppress_messages save, self.verbose = verbose, false yield ensure self.verbose = save end def connection @connection || ActiveRecord::Base.connection end def method_missing(method, *arguments, &block) arg_list = arguments.map{ |a| a.inspect } * ', ' say_with_time "#{method}(#{arg_list})" do unless @connection.respond_to? :revert unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method) arguments[0] = proper_table_name(arguments.first, table_name_options) if [:rename_table, :add_foreign_key].include?(method) || (method == :remove_foreign_key && !arguments.second.is_a?(Hash)) arguments[1] = proper_table_name(arguments.second, table_name_options) end end end return super unless connection.respond_to?(method) connection.send(method, *arguments, &block) end end def copy(destination, sources, options = {}) copied = [] FileUtils.mkdir_p(destination) unless File.exist?(destination) destination_migrations = ActiveRecord::Migrator.migrations(destination) last = destination_migrations.last sources.each do |scope, path| source_migrations = ActiveRecord::Migrator.migrations(path) source_migrations.each do |migration| source = File.binread(migration.filename) inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n" if /\A#.*\b(?:en)?coding:\s*\S+/ =~ source # If we have a magic comment in the original migration, # insert our comment after the first newline(end of the magic comment line) # so the magic keep working. # Note that magic comments must be at the first line(except sh-bang). source[/\n/] = "\n#{inserted_comment}" else source = "#{inserted_comment}#{source}" end if duplicate = destination_migrations.detect { |m| m.name == migration.name } if options[:on_skip] && duplicate.scope != scope.to_s options[:on_skip].call(scope, migration) end next end migration.version = next_migration_number(last ? last.version + 1 : 0).to_i new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb") old_path, migration.filename = migration.filename, new_path last = migration File.binwrite(migration.filename, source) copied << migration options[:on_copy].call(scope, migration, old_path) if options[:on_copy] destination_migrations << migration end end copied end # Finds the correct table name given an Active Record object. # Uses the Active Record object's own table_name, or pre/suffix from the # options passed in. def proper_table_name(name, options = {}) if name.respond_to? :table_name name.table_name else "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}" end end # Determines the version number of the next migration. def next_migration_number(number) if ActiveRecord::Base.timestamped_migrations [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max else SchemaMigration.normalize_migration_number(number) end end def table_name_options(config = ActiveRecord::Base) { table_name_prefix: config.table_name_prefix, table_name_suffix: config.table_name_suffix } end private def execute_block if connection.respond_to? :execute_block super # use normal delegation to record the block else yield end end end # MigrationProxy is used to defer loading of the actual migration classes # until they are needed class MigrationProxy < Struct.new(:name, :version, :filename, :scope) def initialize(name, version, filename, scope) super @migration = nil end def basename File.basename(filename) end def mtime File.mtime filename end delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration private def migration @migration ||= load_migration end def load_migration require(File.expand_path(filename)) name.constantize.new(name, version) end end class NullMigration < MigrationProxy #:nodoc: def initialize super(nil, 0, nil, nil) end def mtime 0 end end class Migrator#:nodoc: class << self attr_writer :migrations_paths alias :migrations_path= :migrations_paths= def migrate(migrations_paths, target_version = nil, &block) case when target_version.nil? up(migrations_paths, target_version, &block) when current_version == 0 && target_version == 0 [] when current_version > target_version down(migrations_paths, target_version, &block) else up(migrations_paths, target_version, &block) end end def rollback(migrations_paths, steps=1) move(:down, migrations_paths, steps) end def forward(migrations_paths, steps=1) move(:up, migrations_paths, steps) end def up(migrations_paths, target_version = nil) migrations = migrations(migrations_paths) migrations.select! { |m| yield m } if block_given? new(:up, migrations, target_version).migrate end def down(migrations_paths, target_version = nil, &block) migrations = migrations(migrations_paths) migrations.select! { |m| yield m } if block_given? new(:down, migrations, target_version).migrate end def run(direction, migrations_paths, target_version) new(direction, migrations(migrations_paths), target_version).run end def open(migrations_paths) new(:up, migrations(migrations_paths), nil) end def schema_migrations_table_name SchemaMigration.table_name end def get_all_versions(connection = Base.connection) if connection.table_exists?(schema_migrations_table_name) SchemaMigration.all.map { |x| x.version.to_i }.sort else [] end end def current_version(connection = Base.connection) get_all_versions(connection).max || 0 end def needs_migration?(connection = Base.connection) (migrations(migrations_paths).collect(&:version) - get_all_versions(connection)).size > 0 end def any_migrations? migrations(migrations_paths).any? end def last_version last_migration.version end def last_migration #:nodoc: migrations(migrations_paths).last || NullMigration.new end def migrations_paths @migrations_paths ||= ['db/migrate'] # just to not break things if someone uses: migration_path = some_string Array(@migrations_paths) end def migrations_path migrations_paths.first end def migrations(paths) paths = Array(paths) files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }] migrations = files.map do |file| version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first raise IllegalMigrationNameError.new(file) unless version version = version.to_i name = name.camelize MigrationProxy.new(name, version, file, scope) end migrations.sort_by(&:version) end private def move(direction, migrations_paths, steps) migrator = new(direction, migrations(migrations_paths)) start_index = migrator.migrations.index(migrator.current_migration) if start_index finish = migrator.migrations[start_index + steps] version = finish ? finish.version : 0 send(direction, migrations_paths, version) end end end def initialize(direction, migrations, target_version = nil) raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? @direction = direction @target_version = target_version @migrated_versions = nil @migrations = migrations validate(@migrations) Base.connection.initialize_schema_migrations_table end def current_version migrated.max || 0 end def current_migration migrations.detect { |m| m.version == current_version } end alias :current :current_migration def run migration = migrations.detect { |m| m.version == @target_version } raise UnknownMigrationVersionError.new(@target_version) if migration.nil? unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i)) begin execute_migration_in_transaction(migration, @direction) rescue => e canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : "" raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace end end end def migrate if !target && @target_version && @target_version > 0 raise UnknownMigrationVersionError.new(@target_version) end runnable.each do |migration| Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger begin execute_migration_in_transaction(migration, @direction) rescue => e canceled_msg = use_transaction?(migration) ? "this and " : "" raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace end end end def runnable runnable = migrations[start..finish] if up? runnable.reject { |m| ran?(m) } else # skip the last migration if we're headed down, but not ALL the way down runnable.pop if target runnable.find_all { |m| ran?(m) } end end def migrations down? ? @migrations.reverse : @migrations.sort_by(&:version) end def pending_migrations already_migrated = migrated migrations.reject { |m| already_migrated.include?(m.version) } end def migrated @migrated_versions ||= Set.new(self.class.get_all_versions) end private def ran?(migration) migrated.include?(migration.version.to_i) end def execute_migration_in_transaction(migration, direction) ddl_transaction(migration) do migration.migrate(direction) record_version_state_after_migrating(migration.version) end end def target migrations.detect { |m| m.version == @target_version } end def finish migrations.index(target) || migrations.size - 1 end def start up? ? 0 : (migrations.index(current) || 0) end def validate(migrations) name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 } raise DuplicateMigrationNameError.new(name) if name version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 } raise DuplicateMigrationVersionError.new(version) if version end def record_version_state_after_migrating(version) if down? migrated.delete(version) ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all else migrated << version ActiveRecord::SchemaMigration.create!(:version => version.to_s) end end def up? @direction == :up end def down? @direction == :down end # Wrap the migration in a transaction only if supported by the adapter. def ddl_transaction(migration) if use_transaction?(migration) Base.transaction { yield } else yield end end def use_transaction?(migration) !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? end end end rails-4.2.6/activerecord/lib/active_record/migration/000077500000000000000000000000001266740050600226655ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/migration/command_recorder.rb000066400000000000000000000144661266740050600265300ustar00rootroot00000000000000module ActiveRecord class Migration # ActiveRecord::Migration::CommandRecorder records commands done during # a migration and knows how to reverse those commands. The CommandRecorder # knows how to invert the following commands: # # * add_column # * add_index # * add_timestamps # * create_table # * create_join_table # * remove_timestamps # * rename_column # * rename_index # * rename_table class CommandRecorder include JoinTable attr_accessor :commands, :delegate, :reverting def initialize(delegate = nil) @commands = [] @delegate = delegate @reverting = false end # While executing the given block, the recorded will be in reverting mode. # All commands recorded will end up being recorded reverted # and in reverse order. # For example: # # recorder.revert{ recorder.record(:rename_table, [:old, :new]) } # # same effect as recorder.record(:rename_table, [:new, :old]) def revert @reverting = !@reverting previous = @commands @commands = [] yield ensure @commands = previous.concat(@commands.reverse) @reverting = !@reverting end # record +command+. +command+ should be a method name and arguments. # For example: # # recorder.record(:method_name, [:arg1, :arg2]) def record(*command, &block) if @reverting @commands << inverse_of(*command, &block) else @commands << (command << block) end end # Returns the inverse of the given command. For example: # # recorder.inverse_of(:rename_table, [:old, :new]) # # => [:rename_table, [:new, :old]] # # This method will raise an +IrreversibleMigration+ exception if it cannot # invert the +command+. def inverse_of(command, args, &block) method = :"invert_#{command}" raise IrreversibleMigration unless respond_to?(method, true) send(method, args, &block) end def respond_to?(*args) # :nodoc: super || delegate.respond_to?(*args) end [:create_table, :create_join_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column_default, :add_reference, :remove_reference, :transaction, :drop_join_table, :drop_table, :execute_block, :enable_extension, :change_column, :execute, :remove_columns, :change_column_null, :add_foreign_key, :remove_foreign_key # irreversible methods need to be here too ].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method}(*args, &block) # def create_table(*args, &block) record(:"#{method}", args, &block) # record(:create_table, args, &block) end # end EOV end alias :add_belongs_to :add_reference alias :remove_belongs_to :remove_reference def change_table(table_name, options = {}) # :nodoc: yield delegate.update_table_definition(table_name, self) end private module StraightReversions private { transaction: :transaction, execute_block: :execute_block, create_table: :drop_table, create_join_table: :drop_join_table, add_column: :remove_column, add_timestamps: :remove_timestamps, add_reference: :remove_reference, enable_extension: :disable_extension }.each do |cmd, inv| [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| class_eval <<-EOV, __FILE__, __LINE__ + 1 def invert_#{method}(args, &block) # def invert_create_table(args, &block) [:#{inverse}, args, block] # [:drop_table, args, block] end # end EOV end end end include StraightReversions def invert_drop_table(args, &block) if args.size == 1 && block == nil raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." end super end def invert_rename_table(args) [:rename_table, args.reverse] end def invert_remove_column(args) raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2 super end def invert_rename_index(args) [:rename_index, [args.first] + args.last(2).reverse] end def invert_rename_column(args) [:rename_column, [args.first] + args.last(2).reverse] end def invert_add_index(args) table, columns, options = *args options ||= {} index_name = options[:name] options_hash = index_name ? { name: index_name } : { column: columns } [:remove_index, [table, options_hash]] end def invert_remove_index(args) table, options = *args unless options && options.is_a?(Hash) && options[:column] raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." end options = options.dup [:add_index, [table, options.delete(:column), options]] end alias :invert_add_belongs_to :invert_add_reference alias :invert_remove_belongs_to :invert_remove_reference def invert_change_column_null(args) args[2] = !args[2] [:change_column_null, args] end def invert_add_foreign_key(args) from_table, to_table, add_options = args add_options ||= {} if add_options[:name] options = { name: add_options[:name] } elsif add_options[:column] options = { column: add_options[:column] } else options = to_table end [:remove_foreign_key, [from_table, options]] end # Forwards any missing method call to the \target. def method_missing(method, *args, &block) if @delegate.respond_to?(method) @delegate.send(method, *args, &block) else super end end end end end rails-4.2.6/activerecord/lib/active_record/migration/join_table.rb000066400000000000000000000005621266740050600253230ustar00rootroot00000000000000module ActiveRecord class Migration module JoinTable #:nodoc: private def find_join_table_name(table_1, table_2, options = {}) options.delete(:table_name) || join_table_name(table_1, table_2) end def join_table_name(table_1, table_2) ModelSchema.derive_join_table_name(table_1, table_2).to_sym end end end end rails-4.2.6/activerecord/lib/active_record/model_schema.rb000066400000000000000000000320571266740050600236500ustar00rootroot00000000000000module ActiveRecord module ModelSchema extend ActiveSupport::Concern included do ## # :singleton-method: # Accessor for the prefix type that will be prepended to every primary key column name. # The options are :table_name and :table_name_with_underscore. If the first is specified, # the Product class will look for "productid" instead of "id" as the primary column. If the # latter is specified, the Product class will look for "product_id" instead of "id". Remember # that this is a global setting for all Active Records. mattr_accessor :primary_key_prefix_type, instance_writer: false ## # :singleton-method: # Accessor for the name of the prefix string to prepend to every table name. So if set # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people", # etc. This is a convenient way of creating a namespace for tables in a shared database. # By default, the prefix is the empty string. # # If you are organising your models within modules you can add a prefix to the models within # a namespace by defining a singleton method in the parent module called table_name_prefix which # returns your chosen prefix. class_attribute :table_name_prefix, instance_writer: false self.table_name_prefix = "" ## # :singleton-method: # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp", # "people_basecamp"). By default, the suffix is the empty string. # # If you are organising your models within modules, you can add a suffix to the models within # a namespace by defining a singleton method in the parent module called table_name_suffix which # returns your chosen suffix. class_attribute :table_name_suffix, instance_writer: false self.table_name_suffix = "" ## # :singleton-method: # Accessor for the name of the schema migrations table. By default, the value is "schema_migrations" class_attribute :schema_migrations_table_name, instance_accessor: false self.schema_migrations_table_name = "schema_migrations" ## # :singleton-method: # Indicates whether table names should be the pluralized versions of the corresponding class names. # If true, the default table name for a Product class will be +products+. If false, it would just be +product+. # See table_name for the full rules on table/class naming. This is true, by default. class_attribute :pluralize_table_names, instance_writer: false self.pluralize_table_names = true self.inheritance_column = 'type' delegate :type_for_attribute, to: :class end # Derives the join table name for +first_table+ and +second_table+. The # table names appear in alphabetical order. A common prefix is removed # (useful for namespaced models like Music::Artist and Music::Record): # # artists, records => artists_records # records, artists => artists_records # music_artists, music_records => music_artists_records def self.derive_join_table_name(first_table, second_table) # :nodoc: [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_") end module ClassMethods # Guesses the table name (in forced lower-case) based on the name of the class in the # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy # looks like: Reply < Message < ActiveRecord::Base, then Message is used # to guess the table name even when called on Reply. The rules used to do the guess # are handled by the Inflector class in Active Support, which knows almost all common # English inflections. You can add new inflections in config/initializers/inflections.rb. # # Nested classes are given table names prefixed by the singular form of # the parent's table name. Enclosing modules are not considered. # # ==== Examples # # class Invoice < ActiveRecord::Base # end # # file class table_name # invoice.rb Invoice invoices # # class Invoice < ActiveRecord::Base # class Lineitem < ActiveRecord::Base # end # end # # file class table_name # invoice.rb Invoice::Lineitem invoice_lineitems # # module Invoice # class Lineitem < ActiveRecord::Base # end # end # # file class table_name # invoice/lineitem.rb Invoice::Lineitem lineitems # # Additionally, the class-level +table_name_prefix+ is prepended and the # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix, # the table name guess for an Invoice class becomes "myapp_invoices". # Invoice::Lineitem becomes "myapp_invoice_lineitems". # # You can also set your own table name explicitly: # # class Mouse < ActiveRecord::Base # self.table_name = "mice" # end # # Alternatively, you can override the table_name method to define your # own computation. (Possibly using super to manipulate the default # table name.) Example: # # class Post < ActiveRecord::Base # def self.table_name # "special_" + super # end # end # Post.table_name # => "special_posts" def table_name reset_table_name unless defined?(@table_name) @table_name end # Sets the table name explicitly. Example: # # class Project < ActiveRecord::Base # self.table_name = "project" # end # # You can also just define your own self.table_name method; see # the documentation for ActiveRecord::Base#table_name. def table_name=(value) value = value && value.to_s if defined?(@table_name) return if value == @table_name reset_column_information if connected? end @table_name = value @quoted_table_name = nil @arel_table = nil @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name @relation = Relation.create(self, arel_table) end # Returns a quoted version of the table name, used to construct SQL statements. def quoted_table_name @quoted_table_name ||= connection.quote_table_name(table_name) end # Computes the table name, (re)sets it internally, and returns it. def reset_table_name #:nodoc: self.table_name = if abstract_class? superclass == Base ? nil : superclass.table_name elsif superclass.abstract_class? superclass.table_name || compute_table_name else compute_table_name end end def full_table_name_prefix #:nodoc: (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix end def full_table_name_suffix #:nodoc: (parents.detect {|p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix end # Defines the name of the table column which will store the class name on single-table # inheritance situations. # # The default inheritance column name is +type+, which means it's a # reserved word inside Active Record. To be able to use single-table # inheritance with another column name, or to use the column +type+ in # your own model for something else, you can set +inheritance_column+: # # self.inheritance_column = 'zoink' def inheritance_column (@inheritance_column ||= nil) || superclass.inheritance_column end # Sets the value of inheritance_column def inheritance_column=(value) @inheritance_column = value.to_s @explicit_inheritance_column = true end def sequence_name if base_class == self @sequence_name ||= reset_sequence_name else (@sequence_name ||= nil) || base_class.sequence_name end end def reset_sequence_name #:nodoc: @explicit_sequence_name = false @sequence_name = connection.default_sequence_name(table_name, primary_key) end # Sets the name of the sequence to use when generating ids to the given # value, or (if the value is nil or false) to the value returned by the # given block. This is required for Oracle and is useful for any # database which relies on sequences for primary key generation. # # If a sequence name is not explicitly set when using Oracle, # it will default to the commonly used pattern of: #{table_name}_seq # # If a sequence name is not explicitly set when using PostgreSQL, it # will discover the sequence corresponding to your primary key for you. # # class Project < ActiveRecord::Base # self.sequence_name = "projectseq" # default would have been "project_seq" # end def sequence_name=(value) @sequence_name = value.to_s @explicit_sequence_name = true end # Indicates whether the table associated with this class exists def table_exists? connection.schema_cache.table_exists?(table_name) end def attributes_builder # :nodoc: @attributes_builder ||= AttributeSet::Builder.new(column_types, primary_key) end def column_types # :nodoc: @column_types ||= columns_hash.transform_values(&:cast_type).tap do |h| h.default = Type::Value.new end end def type_for_attribute(attr_name) # :nodoc: column_types[attr_name] end # Returns a hash where the keys are column names and the values are # default values when instantiating the AR object for this table. def column_defaults _default_attributes.to_hash end def _default_attributes # :nodoc: @default_attributes ||= attributes_builder.build_from_database( raw_default_values) end # Returns an array of column names as strings. def column_names @column_names ||= columns.map { |column| column.name } end # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count", # and columns used for single table inheritance have been removed. def content_columns @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column } end # Resets all the cached information about columns, which will cause them # to be reloaded on the next request. # # The most common usage pattern for this method is probably in a migration, # when just after creating a table you want to populate it with some default # values, eg: # # class CreateJobLevels < ActiveRecord::Migration # def up # create_table :job_levels do |t| # t.integer :id # t.string :name # # t.timestamps # end # # JobLevel.reset_column_information # %w{assistant executive manager director}.each do |type| # JobLevel.create(name: type) # end # end # # def down # drop_table :job_levels # end # end def reset_column_information connection.clear_cache! undefine_attribute_methods connection.schema_cache.clear_table_cache!(table_name) @arel_engine = nil @column_names = nil @column_types = nil @content_columns = nil @default_attributes = nil @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column @relation = nil end private # Guesses the table name, but does not decorate it with prefix and suffix information. def undecorated_table_name(class_name = base_class.name) table_name = class_name.to_s.demodulize.underscore pluralize_table_names ? table_name.pluralize : table_name end # Computes and returns a table name according to default conventions. def compute_table_name base = base_class if self == base # Nested classes are prefixed with singular parent table name. if parent < Base && !parent.abstract_class? contained = parent.table_name contained = contained.singularize if parent.pluralize_table_names contained += '_' end "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}" else # STI subclasses always use their superclass' table. base.table_name end end def raw_default_values columns_hash.transform_values(&:default) end end end end rails-4.2.6/activerecord/lib/active_record/nested_attributes.rb000066400000000000000000000567411266740050600247660ustar00rootroot00000000000000require 'active_support/core_ext/hash/except' require 'active_support/core_ext/object/try' require 'active_support/core_ext/hash/indifferent_access' module ActiveRecord module NestedAttributes #:nodoc: class TooManyRecords < ActiveRecordError end extend ActiveSupport::Concern included do class_attribute :nested_attributes_options, instance_writer: false self.nested_attributes_options = {} end # = Active Record Nested Attributes # # Nested attributes allow you to save attributes on associated records # through the parent. By default nested attribute updating is turned off # and you can enable it using the accepts_nested_attributes_for class # method. When you enable nested attributes an attribute writer is # defined on the model. # # The attribute writer is named after the association, which means that # in the following example, two new methods are added to your model: # # author_attributes=(attributes) and # pages_attributes=(attributes). # # class Book < ActiveRecord::Base # has_one :author # has_many :pages # # accepts_nested_attributes_for :author, :pages # end # # Note that the :autosave option is automatically enabled on every # association that accepts_nested_attributes_for is used for. # # === One-to-one # # Consider a Member model that has one Avatar: # # class Member < ActiveRecord::Base # has_one :avatar # accepts_nested_attributes_for :avatar # end # # Enabling nested attributes on a one-to-one association allows you to # create the member and avatar in one go: # # params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } } # member = Member.create(params[:member]) # member.avatar.id # => 2 # member.avatar.icon # => 'smiling' # # It also allows you to update the avatar through the member: # # params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } } # member.update params[:member] # member.avatar.icon # => 'sad' # # By default you will only be able to set and update attributes on the # associated model. If you want to destroy the associated model through the # attributes hash, you have to enable it first using the # :allow_destroy option. # # class Member < ActiveRecord::Base # has_one :avatar # accepts_nested_attributes_for :avatar, allow_destroy: true # end # # Now, when you add the _destroy key to the attributes hash, with a # value that evaluates to +true+, you will destroy the associated model: # # member.avatar_attributes = { id: '2', _destroy: '1' } # member.avatar.marked_for_destruction? # => true # member.save # member.reload.avatar # => nil # # Note that the model will _not_ be destroyed until the parent is saved. # # === One-to-many # # Consider a member that has a number of posts: # # class Member < ActiveRecord::Base # has_many :posts # accepts_nested_attributes_for :posts # end # # You can now set or update attributes on the associated posts through # an attribute hash for a member: include the key +:posts_attributes+ # with an array of hashes of post attributes as a value. # # For each hash that does _not_ have an id key a new record will # be instantiated, unless the hash also contains a _destroy key # that evaluates to +true+. # # params = { member: { # name: 'joe', posts_attributes: [ # { title: 'Kari, the awesome Ruby documentation browser!' }, # { title: 'The egalitarian assumption of the modern citizen' }, # { title: '', _destroy: '1' } # this will be ignored # ] # }} # # member = Member.create(params[:member]) # member.posts.length # => 2 # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' # # You may also set a :reject_if proc to silently ignore any new record # hashes if they fail to pass your criteria. For example, the previous # example could be rewritten as: # # class Member < ActiveRecord::Base # has_many :posts # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? } # end # # params = { member: { # name: 'joe', posts_attributes: [ # { title: 'Kari, the awesome Ruby documentation browser!' }, # { title: 'The egalitarian assumption of the modern citizen' }, # { title: '' } # this will be ignored because of the :reject_if proc # ] # }} # # member = Member.create(params[:member]) # member.posts.length # => 2 # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' # # Alternatively, :reject_if also accepts a symbol for using methods: # # class Member < ActiveRecord::Base # has_many :posts # accepts_nested_attributes_for :posts, reject_if: :new_record? # end # # class Member < ActiveRecord::Base # has_many :posts # accepts_nested_attributes_for :posts, reject_if: :reject_posts # # def reject_posts(attributed) # attributed['title'].blank? # end # end # # If the hash contains an id key that matches an already # associated record, the matching record will be modified: # # member.attributes = { # name: 'Joe', # posts_attributes: [ # { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' }, # { id: 2, title: '[UPDATED] other post' } # ] # } # # member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' # member.posts.second.title # => '[UPDATED] other post' # # By default the associated records are protected from being destroyed. If # you want to destroy any of the associated records through the attributes # hash, you have to enable it first using the :allow_destroy # option. This will allow you to also use the _destroy key to # destroy existing records: # # class Member < ActiveRecord::Base # has_many :posts # accepts_nested_attributes_for :posts, allow_destroy: true # end # # params = { member: { # posts_attributes: [{ id: '2', _destroy: '1' }] # }} # # member.attributes = params[:member] # member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true # member.posts.length # => 2 # member.save # member.reload.posts.length # => 1 # # Nested attributes for an associated collection can also be passed in # the form of a hash of hashes instead of an array of hashes: # # Member.create(name: 'joe', # posts_attributes: { first: { title: 'Foo' }, # second: { title: 'Bar' } }) # # has the same effect as # # Member.create(name: 'joe', # posts_attributes: [ { title: 'Foo' }, # { title: 'Bar' } ]) # # The keys of the hash which is the value for +:posts_attributes+ are # ignored in this case. # However, it is not allowed to use +'id'+ or +:id+ for one of # such keys, otherwise the hash will be wrapped in an array and # interpreted as an attribute hash for a single post. # # Passing attributes for an associated collection in the form of a hash # of hashes can be used with hashes generated from HTTP/HTML parameters, # where there maybe no natural way to submit an array of hashes. # # === Saving # # All changes to models, including the destruction of those marked for # destruction, are saved and destroyed automatically and atomically when # the parent model is saved. This happens inside the transaction initiated # by the parents save method. See ActiveRecord::AutosaveAssociation. # # === Validating the presence of a parent model # # If you want to validate that a child record is associated with a parent # record, you can use validates_presence_of and # inverse_of as this example illustrates: # # class Member < ActiveRecord::Base # has_many :posts, inverse_of: :member # accepts_nested_attributes_for :posts # end # # class Post < ActiveRecord::Base # belongs_to :member, inverse_of: :posts # validates_presence_of :member # end # # Note that if you do not specify the inverse_of option, then # Active Record will try to automatically guess the inverse association # based on heuristics. # # For one-to-one nested associations, if you build the new (in-memory) # child object yourself before assignment, then this module will not # overwrite it, e.g.: # # class Member < ActiveRecord::Base # has_one :avatar # accepts_nested_attributes_for :avatar # # def avatar # super || build_avatar(width: 200) # end # end # # member = Member.new # member.avatar_attributes = {icon: 'sad'} # member.avatar.width # => 200 module ClassMethods REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } # Defines an attributes writer for the specified association(s). # # Supported options: # [:allow_destroy] # If true, destroys any members from the attributes hash with a # _destroy key and a value that evaluates to +true+ # (eg. 1, '1', true, or 'true'). This option is off by default. # [:reject_if] # Allows you to specify a Proc or a Symbol pointing to a method # that checks whether a record should be built for a certain attribute # hash. The hash is passed to the supplied Proc or the method # and it should return either +true+ or +false+. When no :reject_if # is specified, a record will be built for all attribute hashes that # do not have a _destroy value that evaluates to true. # Passing :all_blank instead of a Proc will create a proc # that will reject a record where all the attributes are blank excluding # any value for _destroy. # [:limit] # Allows you to specify the maximum number of the associated records that # can be processed with the nested attributes. Limit also can be specified as a # Proc or a Symbol pointing to a method that should return number. If the size of the # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords # exception is raised. If omitted, any number associations can be processed. # Note that the :limit option is only applicable to one-to-many associations. # [:update_only] # For a one-to-one association, this option allows you to specify how # nested attributes are to be used when an associated record already # exists. In general, an existing record may either be updated with the # new set of attribute values or be replaced by a wholly new record # containing those values. By default the :update_only option is +false+ # and the nested attributes are used to update the existing record only # if they include the record's :id value. Otherwise a new # record will be instantiated and used to replace the existing one. # However if the :update_only option is +true+, the nested attributes # are used to update the record's attributes always, regardless of # whether the :id is present. The option is ignored for collection # associations. # # Examples: # # creates avatar_attributes= # accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? } # # creates avatar_attributes= # accepts_nested_attributes_for :avatar, reject_if: :all_blank # # creates avatar_attributes= and posts_attributes= # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true def accepts_nested_attributes_for(*attr_names) options = { :allow_destroy => false, :update_only => false } options.update(attr_names.extract_options!) options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only) options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank attr_names.each do |association_name| if reflection = _reflect_on_association(association_name) reflection.autosave = true define_autosave_validation_callbacks(reflection) nested_attributes_options = self.nested_attributes_options.dup nested_attributes_options[association_name.to_sym] = options self.nested_attributes_options = nested_attributes_options type = (reflection.collection? ? :collection : :one_to_one) generate_association_writer(association_name, type) else raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" end end end private # Generates a writer method for this association. Serves as a point for # accessing the objects in the association. For example, this method # could generate the following: # # def pirate_attributes=(attributes) # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) # end # # This redirects the attempts to write objects in an association through # the helper methods defined below. Makes it seem like the nested # associations are just regular associations. def generate_association_writer(association_name, type) generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 if method_defined?(:#{association_name}_attributes=) remove_method(:#{association_name}_attributes=) end def #{association_name}_attributes=(attributes) assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) end eoruby end end # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's # used in conjunction with fields_for to build a form element for the # destruction of this association. # # See ActionView::Helpers::FormHelper::fields_for for more info. def _destroy marked_for_destruction? end private # Attribute hash keys that should not be assigned as normal attributes. # These hash keys are nested attributes implementation details. UNASSIGNABLE_KEYS = %w( id _destroy ) # Assigns the given attributes to the association. # # If an associated record does not yet exist, one will be instantiated. If # an associated record already exists, the method's behavior depends on # the value of the update_only option. If update_only is +false+ and the # given attributes include an :id that matches the existing record's # id, then the existing record will be modified. If no :id is provided # it will be replaced with a new record. If update_only is +true+ the existing # record will be modified regardless of whether an :id is provided. # # If the given attributes include a matching :id attribute, or # update_only is true, and a :_destroy key set to a truthy value, # then the existing record will be marked for destruction. def assign_nested_attributes_for_one_to_one_association(association_name, attributes) options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access existing_record = send(association_name) if (options[:update_only] || !attributes['id'].blank?) && existing_record && (options[:update_only] || existing_record.id.to_s == attributes['id'].to_s) assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) elsif attributes['id'].present? raise_nested_attributes_record_not_found!(association_name, attributes['id']) elsif !reject_new_record?(association_name, attributes) assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) if existing_record && existing_record.new_record? existing_record.assign_attributes(assignable_attributes) association(association_name).initialize_attributes(existing_record) else method = "build_#{association_name}" if respond_to?(method) send(method, assignable_attributes) else raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" end end end end # Assigns the given attributes to the collection association. # # Hashes with an :id value matching an existing associated record # will update that record. Hashes without an :id value will build # a new record for the association. Hashes with a matching :id # value and a :_destroy key set to a truthy value will mark the # matched record for destruction. # # For example: # # assign_nested_attributes_for_collection_association(:people, { # '1' => { id: '1', name: 'Peter' }, # '2' => { name: 'John' }, # '3' => { id: '2', _destroy: true } # }) # # Will update the name of the Person with ID 1, build a new associated # person with the name 'John', and mark the associated Person with ID 2 # for destruction. # # Also accepts an Array of attribute hashes: # # assign_nested_attributes_for_collection_association(:people, [ # { id: '1', name: 'Peter' }, # { name: 'John' }, # { id: '2', _destroy: true } # ]) def assign_nested_attributes_for_collection_association(association_name, attributes_collection) options = self.nested_attributes_options[association_name] unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" end check_record_limit!(options[:limit], attributes_collection) if attributes_collection.is_a? Hash keys = attributes_collection.keys attributes_collection = if keys.include?('id') || keys.include?(:id) [attributes_collection] else attributes_collection.values end end association = association(association_name) existing_records = if association.loaded? association.target else attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) end attributes_collection.each do |attributes| attributes = attributes.with_indifferent_access if attributes['id'].blank? unless reject_new_record?(association_name, attributes) association.build(attributes.except(*UNASSIGNABLE_KEYS)) end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } unless call_reject_if(association_name, attributes) # Make sure we are operating on the actual object which is in the association's # proxy_target array (either by finding it, or adding it if not found) # Take into account that the proxy_target may have changed due to callbacks target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s } if target_record existing_record = target_record else association.add_to_target(existing_record, :skip_callbacks) end assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end else raise_nested_attributes_record_not_found!(association_name, attributes['id']) end end end # Takes in a limit and checks if the attributes_collection has too many # records. It accepts limit in the form of symbol, proc, or # number-like object (anything that can be compared with an integer). # # Raises TooManyRecords error if the attributes_collection is # larger than the limit. def check_record_limit!(limit, attributes_collection) if limit limit = case limit when Symbol send(limit) when Proc limit.call else limit end if limit && attributes_collection.size > limit raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." end end end # Updates a record with the +attributes+ or marks it for destruction if # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy end # Determines if a hash contains a truthy _destroy key. def has_destroy_flag?(hash) Type::Boolean.new.type_cast_from_user(hash['_destroy']) end # Determines if a new record should be rejected by checking # has_destroy_flag? or if a :reject_if proc exists for this # association and evaluates to +true+. def reject_new_record?(association_name, attributes) will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) end # Determines if a record with the particular +attributes+ should be # rejected by calling the reject_if Symbol or Proc (if defined). # The reject_if option is defined by +accepts_nested_attributes_for+. # # Returns false if there is a +destroy_flag+ on the attributes. def call_reject_if(association_name, attributes) return false if will_be_destroyed?(association_name, attributes) case callback = self.nested_attributes_options[association_name][:reject_if] when Symbol method(callback).arity == 0 ? send(callback) : send(callback, attributes) when Proc callback.call(attributes) end end # Only take into account the destroy flag if :allow_destroy is true def will_be_destroyed?(association_name, attributes) allow_destroy?(association_name) && has_destroy_flag?(attributes) end def allow_destroy?(association_name) self.nested_attributes_options[association_name][:allow_destroy] end def raise_nested_attributes_record_not_found!(association_name, record_id) raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}" end end end rails-4.2.6/activerecord/lib/active_record/no_touching.rb000066400000000000000000000022311266740050600235330ustar00rootroot00000000000000module ActiveRecord # = Active Record No Touching module NoTouching extend ActiveSupport::Concern module ClassMethods # Lets you selectively disable calls to `touch` for the # duration of a block. # # ==== Examples # ActiveRecord::Base.no_touching do # Project.first.touch # does nothing # Message.first.touch # does nothing # end # # Project.no_touching do # Project.first.touch # does nothing # Message.first.touch # works, but does not touch the associated project # end # def no_touching(&block) NoTouching.apply_to(self, &block) end end class << self def apply_to(klass) #:nodoc: klasses.push(klass) yield ensure klasses.pop end def applied_to?(klass) #:nodoc: klasses.any? { |k| k >= klass } end private def klasses Thread.current[:no_touching_classes] ||= [] end end def no_touching? NoTouching.applied_to?(self.class) end def touch(*) # :nodoc: super unless no_touching? end end end rails-4.2.6/activerecord/lib/active_record/null_relation.rb000066400000000000000000000023771266740050600241010ustar00rootroot00000000000000# -*- coding: utf-8 -*- module ActiveRecord module NullRelation # :nodoc: def exec_queries @records = [] end def pluck(*column_names) [] end def delete_all(_conditions = nil) 0 end def update_all(_updates, _conditions = nil, _options = {}) 0 end def delete(_id_or_array) 0 end def size calculate :size, nil end def empty? true end def any? false end def many? false end def to_sql "" end def count(*) calculate :count, nil end def sum(*) calculate :sum, nil end def average(*) calculate :average, nil end def minimum(*) calculate :minimum, nil end def maximum(*) calculate :maximum, nil end def calculate(operation, _column_name, _options = {}) # TODO: Remove _options argument as soon we remove support to # activerecord-deprecated_finders. if [:count, :sum, :size].include? operation group_values.any? ? Hash.new : 0 elsif [:average, :minimum, :maximum].include?(operation) && group_values.any? Hash.new else nil end end def exists?(_id = false) false end end end rails-4.2.6/activerecord/lib/active_record/persistence.rb000066400000000000000000000477441266740050600235650ustar00rootroot00000000000000module ActiveRecord # = Active Record Persistence module Persistence extend ActiveSupport::Concern module ClassMethods # Creates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the # attributes on the objects that are to be created. # # ==== Examples # # Create a single new object # User.create(first_name: 'Jamie') # # # Create an Array of new objects # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) # # # Create a single object and pass it into a block to set other attributes. # User.create(first_name: 'Jamie') do |u| # u.is_admin = false # end # # # Creating an Array of new objects using a block, where the block is executed for each object: # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u| # u.is_admin = false # end def create(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create(attr, &block) } else object = new(attributes, &block) object.save object end end # Creates an object (or multiple objects) and saves it to the database, # if validations pass. Raises a RecordInvalid error if validations fail, # unlike Base#create. # # The +attributes+ parameter can be either a Hash or an Array of Hashes. # These describe which attributes to be created on the object, or # multiple objects when given an Array of Hashes. def create!(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create!(attr, &block) } else object = new(attributes, &block) object.save! object end end # Given an attributes hash, +instantiate+ returns a new instance of # the appropriate class. Accepts only keys as strings. # # For example, +Post.all+ may return Comments, Messages, and Emails # by storing the record's subclass in a +type+ attribute. By calling # +instantiate+ instead of +new+, finder methods ensure they get new # instances of the appropriate class for each record. # # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see # how this "single-table" inheritance mapping is implemented. def instantiate(attributes, column_types = {}) klass = discriminate_class_for_record(attributes) attributes = klass.attributes_builder.build_from_database(attributes, column_types) klass.allocate.init_with('attributes' => attributes, 'new_record' => false) end private # Called by +instantiate+ to decide which class to use for a new # record instance. # # See +ActiveRecord::Inheritance#discriminate_class_for_record+ for # the single-table inheritance discriminator. def discriminate_class_for_record(record) self end end # Returns true if this object hasn't been saved yet -- that is, a record # for the object doesn't exist in the database yet; otherwise, returns false. def new_record? sync_with_transaction_state @new_record end # Returns true if this object has been destroyed, otherwise returns false. def destroyed? sync_with_transaction_state @destroyed end # Returns true if the record is persisted, i.e. it's not a new record and it was # not destroyed, otherwise returns false. def persisted? !(new_record? || destroyed?) end # Saves the model. # # If the model is new a record gets created in the database, otherwise # the existing record gets updated. # # By default, save always run validations. If any of them fail the action # is cancelled and +save+ returns +false+. However, if you supply # validate: false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # # There's a series of callbacks associated with +save+. If any of the # before_* callbacks return +false+ the action is cancelled and # +save+ returns +false+. See ActiveRecord::Callbacks for further # details. # # Attributes marked as readonly are silently ignored if the record is # being updated. def save(*) create_or_update rescue ActiveRecord::RecordInvalid false end # Saves the model. # # If the model is new a record gets created in the database, otherwise # the existing record gets updated. # # With save! validations always run. If any of them fail # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations # for more information. # # There's a series of callbacks associated with save!. If any of # the before_* callbacks return +false+ the action is cancelled # and save! raises ActiveRecord::RecordNotSaved. See # ActiveRecord::Callbacks for further details. # # Attributes marked as readonly are silently ignored if the record is # being updated. def save!(*) create_or_update || raise(RecordNotSaved.new("Failed to save the record", self)) end # Deletes the record in the database and freezes this instance to # reflect that no changes should be made (since they can't be # persisted). Returns the frozen instance. # # The row is simply removed with an SQL +DELETE+ statement on the # record's primary key, and no callbacks are executed. # # To enforce the object's +before_destroy+ and +after_destroy+ # callbacks or any :dependent association # options, use #destroy. def delete self.class.delete(id) if persisted? @destroyed = true freeze end # Deletes the record in the database and freezes this instance to reflect # that no changes should be made (since they can't be persisted). # # There's a series of callbacks associated with destroy. If # the before_destroy callback return +false+ the action is cancelled # and destroy returns +false+. See # ActiveRecord::Callbacks for further details. def destroy raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? destroy_associations self.class.connection.add_transaction_record(self) destroy_row if persisted? @destroyed = true freeze end # Deletes the record in the database and freezes this instance to reflect # that no changes should be made (since they can't be persisted). # # There's a series of callbacks associated with destroy!. If # the before_destroy callback return +false+ the action is cancelled # and destroy! raises ActiveRecord::RecordNotDestroyed. See # ActiveRecord::Callbacks for further details. def destroy! destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self)) end # Returns an instance of the specified +klass+ with the attributes of the # current record. This is mostly useful in relation to single-table # inheritance structures where you want a subclass to appear as the # superclass. This can be used along with record identification in # Action Pack to allow, say, Client < Company to do something # like render partial: @client.becomes(Company) to render that # instance using the companies/company partial instead of clients/client. # # Note: The new instance will share a link to the same attributes as the original class. # So any change to the attributes in either instance will affect the other. def becomes(klass) became = klass.new became.instance_variable_set("@attributes", @attributes) changed_attributes = @changed_attributes if defined?(@changed_attributes) became.instance_variable_set("@changed_attributes", changed_attributes || {}) became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) became.instance_variable_set("@errors", errors) became end # Wrapper around +becomes+ that also changes the instance's sti column value. # This is especially useful if you want to persist the changed class in your # database. # # Note: The old instance's sti column value will be changed too, as both objects # share the same set of attributes. def becomes!(klass) became = becomes(klass) sti_type = nil if !klass.descends_from_active_record? sti_type = klass.sti_name end became.public_send("#{klass.inheritance_column}=", sti_type) became end # Updates a single attribute and saves the record. # This is especially useful for boolean flags on existing records. Also note that # # * Validation is skipped. # * Callbacks are invoked. # * updated_at/updated_on column is updated if that column is available. # * Updates all the attributes that are dirty in this object. # # This method raises an +ActiveRecord::ActiveRecordError+ if the # attribute is marked as readonly. # # See also +update_column+. def update_attribute(name, value) name = name.to_s verify_readonly_attribute(name) send("#{name}=", value) save(validate: false) end # Updates the attributes of the model from the passed-in hash and saves the # record, all wrapped in a transaction. If the object is invalid, the saving # will fail and false will be returned. def update(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do assign_attributes(attributes) save end end alias update_attributes update # Updates its receiver just like +update+ but calls save! instead # of +save+, so an exception is raised if the record is invalid. def update!(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do assign_attributes(attributes) save! end end alias update_attributes! update! # Equivalent to update_columns(name => value). def update_column(name, value) update_columns(name => value) end # Updates the attributes directly in the database issuing an UPDATE SQL # statement and sets them in the receiver: # # user.update_columns(last_request_at: Time.current) # # This is the fastest way to update attributes because it goes straight to # the database, but take into account that in consequence the regular update # procedures are totally bypassed. In particular: # # * Validations are skipped. # * Callbacks are skipped. # * +updated_at+/+updated_on+ are not updated. # # This method raises an +ActiveRecord::ActiveRecordError+ when called on new # objects, or when at least one of the attributes is marked as readonly. def update_columns(attributes) raise ActiveRecordError, "cannot update a new record" if new_record? raise ActiveRecordError, "cannot update a destroyed record" if destroyed? attributes.each_key do |key| verify_readonly_attribute(key.to_s) end updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes) attributes.each do |k, v| raw_write_attribute(k, v) end updated_count == 1 end # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1). # The increment is performed directly on the underlying attribute, no setter is invoked. # Only makes sense for number-based attributes. Returns +self+. def increment(attribute, by = 1) self[attribute] ||= 0 self[attribute] += by self end # Wrapper around +increment+ that saves the record. This method differs from # its non-bang version in that it passes through the attribute setter. # Saving is not subjected to validation checks. Returns +true+ if the # record could be saved. def increment!(attribute, by = 1) increment(attribute, by).update_attribute(attribute, self[attribute]) end # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1). # The decrement is performed directly on the underlying attribute, no setter is invoked. # Only makes sense for number-based attributes. Returns +self+. def decrement(attribute, by = 1) self[attribute] ||= 0 self[attribute] -= by self end # Wrapper around +decrement+ that saves the record. This method differs from # its non-bang version in that it passes through the attribute setter. # Saving is not subjected to validation checks. Returns +true+ if the # record could be saved. def decrement!(attribute, by = 1) decrement(attribute, by).update_attribute(attribute, self[attribute]) end # Assigns to +attribute+ the boolean opposite of attribute?. So # if the predicate returns +true+ the attribute will become +false+. This # method toggles directly the underlying value without calling any setter. # Returns +self+. def toggle(attribute) self[attribute] = !send("#{attribute}?") self end # Wrapper around +toggle+ that saves the record. This method differs from # its non-bang version in that it passes through the attribute setter. # Saving is not subjected to validation checks. Returns +true+ if the # record could be saved. def toggle!(attribute) toggle(attribute).update_attribute(attribute, self[attribute]) end # Reloads the record from the database. # # This method finds record by its primary key (which could be assigned manually) and # modifies the receiver in-place: # # account = Account.new # # => # # account.id = 1 # account.reload # # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]] # # => # # # Attributes are reloaded from the database, and caches busted, in # particular the associations cache and the QueryCache. # # If the record no longer exists in the database ActiveRecord::RecordNotFound # is raised. Otherwise, in addition to the in-place modification the method # returns +self+ for convenience. # # The optional :lock flag option allows you to lock the reloaded record: # # reload(lock: true) # reload with pessimistic locking # # Reloading is commonly used in test suites to test something is actually # written to the database, or when some action modifies the corresponding # row in the database but not the object in memory: # # assert account.deposit!(25) # assert_equal 25, account.credit # check it is updated in memory # assert_equal 25, account.reload.credit # check it is also persisted # # Another common use case is optimistic locking handling: # # def with_optimistic_retry # begin # yield # rescue ActiveRecord::StaleObjectError # begin # # Reload lock_version in particular. # reload # rescue ActiveRecord::RecordNotFound # # If the record is gone there is nothing to do. # else # retry # end # end # end # def reload(options = nil) clear_aggregation_cache clear_association_cache self.class.connection.clear_query_cache fresh_object = if options && options[:lock] self.class.unscoped { self.class.lock(options[:lock]).find(id) } else self.class.unscoped { self.class.find(id) } end @attributes = fresh_object.instance_variable_get('@attributes') @new_record = false self end # Saves the record with the updated_at/on attributes set to the current time. # Please note that no validation is performed and only the +after_touch+, # +after_commit+ and +after_rollback+ callbacks are executed. # # If attribute names are passed, they are updated along with updated_at/on # attributes. # # product.touch # updates updated_at/on # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes # # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on # associated object. # # class Brake < ActiveRecord::Base # belongs_to :car, touch: true # end # # class Car < ActiveRecord::Base # belongs_to :corporation, touch: true # end # # # triggers @brake.car.touch and @brake.car.corporation.touch # @brake.touch # # Note that +touch+ must be used on a persisted object, or else an # ActiveRecordError will be thrown. For example: # # ball = Ball.new # ball.touch(:updated_at) # => raises ActiveRecordError # def touch(*names) raise ActiveRecordError, "cannot touch on a new record object" unless persisted? attributes = timestamp_attributes_for_update_in_model attributes.concat(names) unless attributes.empty? current_time = current_time_from_proper_timezone changes = {} attributes.each do |column| column = column.to_s changes[column] = write_attribute(column, current_time) end changes[self.class.locking_column] = increment_lock if locking_enabled? clear_attribute_changes(changes.keys) primary_key = self.class.primary_key self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1 else true end end private # A hook to be overridden by association modules. def destroy_associations end def destroy_row relation_for_destroy.delete_all end def relation_for_destroy pk = self.class.primary_key column = self.class.columns_hash[pk] substitute = self.class.connection.substitute_at(column) relation = self.class.unscoped.where( self.class.arel_table[pk].eq(substitute)) relation.bind_values = [[column, id]] relation end def create_or_update raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? result = new_record? ? _create_record : _update_record result != false end # Updates the associated record with values matching those of the instance attributes. # Returns the number of affected rows. def _update_record(attribute_names = self.attribute_names) attributes_values = arel_attributes_with_values_for_update(attribute_names) if attributes_values.empty? 0 else self.class.unscoped._update_record attributes_values, id, id_was end end # Creates a record with values matching those of the instance attributes # and returns its id. def _create_record(attribute_names = self.attribute_names) attributes_values = arel_attributes_with_values_for_create(attribute_names) new_id = self.class.unscoped.insert attributes_values self.id ||= new_id if self.class.primary_key @new_record = false id end def verify_readonly_attribute(name) raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name) end end end rails-4.2.6/activerecord/lib/active_record/query_cache.rb000066400000000000000000000027411266740050600235150ustar00rootroot00000000000000module ActiveRecord # = Active Record Query Cache class QueryCache module ClassMethods # Enable the query cache within the block if Active Record is configured. # If it's not, it will execute the given block. def cache(&block) if ActiveRecord::Base.connected? connection.cache(&block) else yield end end # Disable the query cache within the block if Active Record is configured. # If it's not, it will execute the given block. def uncached(&block) if ActiveRecord::Base.connected? connection.uncached(&block) else yield end end end def initialize(app) @app = app end def call(env) connection = ActiveRecord::Base.connection enabled = connection.query_cache_enabled connection_id = ActiveRecord::Base.connection_id connection.enable_query_cache! response = @app.call(env) response[2] = Rack::BodyProxy.new(response[2]) do restore_query_cache_settings(connection_id, enabled) end response rescue Exception => e restore_query_cache_settings(connection_id, enabled) raise e end private def restore_query_cache_settings(connection_id, enabled) ActiveRecord::Base.connection_id = connection_id ActiveRecord::Base.connection.clear_query_cache ActiveRecord::Base.connection.disable_query_cache! unless enabled end end end rails-4.2.6/activerecord/lib/active_record/querying.rb000066400000000000000000000073301266740050600230670ustar00rootroot00000000000000module ActiveRecord module Querying delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all delegate :find_by, :find_by!, to: :all delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all delegate :find_each, :find_in_batches, to: :all delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all delegate :pluck, :ids, to: :all # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call # this method from. If you call Product.find_by_sql then the results will be returned in # a +Product+ object with the attributes you specified in the SQL query. # # If you call a complicated SQL query which spans multiple tables the columns specified by the # SELECT will be attributes of the model, whether or not they are columns of the corresponding # table. # # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be # no database agnostic conversions performed. This should be a last resort because using, for example, # MySQL specific terms will lock you to using that particular database engine or require you to # change your call if you switch engines. # # # A simple SQL query spanning multiple tables # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id" # # => [#"Ruby Meetup", "first_name"=>"Quentin"}>, ...] # # You can use the same string replacement techniques as you can with ActiveRecord::QueryMethods#where: # # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date] # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }] def find_by_sql(sql, binds = []) result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) column_types = result_set.column_types.dup columns_hash.each_key { |k| column_types.delete k } message_bus = ActiveSupport::Notifications.instrumenter payload = { record_count: result_set.length, class_name: name } message_bus.instrument('instantiation.active_record', payload) do result_set.map { |record| instantiate(record, column_types) } end end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. # The use of this method should be restricted to complicated SQL queries that can't be executed # using the ActiveRecord::Calculations class methods. Look into those before using this. # # ==== Parameters # # * +sql+ - An SQL statement which should return a count query from the database, see the example below. # # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" def count_by_sql(sql) sql = sanitize_conditions(sql) connection.select_value(sql, "#{name} Count").to_i end end end rails-4.2.6/activerecord/lib/active_record/railtie.rb000066400000000000000000000127731266740050600226640ustar00rootroot00000000000000require "active_record" require "rails" require "active_model/railtie" # For now, action_controller must always be present with # rails, so let's make sure that it gets required before # here. This is needed for correctly setting up the middleware. # In the future, this might become an optional require. require "action_controller/railtie" module ActiveRecord # = Active Record Railtie class Railtie < Rails::Railtie # :nodoc: config.active_record = ActiveSupport::OrderedOptions.new config.app_generators.orm :active_record, :migration => true, :timestamps => true config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::QueryCache" config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::ConnectionAdapters::ConnectionManagement" config.action_dispatch.rescue_responses.merge!( 'ActiveRecord::RecordNotFound' => :not_found, 'ActiveRecord::StaleObjectError' => :conflict, 'ActiveRecord::RecordInvalid' => :unprocessable_entity, 'ActiveRecord::RecordNotSaved' => :unprocessable_entity ) config.active_record.use_schema_cache_dump = true config.active_record.maintain_test_schema = true config.eager_load_namespaces << ActiveRecord rake_tasks do namespace :db do task :load_config do ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) if engine.paths['db/migrate'].existent ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a end end end end load "active_record/railties/databases.rake" end # When loading console, force ActiveRecord::Base to be loaded # to avoid cross references when loading a constant for the # first time. Also, make it output to STDERR. console do |app| require "active_record/railties/console_sandbox" if app.sandbox? require "active_record/base" console = ActiveSupport::Logger.new(STDERR) Rails.logger.extend ActiveSupport::Logger.broadcast console end runner do require "active_record/base" end initializer "active_record.initialize_timezone" do ActiveSupport.on_load(:active_record) do self.time_zone_aware_attributes = true self.default_timezone = :utc end end initializer "active_record.logger" do ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger } end initializer "active_record.migration_error" do if config.active_record.delete(:migration_error) == :page_load config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::Migration::CheckPending" end end initializer "active_record.check_schema_cache_dump" do if config.active_record.delete(:use_schema_cache_dump) config.after_initialize do |app| ActiveSupport.on_load(:active_record) do filename = File.join(app.config.paths["db"].first, "schema_cache.dump") if File.file?(filename) cache = Marshal.load File.binread filename if cache.version == ActiveRecord::Migrator.current_version self.connection.schema_cache = cache else warn "Ignoring db/schema_cache.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}." end end end end end end initializer "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do app.config.active_record.each do |k,v| send "#{k}=", v end end end # This sets the database configuration from Configuration#database_configuration # and then establishes the connection. initializer "active_record.initialize_database" do |app| ActiveSupport.on_load(:active_record) do self.configurations = Rails.application.config.database_configuration begin establish_connection rescue ActiveRecord::NoDatabaseError warn <<-end_warning Oops - You have a database configured, but it doesn't exist yet! Here's how to get started: 1. Configure your database in config/database.yml. 2. Run `bin/rake db:create` to create the database. 3. Run `bin/rake db:setup` to load your database schema. end_warning raise end end end # Expose database runtime to controller for logging. initializer "active_record.log_runtime" do require "active_record/railties/controller_runtime" ActiveSupport.on_load(:action_controller) do include ActiveRecord::Railties::ControllerRuntime end end initializer "active_record.set_reloader_hooks" do |app| hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup ActiveSupport.on_load(:active_record) do ActionDispatch::Reloader.send(hook) do if ActiveRecord::Base.connected? ActiveRecord::Base.clear_cache! ActiveRecord::Base.clear_reloadable_connections! end end end end initializer "active_record.add_watchable_files" do |app| path = app.paths["db"].first config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"] end end end rails-4.2.6/activerecord/lib/active_record/railties/000077500000000000000000000000001266740050600225105ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/railties/console_sandbox.rb000066400000000000000000000002061266740050600262130ustar00rootroot00000000000000ActiveRecord::Base.connection.begin_transaction(joinable: false) at_exit do ActiveRecord::Base.connection.rollback_transaction end rails-4.2.6/activerecord/lib/active_record/railties/controller_runtime.rb000066400000000000000000000027541266740050600267730ustar00rootroot00000000000000require 'active_support/core_ext/module/attr_internal' require 'active_record/log_subscriber' module ActiveRecord module Railties # :nodoc: module ControllerRuntime #:nodoc: extend ActiveSupport::Concern protected attr_internal :db_runtime def process_action(action, *args) # We also need to reset the runtime before each action # because of queries in middleware or in cases we are streaming # and it won't be cleaned up by the method below. ActiveRecord::LogSubscriber.reset_runtime super end def cleanup_view_runtime if ActiveRecord::Base.connected? db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime self.db_runtime = (db_runtime || 0) + db_rt_before_render runtime = super db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime self.db_runtime += db_rt_after_render runtime - db_rt_after_render else super end end def append_info_to_payload(payload) super if ActiveRecord::Base.connected? payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime end end module ClassMethods # :nodoc: def log_process_action(payload) messages, db_runtime = super, payload[:db_runtime] messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime messages end end end end end rails-4.2.6/activerecord/lib/active_record/railties/databases.rake000066400000000000000000000367041266740050600253150ustar00rootroot00000000000000require 'active_record' db_namespace = namespace :db do task :load_config do ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {} ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths end namespace :create do task :all => :load_config do ActiveRecord::Tasks::DatabaseTasks.create_all end end desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV it defaults to creating the development and test databases.' task :create => [:load_config] do ActiveRecord::Tasks::DatabaseTasks.create_current end namespace :drop do task :all => :load_config do ActiveRecord::Tasks::DatabaseTasks.drop_all end end desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to dropping the development and test databases.' task :drop => [:load_config] do ActiveRecord::Tasks::DatabaseTasks.drop_current end namespace :purge do task :all => :load_config do ActiveRecord::Tasks::DatabaseTasks.purge_all end end # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases." task :purge => [:load_config] do ActiveRecord::Tasks::DatabaseTasks.purge_current end desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." task :migrate => [:environment, :load_config] do ActiveRecord::Tasks::DatabaseTasks.migrate db_namespace['_dump'].invoke end # IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false task :_dump do if ActiveRecord::Base.dump_schema_after_migration case ActiveRecord::Base.schema_format when :ruby then db_namespace["schema:dump"].invoke when :sql then db_namespace["structure:dump"].invoke else raise "unknown schema format #{ActiveRecord::Base.schema_format}" end end # Allow this task to be called as many times as required. An example is the # migrate:redo task, which calls other two internally that depend on this one. db_namespace['_dump'].reenable end namespace :migrate do # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' task :redo => [:environment, :load_config] do if ENV['VERSION'] db_namespace['migrate:down'].invoke db_namespace['migrate:up'].invoke else db_namespace['rollback'].invoke db_namespace['migrate'].invoke end end # desc 'Resets your database using your migrations for the current environment' task :reset => ['db:drop', 'db:create', 'db:migrate'] # desc 'Runs the "up" for a given migration VERSION.' task :up => [:environment, :load_config] do version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil raise 'VERSION is required' unless version ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version) db_namespace['_dump'].invoke end # desc 'Runs the "down" for a given migration VERSION.' task :down => [:environment, :load_config] do version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil raise 'VERSION is required - To go down one migration, run db:rollback' unless version ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version) db_namespace['_dump'].invoke end desc 'Display status of migrations' task :status => [:environment, :load_config] do unless ActiveRecord::SchemaMigration.table_exists? abort 'Schema migrations table does not exist yet.' end db_list = ActiveRecord::SchemaMigration.normalized_versions file_list = ActiveRecord::Migrator.migrations_paths.flat_map do |path| # match "20091231235959_some_name.rb" and "001_some_name.rb" pattern Dir.foreach(path).grep(/^(\d{3,})_(.+)\.rb$/) do version = ActiveRecord::SchemaMigration.normalize_migration_number($1) status = db_list.delete(version) ? 'up' : 'down' [status, version, $2.humanize] end end db_list.map! do |version| ['up', version, '********** NO FILE **********'] end # output puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n" puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" puts "-" * 50 (db_list + file_list).sort_by { |_, version, _| version }.each do |status, version, name| puts "#{status.center(8)} #{version.ljust(14)} #{name}" end puts end end desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).' task :rollback => [:environment, :load_config] do step = ENV['STEP'] ? ENV['STEP'].to_i : 1 ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step) db_namespace['_dump'].invoke end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' task :forward => [:environment, :load_config] do step = ENV['STEP'] ? ENV['STEP'].to_i : 1 ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step) db_namespace['_dump'].invoke end # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' task :reset => [:environment, :load_config] do db_namespace["drop"].invoke db_namespace["setup"].invoke end # desc "Retrieves the charset for the current environment's database" task :charset => [:environment, :load_config] do puts ActiveRecord::Tasks::DatabaseTasks.charset_current end # desc "Retrieves the collation for the current environment's database" task :collation => [:environment, :load_config] do begin puts ActiveRecord::Tasks::DatabaseTasks.collation_current rescue NoMethodError $stderr.puts 'Sorry, your database adapter is not supported yet. Feel free to submit a patch.' end end desc 'Retrieves the current schema version number' task :version => [:environment, :load_config] do puts "Current version: #{ActiveRecord::Migrator.current_version}" end # desc "Raises an error if there are pending migrations" task :abort_if_pending_migrations => :environment do pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations if pending_migrations.any? puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}" pending_migrations.each do |pending_migration| puts ' %4d %s' % [pending_migration.version, pending_migration.name] end abort %{Run `rake db:migrate` to update your database then try again.} end end desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)' task :setup => ['db:schema:load_if_ruby', 'db:structure:load_if_sql', :seed] desc 'Load the seed data from db/seeds.rb' task :seed do db_namespace['abort_if_pending_migrations'].invoke ActiveRecord::Tasks::DatabaseTasks.load_seed end namespace :fixtures do desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." task :load => [:environment, :load_config] do require 'active_record/fixtures' base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path fixtures_dir = if ENV['FIXTURES_DIR'] File.join base_dir, ENV['FIXTURES_DIR'] else base_dir end fixture_files = if ENV['FIXTURES'] ENV['FIXTURES'].split(',') else # The use of String#[] here is to support namespaced fixtures Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] } end ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files) end # desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." task :identify => [:environment, :load_config] do require 'active_record/fixtures' label, id = ENV['LABEL'], ENV['ID'] raise 'LABEL or ID required' if label.blank? && id.blank? puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path Dir["#{base_dir}/**/*.yml"].each do |file| if data = YAML::load(ERB.new(IO.read(file)).result) data.each_key do |key| key_id = ActiveRecord::FixtureSet.identify(key) if key == label || key_id == id.to_i puts "#{file}: #{key} (#{key_id})" end end end end end end namespace :schema do desc 'Create a db/schema.rb file that is portable against any DB supported by AR' task :dump => [:environment, :load_config] do require 'active_record/schema_dumper' filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb') File.open(filename, "w:utf-8") do |file| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) end db_namespace['schema:dump'].reenable end desc 'Load a schema.rb file into the database' task :load => [:environment, :load_config] do ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA']) end task :load_if_ruby => ['db:create', :environment] do db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby end namespace :cache do desc 'Create a db/schema_cache.dump file.' task :dump => [:environment, :load_config] do con = ActiveRecord::Base.connection filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") con.schema_cache.clear! con.tables.each { |table| con.schema_cache.add(table) } open(filename, 'wb') { |f| f.write(Marshal.dump(con.schema_cache)) } end desc 'Clear a db/schema_cache.dump file.' task :clear => [:environment, :load_config] do filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") FileUtils.rm(filename) if File.exist?(filename) end end end namespace :structure do desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql' task :dump => [:environment, :load_config] do filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql") current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) if ActiveRecord::Base.connection.supports_migrations? && ActiveRecord::SchemaMigration.table_exists? File.open(filename, "a") do |f| f.puts ActiveRecord::Base.connection.dump_schema_information f.print "\n" end end db_namespace['structure:dump'].reenable end desc "Recreate the databases from the structure.sql file" task :load => [:load_config] do ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['DB_STRUCTURE']) end task :load_if_sql => ['db:create', :environment] do db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql end end namespace :test do task :deprecated do Rake.application.top_level_tasks.grep(/^db:test:/).each do |task| $stderr.puts "WARNING: #{task} is deprecated. The Rails test helper now maintains " \ "your test schema automatically, see the release notes for details." end end # desc "Recreate the test database from the current schema" task :load => %w(db:test:purge) do case ActiveRecord::Base.schema_format when :ruby db_namespace["test:load_schema"].invoke when :sql db_namespace["test:load_structure"].invoke end end # desc "Recreate the test database from an existent schema.rb file" task :load_schema => %w(db:test:purge) do begin should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Schema.verbose = false ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA'] ensure if should_reconnect ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) end end end # desc "Recreate the test database from an existent structure.sql file" task :load_structure => %w(db:test:purge) do ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA'] end # desc "Recreate the test database from a fresh schema" task :clone => %w(db:test:deprecated environment) do case ActiveRecord::Base.schema_format when :ruby db_namespace["test:clone_schema"].invoke when :sql db_namespace["test:clone_structure"].invoke end end # desc "Recreate the test database from a fresh schema.rb file" task :clone_schema => %w(db:test:deprecated db:schema:dump db:test:load_schema) # desc "Recreate the test database from a fresh structure.sql file" task :clone_structure => %w(db:test:deprecated db:structure:dump db:test:load_structure) # desc "Empty the test database" task :purge => %w(environment load_config) do ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test'] end # desc 'Check for pending migrations and load the test schema' task :prepare => %w(environment load_config) do unless ActiveRecord::Base.configurations.blank? db_namespace['test:load'].invoke end end end end namespace :railties do namespace :install do # desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2" task :migrations => :'db:load_config' do to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } railties = {} Rails.application.migration_railties.each do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first) railties[railtie.railtie_name] = path end end on_skip = Proc.new do |name, migration| puts "NOTE: Migration #{migration.basename} from #{name} has been skipped. Migration with the same name already exists." end on_copy = Proc.new do |name, migration| puts "Copied migration #{migration.basename} from #{name}" end ActiveRecord::Migration.copy(ActiveRecord::Migrator.migrations_paths.first, railties, :on_skip => on_skip, :on_copy => on_copy) end end end rails-4.2.6/activerecord/lib/active_record/railties/jdbcmysql_error.rb000066400000000000000000000005511266740050600262370ustar00rootroot00000000000000#FIXME Remove if ArJdbcMysql will give. module ArJdbcMySQL #:nodoc: class Error < StandardError #:nodoc: attr_accessor :error_number, :sql_state def initialize msg super @error_number = nil @sql_state = nil end # Mysql gem compatibility alias_method :errno, :error_number alias_method :error, :message end end rails-4.2.6/activerecord/lib/active_record/readonly_attributes.rb000066400000000000000000000012431266740050600253040ustar00rootroot00000000000000module ActiveRecord module ReadonlyAttributes extend ActiveSupport::Concern included do class_attribute :_attr_readonly, instance_accessor: false self._attr_readonly = [] end module ClassMethods # Attributes listed as readonly will be used to create a new record but update operations will # ignore these fields. def attr_readonly(*attributes) self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || []) end # Returns an array of all the attributes that have been specified as readonly. def readonly_attributes self._attr_readonly end end end end rails-4.2.6/activerecord/lib/active_record/reflection.rb000066400000000000000000000720601266740050600233600ustar00rootroot00000000000000require 'thread' require 'active_support/core_ext/string/filters' module ActiveRecord # = Active Record Reflection module Reflection # :nodoc: extend ActiveSupport::Concern included do class_attribute :_reflections, instance_writer: false class_attribute :aggregate_reflections, instance_writer: false self._reflections = {} self.aggregate_reflections = {} end def self.create(macro, name, scope, options, ar) klass = case macro when :composed_of AggregateReflection when :has_many HasManyReflection when :has_one HasOneReflection when :belongs_to BelongsToReflection else raise "Unsupported Macro: #{macro}" end reflection = klass.new(name, scope, options, ar) options[:through] ? ThroughReflection.new(reflection) : reflection end def self.add_reflection(ar, name, reflection) ar.clear_reflections_cache ar._reflections = ar._reflections.merge(name.to_s => reflection) end def self.add_aggregate_reflection(ar, name, reflection) ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection) end # \Reflection enables interrogating of Active Record classes and objects # about their associations and aggregations. This information can, # for example, be used in a form builder that takes an Active Record object # and creates input fields for all of the attributes depending on their type # and displays the associations to other objects. # # MacroReflection class has info for AggregateReflection and AssociationReflection # classes. module ClassMethods # Returns an array of AggregateReflection objects for all the aggregations in the class. def reflect_on_all_aggregations aggregate_reflections.values end # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). # # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection # def reflect_on_aggregation(aggregation) aggregate_reflections[aggregation.to_s] end # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value. # # Account.reflections # => {"balance" => AggregateReflection} # # @api public def reflections @__reflections ||= begin ref = {} _reflections.each do |name, reflection| parent_name, parent_reflection = reflection.parent_reflection if parent_name ref[parent_name] = parent_reflection else ref[name] = reflection end end ref end end # Returns an array of AssociationReflection objects for all the # associations in the class. If you only want to reflect on a certain # association type, pass in the symbol (:has_many, :has_one, # :belongs_to) as the first parameter. # # Example: # # Account.reflect_on_all_associations # returns an array of all associations # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations # # @api public def reflect_on_all_associations(macro = nil) association_reflections = reflections.values macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections end # Returns the AssociationReflection object for the +association+ (use the symbol). # # Account.reflect_on_association(:owner) # returns the owner AssociationReflection # Invoice.reflect_on_association(:line_items).macro # returns :has_many # # @api public def reflect_on_association(association) reflections[association.to_s] end # @api private def _reflect_on_association(association) #:nodoc: _reflections[association.to_s] end # Returns an array of AssociationReflection objects for all associations which have :autosave enabled. # # @api public def reflect_on_all_autosave_associations reflections.values.select { |reflection| reflection.options[:autosave] } end def clear_reflections_cache #:nodoc: @__reflections = nil end end # Holds all the methods that are shared between MacroReflection, AssociationReflection # and ThroughReflection class AbstractReflection # :nodoc: def table_name klass.table_name end # Returns a new, unsaved instance of the associated class. +attributes+ will # be passed to the class's constructor. def build_association(attributes, &block) klass.new(attributes, &block) end def quoted_table_name klass.quoted_table_name end def primary_key_type klass.type_for_attribute(klass.primary_key) end # Returns the class name for the macro. # # composed_of :balance, class_name: 'Money' returns 'Money' # has_many :clients returns 'Client' def class_name @class_name ||= (options[:class_name] || derive_class_name).to_s end JoinKeys = Struct.new(:key, :foreign_key) # :nodoc: def join_keys(assoc_klass) JoinKeys.new(foreign_key, active_record_primary_key) end def source_macro ActiveSupport::Deprecation.warn(<<-MSG.squish) ActiveRecord::Base.source_macro is deprecated and will be removed without replacement. MSG macro end def inverse_of return unless inverse_name @inverse_of ||= klass._reflect_on_association inverse_name end def check_validity_of_inverse! unless polymorphic? if has_inverse? && inverse_of.nil? raise InverseOfAssociationNotFoundError.new(self) end end end end # Base class for AggregateReflection and AssociationReflection. Objects of # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. # # MacroReflection # AssociationReflection # AggregateReflection # HasManyReflection # HasOneReflection # BelongsToReflection # ThroughReflection class MacroReflection < AbstractReflection # Returns the name of the macro. # # composed_of :balance, class_name: 'Money' returns :balance # has_many :clients returns :clients attr_reader :name attr_reader :scope # Returns the hash of options used for the macro. # # composed_of :balance, class_name: 'Money' returns { class_name: "Money" } # has_many :clients returns {} attr_reader :options attr_reader :active_record attr_reader :plural_name # :nodoc: def initialize(name, scope, options, active_record) @name = name @scope = scope @options = options @active_record = active_record @klass = options[:anonymous_class] @plural_name = active_record.pluralize_table_names ? name.to_s.pluralize : name.to_s end def autosave=(autosave) @automatic_inverse_of = false @options[:autosave] = autosave _, parent_reflection = self.parent_reflection if parent_reflection parent_reflection.autosave = autosave end end # Returns the class for the macro. # # composed_of :balance, class_name: 'Money' returns the Money class # has_many :clients returns the Client class def klass @klass ||= compute_class(class_name) end def compute_class(name) name.constantize end # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute, # and +other_aggregation+ has an options hash assigned to it. def ==(other_aggregation) super || other_aggregation.kind_of?(self.class) && name == other_aggregation.name && !other_aggregation.options.nil? && active_record == other_aggregation.active_record end private def derive_class_name name.to_s.camelize end end # Holds all the meta-data about an aggregation as it was specified in the # Active Record class. class AggregateReflection < MacroReflection #:nodoc: def mapping mapping = options[:mapping] || [name, name] mapping.first.is_a?(Array) ? mapping : [mapping] end end # Holds all the meta-data about an association as it was specified in the # Active Record class. class AssociationReflection < MacroReflection #:nodoc: # Returns the target association's class. # # class Author < ActiveRecord::Base # has_many :books # end # # Author.reflect_on_association(:books).klass # # => Book # # Note: Do not call +klass.new+ or +klass.create+ to instantiate # a new association object. Use +build_association+ or +create_association+ # instead. This allows plugins to hook into association object creation. def klass @klass ||= compute_class(class_name) end def compute_class(name) active_record.send(:compute_type, name) end attr_reader :type, :foreign_type attr_accessor :parent_reflection # [:name, Reflection] def initialize(name, scope, options, active_record) super @automatic_inverse_of = nil @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type") @foreign_type = options[:foreign_type] || "#{name}_type" @constructable = calculate_constructable(macro, options) @association_scope_cache = {} @scope_lock = Mutex.new end def association_scope_cache(conn, owner) key = conn.prepared_statements if polymorphic? key = [key, owner._read_attribute(@foreign_type)] end @association_scope_cache[key] ||= @scope_lock.synchronize { @association_scope_cache[key] ||= yield } end def constructable? # :nodoc: @constructable end def join_table @join_table ||= options[:join_table] || derive_join_table end def foreign_key @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze end def association_foreign_key @association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key end # klass option is necessary to support loading polymorphic associations def association_primary_key(klass = nil) options[:primary_key] || primary_key(klass || self.klass) end def active_record_primary_key @active_record_primary_key ||= options[:primary_key] || primary_key(active_record) end def counter_cache_column if options[:counter_cache] == true "#{active_record.name.demodulize.underscore.pluralize}_count" elsif options[:counter_cache] options[:counter_cache].to_s end end def check_validity! check_validity_of_inverse! end def check_preloadable! return unless scope if scope.arity > 0 ActiveSupport::Deprecation.warn(<<-MSG.squish) The association scope '#{name}' is instance dependent (the scope block takes an argument). Preloading happens before the individual instances are created. This means that there is no instance being passed to the association scope. This will most likely result in broken or incorrect behavior. Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future. MSG end end alias :check_eager_loadable! :check_preloadable! def join_id_for(owner) # :nodoc: owner[active_record_primary_key] end def through_reflection nil end def source_reflection self end # A chain of reflections from this one back to the owner. For more see the explanation in # ThroughReflection. def chain [self] end def nested? false end # An array of arrays of scopes. Each item in the outside array corresponds to a reflection # in the #chain. def scope_chain scope ? [[scope]] : [[]] end def has_inverse? inverse_name end def polymorphic_inverse_of(associated_class) if has_inverse? if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of]) inverse_relationship else raise InverseOfAssociationNotFoundError.new(self, associated_class) end end end # Returns the macro type. # # has_many :clients returns :has_many def macro; raise NotImplementedError; end # Returns whether or not this association reflection is for a collection # association. Returns +true+ if the +macro+ is either +has_many+ or # +has_and_belongs_to_many+, +false+ otherwise. def collection? false end # Returns whether or not the association should be validated as part of # the parent's validation. # # Unless you explicitly disable validation with # validate: false, validation will take place when: # # * you explicitly enable validation; validate: true # * you use autosave; autosave: true # * the association is a +has_many+ association def validate? !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?) end # Returns +true+ if +self+ is a +belongs_to+ reflection. def belongs_to?; false; end # Returns +true+ if +self+ is a +has_one+ reflection. def has_one?; false; end def association_class case macro when :belongs_to if polymorphic? Associations::BelongsToPolymorphicAssociation else Associations::BelongsToAssociation end when :has_many if options[:through] Associations::HasManyThroughAssociation else Associations::HasManyAssociation end when :has_one if options[:through] Associations::HasOneThroughAssociation else Associations::HasOneAssociation end end end def polymorphic? options[:polymorphic] end VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key] protected def actual_source_reflection # FIXME: this is a horrible name self end private def calculate_constructable(macro, options) case macro when :belongs_to !polymorphic? when :has_one !options[:through] else true end end # Attempts to find the inverse association name automatically. # If it cannot find a suitable inverse association name, it returns # nil. def inverse_name options.fetch(:inverse_of) do if @automatic_inverse_of == false nil else @automatic_inverse_of ||= automatic_inverse_of end end end # returns either nil or the inverse association name that it finds. def automatic_inverse_of if can_find_inverse_of_automatically?(self) inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym begin reflection = klass._reflect_on_association(inverse_name) rescue NameError # Give up: we couldn't compute the klass type so we won't be able # to find any associations either. reflection = false end if valid_inverse_reflection?(reflection) return inverse_name end end false end # Checks if the inverse reflection that is returned from the # +automatic_inverse_of+ method is a valid reflection. We must # make sure that the reflection's active_record name matches up # with the current reflection's klass name. # # Note: klass will always be valid because when there's a NameError # from calling +klass+, +reflection+ will already be set to false. def valid_inverse_reflection?(reflection) reflection && klass.name == reflection.active_record.name && can_find_inverse_of_automatically?(reflection) end # Checks to see if the reflection doesn't have any options that prevent # us from being able to guess the inverse automatically. First, the # inverse_of option cannot be set to false. Second, we must # have has_many, has_one, belongs_to associations. # Third, we must not have options such as :polymorphic or # :foreign_key which prevent us from correctly guessing the # inverse association. # # Anything with a scope can additionally ruin our attempt at finding an # inverse, so we exclude reflections with scopes. def can_find_inverse_of_automatically?(reflection) reflection.options[:inverse_of] != false && VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) && !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } && !reflection.scope end def derive_class_name class_name = name.to_s class_name = class_name.singularize if collection? class_name.camelize end def derive_foreign_key if belongs_to? "#{name}_id" elsif options[:as] "#{options[:as]}_id" else active_record.name.foreign_key end end def derive_join_table ModelSchema.derive_join_table_name active_record.table_name, klass.table_name end def primary_key(klass) klass.primary_key || raise(UnknownPrimaryKey.new(klass)) end end class HasManyReflection < AssociationReflection # :nodoc: def initialize(name, scope, options, active_record) super(name, scope, options, active_record) end def macro; :has_many; end def collection?; true; end end class HasOneReflection < AssociationReflection # :nodoc: def initialize(name, scope, options, active_record) super(name, scope, options, active_record) end def macro; :has_one; end def has_one?; true; end end class BelongsToReflection < AssociationReflection # :nodoc: def initialize(name, scope, options, active_record) super(name, scope, options, active_record) end def macro; :belongs_to; end def belongs_to?; true; end def join_keys(assoc_klass) key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key JoinKeys.new(key, foreign_key) end def join_id_for(owner) # :nodoc: owner[foreign_key] end end class HasAndBelongsToManyReflection < AssociationReflection # :nodoc: def initialize(name, scope, options, active_record) super end def macro; :has_and_belongs_to_many; end def collection? true end end # Holds all the meta-data about a :through association as it was specified # in the Active Record class. class ThroughReflection < AbstractReflection #:nodoc: attr_reader :delegate_reflection delegate :foreign_key, :foreign_type, :association_foreign_key, :active_record_primary_key, :type, :to => :source_reflection def initialize(delegate_reflection) @delegate_reflection = delegate_reflection @klass = delegate_reflection.options[:anonymous_class] @source_reflection_name = delegate_reflection.options[:source] end def klass @klass ||= delegate_reflection.compute_class(class_name) end # Returns the source of the through reflection. It checks both a singularized # and pluralized form for :belongs_to or :has_many. # # class Post < ActiveRecord::Base # has_many :taggings # has_many :tags, through: :taggings # end # # class Tagging < ActiveRecord::Base # belongs_to :post # belongs_to :tag # end # # tags_reflection = Post.reflect_on_association(:tags) # tags_reflection.source_reflection # # => # def source_reflection through_reflection.klass._reflect_on_association(source_reflection_name) end # Returns the AssociationReflection object specified in the :through option # of a HasManyThrough or HasOneThrough association. # # class Post < ActiveRecord::Base # has_many :taggings # has_many :tags, through: :taggings # end # # tags_reflection = Post.reflect_on_association(:tags) # tags_reflection.through_reflection # # => # def through_reflection active_record._reflect_on_association(options[:through]) end # Returns an array of reflections which are involved in this association. Each item in the # array corresponds to a table which will be part of the query for this association. # # The chain is built by recursively calling #chain on the source reflection and the through # reflection. The base case for the recursion is a normal association, which just returns # [self] as its #chain. # # class Post < ActiveRecord::Base # has_many :taggings # has_many :tags, through: :taggings # end # # tags_reflection = Post.reflect_on_association(:tags) # tags_reflection.chain # # => [, # ] # def chain @chain ||= begin a = source_reflection.chain b = through_reflection.chain chain = a + b chain[0] = self # Use self so we don't lose the information from :source_type chain end end # Consider the following example: # # class Person # has_many :articles # has_many :comment_tags, through: :articles # end # # class Article # has_many :comments # has_many :comment_tags, through: :comments, source: :tags # end # # class Comment # has_many :tags # end # # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags, # but only Comment.tags will be represented in the #chain. So this method creates an array # of scopes corresponding to the chain. def scope_chain @scope_chain ||= begin scope_chain = source_reflection.scope_chain.map(&:dup) # Add to it the scope from this reflection (if any) scope_chain.first << scope if scope through_scope_chain = through_reflection.scope_chain.map(&:dup) if options[:source_type] type = foreign_type source_type = options[:source_type] through_scope_chain.first << lambda { |object| where(type => source_type) } end # Recursively fill out the rest of the array from the through reflection scope_chain + through_scope_chain end end def join_keys(assoc_klass) source_reflection.join_keys(assoc_klass) end # The macro used by the source association def source_macro ActiveSupport::Deprecation.warn(<<-MSG.squish) ActiveRecord::Base.source_macro is deprecated and will be removed without replacement. MSG source_reflection.source_macro end # A through association is nested if there would be more than one join table def nested? chain.length > 2 end # We want to use the klass from this reflection, rather than just delegate straight to # the source_reflection, because the source_reflection may be polymorphic. We still # need to respect the source_reflection's :primary_key option, though. def association_primary_key(klass = nil) # Get the "actual" source reflection if the immediate source reflection has a # source reflection itself actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass) end # Gets an array of possible :through source reflection names in both singular and plural form. # # class Post < ActiveRecord::Base # has_many :taggings # has_many :tags, through: :taggings # end # # tags_reflection = Post.reflect_on_association(:tags) # tags_reflection.source_reflection_names # # => [:tag, :tags] # def source_reflection_names options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq end def source_reflection_name # :nodoc: return @source_reflection_name if @source_reflection_name names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq names = names.find_all { |n| through_reflection.klass._reflect_on_association(n) } if names.length > 1 example_options = options.dup example_options[:source] = source_reflection_names.first ActiveSupport::Deprecation.warn \ "Ambiguous source reflection for through association. Please " \ "specify a :source directive on your declaration like:\n" \ "\n" \ " class #{active_record.name} < ActiveRecord::Base\n" \ " #{macro} :#{name}, #{example_options}\n" \ " end" end @source_reflection_name = names.first end def source_options source_reflection.options end def through_options through_reflection.options end def join_id_for(owner) # :nodoc: source_reflection.join_id_for(owner) end def check_validity! if through_reflection.nil? raise HasManyThroughAssociationNotFoundError.new(active_record.name, self) end if through_reflection.polymorphic? if has_one? raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self) else raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self) end end if source_reflection.nil? raise HasManyThroughSourceAssociationNotFoundError.new(self) end if options[:source_type] && !source_reflection.polymorphic? raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection) end if source_reflection.polymorphic? && options[:source_type].nil? raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection) end if has_one? && through_reflection.collection? raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection) end check_validity_of_inverse! end protected def actual_source_reflection # FIXME: this is a horrible name source_reflection.send(:actual_source_reflection) end def primary_key(klass) klass.primary_key || raise(UnknownPrimaryKey.new(klass)) end def inverse_name; delegate_reflection.send(:inverse_name); end private def derive_class_name # get the class_name of the belongs_to association of the through reflection options[:source_type] || source_reflection.class_name end delegate_methods = AssociationReflection.public_instance_methods - public_instance_methods delegate(*delegate_methods, to: :delegate_reflection) end end end rails-4.2.6/activerecord/lib/active_record/relation.rb000066400000000000000000000551171266740050600230470ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'arel/collectors/bind' module ActiveRecord # = Active Record Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, :order, :joins, :where, :having, :bind, :references, :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :distinct, :create_with, :uniq] INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having] VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation attr_reader :table, :klass, :loaded alias :model :klass alias :loaded? :loaded def initialize(klass, table, values = {}) @klass = klass @table = table @values = values @offsets = {} @loaded = false end def initialize_copy(other) # This method is a hot spot, so for now, use Hash[] to dup the hash. # https://bugs.ruby-lang.org/issues/7166 @values = Hash[@values] @values[:bind] = @values[:bind].dup if @values.key? :bind reset end def insert(values) # :nodoc: primary_key_value = nil if primary_key && Hash === values primary_key_value = values[values.keys.find { |k| k.name == primary_key }] if !primary_key_value && connection.prefetch_primary_key?(klass.table_name) primary_key_value = connection.next_sequence_value(klass.sequence_name) values[klass.arel_table[klass.primary_key]] = primary_key_value end end im = arel.create_insert im.into @table substitutes, binds = substitute_values values if values.empty? # empty insert im.values = Arel.sql(connection.empty_insert_statement_value) else im.insert substitutes end @klass.connection.insert( im, 'SQL', primary_key, primary_key_value, nil, binds) end def _update_record(values, id, id_was) # :nodoc: substitutes, binds = substitute_values values scope = @klass.unscoped if @klass.finder_needs_type_condition? scope.unscope!(where: @klass.inheritance_column) end relation = scope.where(@klass.primary_key => (id_was || id)) bvs = binds + relation.bind_values um = relation .arel .compile_update(substitutes, @klass.primary_key) @klass.connection.update( um, 'SQL', bvs, ) end def substitute_values(values) # :nodoc: binds = values.map do |arel_attr, value| [@klass.columns_hash[arel_attr.name], value] end substitutes = values.each_with_index.map do |(arel_attr, _), i| [arel_attr, @klass.connection.substitute_at(binds[i][0])] end [substitutes, binds] end # Initializes new record from relation while maintaining the current # scope. # # Expects arguments in the same format as +Base.new+. # # users = User.where(name: 'DHH') # user = users.new # => # # # You can also pass a block to new with the new record as argument: # # user = users.new { |user| user.name = 'Oscar' } # user.name # => Oscar def new(*args, &block) scoping { @klass.new(*args, &block) } end alias build new # Tries to create a new record with the same scoped attributes # defined in the relation. Returns the initialized object if validation fails. # # Expects arguments in the same format as +Base.create+. # # ==== Examples # users = User.where(name: 'Oscar') # users.create # # # # users.create(name: 'fxn') # users.create # # # # users.create { |user| user.name = 'tenderlove' } # # # # # users.create(name: nil) # validation on name # # # def create(*args, &block) scoping { @klass.create(*args, &block) } end # Similar to #create, but calls +create!+ on the base class. Raises # an exception if a validation error occurs. # # Expects arguments in the same format as Base.create!. def create!(*args, &block) scoping { @klass.create!(*args, &block) } end def first_or_create(attributes = nil, &block) # :nodoc: first || create(attributes, &block) end def first_or_create!(attributes = nil, &block) # :nodoc: first || create!(attributes, &block) end def first_or_initialize(attributes = nil, &block) # :nodoc: first || new(attributes, &block) end # Finds the first record with the given attributes, or creates a record # with the attributes if one is not found: # # # Find the first user named "Penélope" or create a new one. # User.find_or_create_by(first_name: 'Penélope') # # => # # # # Find the first user named "Penélope" or create a new one. # # We already have one so the existing record will be returned. # User.find_or_create_by(first_name: 'Penélope') # # => # # # # Find the first user named "Scarlett" or create a new one with # # a particular last name. # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett') # # => # # # This method accepts a block, which is passed down to +create+. The last example # above can be alternatively written this way: # # # Find the first user named "Scarlett" or create a new one with a # # different last name. # User.find_or_create_by(first_name: 'Scarlett') do |user| # user.last_name = 'Johansson' # end # # => # # # This method always returns a record, but if creation was attempted and # failed due to validation errors it won't be persisted, you get what # +create+ returns in such situation. # # Please note *this method is not atomic*, it runs first a SELECT, and if # there are no results an INSERT is attempted. If there are other threads # or processes there is a race condition between both calls and it could # be the case that you end up with two similar records. # # Whether that is a problem or not depends on the logic of the # application, but in the particular case in which rows have a UNIQUE # constraint an exception may be raised, just retry: # # begin # CreditAccount.find_or_create_by(user_id: user.id) # rescue ActiveRecord::RecordNotUnique # retry # end # def find_or_create_by(attributes, &block) find_by(attributes) || create(attributes, &block) end # Like find_or_create_by, but calls create! so an exception # is raised if the created record is invalid. def find_or_create_by!(attributes, &block) find_by(attributes) || create!(attributes, &block) end # Like find_or_create_by, but calls new instead of create. def find_or_initialize_by(attributes, &block) find_by(attributes) || new(attributes, &block) end # Runs EXPLAIN on the query or queries triggered by this relation and # returns the result as a string. The string is formatted imitating the # ones printed by the database shell. # # Note that this method actually runs the queries, since the results of some # are needed by the next ones when eager loading is going on. # # Please see further details in the # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain]. def explain #TODO: Fix for binds. exec_explain(collecting_queries_for_explain { exec_queries }) end # Converts relation objects to Array. def to_a load @records end # Serializes the relation objects Array. def encode_with(coder) coder.represent_seq(nil, to_a) end def as_json(options = nil) #:nodoc: to_a.as_json(options) end # Returns size of the records. def size loaded? ? @records.length : count(:all) end # Returns true if there are no records. def empty? return @records.empty? if loaded? if limit_value == 0 true else c = count(:all) c.respond_to?(:zero?) ? c.zero? : c.empty? end end # Returns true if there are any records. def any? if block_given? to_a.any? { |*block_args| yield(*block_args) } else !empty? end end # Returns true if there is more than one record. def many? if block_given? to_a.many? { |*block_args| yield(*block_args) } else limit_value ? to_a.many? : size > 1 end end # Scope all queries to the current scope. # # Comment.where(post_id: 1).scoping do # Comment.first # end # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1 # # Please check unscoped if you want to remove all previous scopes (including # the default_scope) during the execution of a block. def scoping previous, klass.current_scope = klass.current_scope, self yield ensure klass.current_scope = previous end # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE # statement and sends it straight to the database. It does not instantiate the involved models and it does not # trigger Active Record callbacks or validations. Values passed to `update_all` will not go through # ActiveRecord's type-casting behavior. It should receive only values that can be passed as-is to the SQL # database. # # ==== Parameters # # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. # # ==== Examples # # # Update all customers with the given attributes # Customer.update_all wants_email: true # # # Update all books with 'Rails' in their title # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David') # # # Update all books that match conditions, but limit it to 5 ordered by date # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David') def update_all(updates) raise ArgumentError, "Empty list of attributes to change" if updates.blank? stmt = Arel::UpdateManager.new(arel.engine) stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)) stmt.table(table) stmt.key = table[primary_key] if joins_values.any? @klass.connection.join_to_update(stmt, arel) else stmt.take(arel.limit) stmt.order(*arel.orders) stmt.wheres = arel.constraints end bvs = arel.bind_values + bind_values @klass.connection.update stmt, 'SQL', bvs end # Updates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # # ==== Parameters # # * +id+ - This should be the id or an array of ids to be updated. # * +attributes+ - This should be a hash of attributes or an array of hashes. # # ==== Examples # # # Updates one record # Person.update(15, user_name: 'Samuel', group: 'expert') # # # Updates multiple records # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } # Person.update(people.keys, people.values) def update(id, attributes) if id.is_a?(Array) id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } else object = find(id) object.update(attributes) object end end # Destroys the records matching +conditions+ by instantiating each # record and calling its +destroy+ method. Each object's callbacks are # executed (including :dependent association options). Returns the # collection of objects that were destroyed; each will be frozen, to # reflect that no changes should be made (since they can't be persisted). # # Note: Instantiation, callback execution, and deletion of each # record can be time consuming when you're removing many records at # once. It generates at least one SQL +DELETE+ query per record (or # possibly more, to enforce your callbacks). If you want to delete many # rows quickly, without concern for their associations or callbacks, use # +delete_all+ instead. # # ==== Parameters # # * +conditions+ - A string, array, or hash that specifies which records # to destroy. If omitted, all records are destroyed. See the # Conditions section in the introduction to ActiveRecord::Base for # more information. # # ==== Examples # # Person.destroy_all("last_login < '2004-04-04'") # Person.destroy_all(status: "inactive") # Person.where(age: 0..18).destroy_all def destroy_all(conditions = nil) if conditions where(conditions).destroy_all else to_a.each {|object| object.destroy }.tap { reset } end end # Destroy an object (or multiple objects) that has the given id. The object is instantiated first, # therefore all callbacks and filters are fired off before the object is deleted. This method is # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run. # # This essentially finds the object (or multiple objects) with the given id, creates a new object # from the attributes, and then calls destroy on it. # # ==== Parameters # # * +id+ - Can be either an Integer or an Array of Integers. # # ==== Examples # # # Destroy a single object # Todo.destroy(1) # # # Destroy multiple objects # todos = [1,2,3] # Todo.destroy(todos) def destroy(id) if id.is_a?(Array) id.map { |one_id| destroy(one_id) } else find(id).destroy end end # Deletes the records matching +conditions+ without instantiating the records # first, and hence not calling the +destroy+ method nor invoking callbacks. This # is a single SQL DELETE statement that goes straight to the database, much more # efficient than +destroy_all+. Be careful with relations though, in particular # :dependent rules defined on associations are not honored. Returns the # number of rows affected. # # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')") # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else']) # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all # # Both calls delete the affected posts all at once with a single DELETE statement. # If you need to destroy dependent associations or call your before_* or # +after_destroy+ callbacks, use the +destroy_all+ method instead. # # If an invalid method is supplied, +delete_all+ raises an ActiveRecord error: # # Post.limit(100).delete_all # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit def delete_all(conditions = nil) invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method| if MULTI_VALUE_METHODS.include?(method) send("#{method}_values").any? else send("#{method}_value") end } if invalid_methods.any? raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") end if conditions where(conditions).delete_all else stmt = Arel::DeleteManager.new(arel.engine) stmt.from(table) if joins_values.any? @klass.connection.join_to_delete(stmt, arel, table[primary_key]) else stmt.wheres = arel.constraints end bvs = arel.bind_values + bind_values affected = @klass.connection.delete(stmt, 'SQL', bvs) reset affected end end # Deletes the row with a primary key matching the +id+ argument, using a # SQL +DELETE+ statement, and returns the number of rows deleted. Active # Record objects are not instantiated, so the object's callbacks are not # executed, including any :dependent association options. # # You can delete multiple rows at once by passing an Array of ids. # # Note: Although it is often much faster than the alternative, # #destroy, skipping callbacks might bypass business logic in # your application that ensures referential integrity or performs other # essential jobs. # # ==== Examples # # # Delete a single row # Todo.delete(1) # # # Delete multiple rows # Todo.delete([2,3,4]) def delete(id_or_array) where(primary_key => id_or_array).delete_all end # Causes the records to be loaded from the database if they have not # been loaded already. You can use this if for some reason you need # to explicitly load some records before actually using them. The # return value is the relation itself, not the records. # # Post.where(published: true).load # => # def load exec_queries unless loaded? self end # Forces reloading of relation. def reload reset load end def reset @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil @should_eager_load = @join_dependency = nil @records = [] @offsets = {} self end # Returns sql statement for the relation. # # User.where(name: 'Oscar').to_sql # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar' def to_sql @to_sql ||= begin relation = self connection = klass.connection visitor = connection.visitor if eager_loading? find_with_associations { |rel| relation = rel } end arel = relation.arel binds = (arel.bind_values + relation.bind_values).dup binds.map! { |bv| connection.quote(*bv.reverse) } collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new) collect.substitute_binds(binds).join end end # Returns a hash of where conditions. # # User.where(name: 'Oscar').where_values_hash # # => {name: "Oscar"} def where_values_hash(relation_table_name = table_name) equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == relation_table_name } binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] Hash[equalities.map { |where| name = where.left.name [name, binds.fetch(name.to_s) { case where.right when Array then where.right.map(&:val) when Arel::Nodes::Casted where.right.val end }] }] end def scope_for_create @scope_for_create ||= where_values_hash.merge(create_with_value) end # Returns true if relation needs eager loading. def eager_loading? @should_eager_load ||= eager_load_values.any? || includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) end # Joins that are also marked for preloading. In which case we should just eager load them. # Note that this is a naive implementation because we could have strings and symbols which # represent the same association, but that aren't matched by this. Also, we could have # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] } def joined_includes_values includes_values & joins_values end # +uniq+ and +uniq!+ are silently deprecated. +uniq_value+ delegates to +distinct_value+ # to maintain backwards compatibility. Use +distinct_value+ instead. def uniq_value distinct_value end # Compares two relations for equality. def ==(other) case other when Associations::CollectionProxy, AssociationRelation self == other.to_a when Relation other.to_sql == to_sql when Array to_a == other end end def pretty_print(q) q.pp(self.to_a) end # Returns true if relation is blank. def blank? to_a.blank? end def values Hash[@values] end def inspect entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect) entries[10] = '...' if entries.size == 11 "#<#{self.class.name} [#{entries.join(', ')}]>" end private def exec_queries @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values) preload = preload_values preload += includes_values unless eager_loading? preloader = build_preloader preload.each do |associations| preloader.preload @records, associations end @records.each { |record| record.readonly! } if readonly_value @loaded = true @records end def build_preloader ActiveRecord::Associations::Preloader.new end def references_eager_loaded_tables? joined_tables = arel.join_sources.map do |join| if join.is_a?(Arel::Nodes::StringJoin) tables_in_string(join.left) else [join.left.table_name, join.left.table_alias] end end joined_tables += [table.name, table.table_alias] # always convert table names to downcase as in Oracle quoted table names are in uppercase joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq (references_values - joined_tables).any? end def tables_in_string(string) return [] if string.blank? # always convert table names to downcase as in Oracle quoted table names are in uppercase # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_'] end end end rails-4.2.6/activerecord/lib/active_record/relation/000077500000000000000000000000001266740050600225115ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/relation/batches.rb000066400000000000000000000121221266740050600244450ustar00rootroot00000000000000module ActiveRecord module Batches # Looping through a collection of records from the database # (using the +all+ method, for example) is very inefficient # since it will try to instantiate all the objects at once. # # In that case, batch processing methods allow you to work # with the records in batches, thereby greatly reducing memory consumption. # # The #find_each method uses #find_in_batches with a batch size of 1000 (or as # specified by the +:batch_size+ option). # # Person.find_each do |person| # person.do_awesome_stuff # end # # Person.where("age > 21").find_each do |person| # person.party_all_night! # end # # If you do not provide a block to #find_each, it will return an Enumerator # for chaining with other methods: # # Person.find_each.with_index do |person, index| # person.award_trophy(index + 1) # end # # ==== Options # * :batch_size - Specifies the size of the batch. Default to 1000. # * :start - Specifies the starting point for the batch processing. # This is especially useful if you want multiple workers dealing with # the same processing queue. You can make worker 1 handle all the records # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond # (by setting the +:start+ option on that worker). # # # Let's process for a batch of 2000 records, skipping the first 2000 rows # Person.find_each(start: 2000, batch_size: 2000) do |person| # person.party_all_night! # end # # NOTE: It's not possible to set the order. That is automatically set to # ascending on the primary key ("id ASC") to make the batch ordering # work. This also means that this method only works with integer-based # primary keys. # # NOTE: You can't set the limit either, that's used to control # the batch sizes. def find_each(options = {}) if block_given? find_in_batches(options) do |records| records.each { |record| yield record } end else enum_for :find_each, options do options[:start] ? where(table[primary_key].gteq(options[:start])).size : size end end end # Yields each batch of records that was found by the find +options+ as # an array. # # Person.where("age > 21").find_in_batches do |group| # sleep(50) # Make sure it doesn't get too crowded in there! # group.each { |person| person.party_all_night! } # end # # If you do not provide a block to #find_in_batches, it will return an Enumerator # for chaining with other methods: # # Person.find_in_batches.with_index do |group, batch| # puts "Processing group ##{batch}" # group.each(&:recover_from_last_night!) # end # # To be yielded each record one by one, use #find_each instead. # # ==== Options # * :batch_size - Specifies the size of the batch. Default to 1000. # * :start - Specifies the starting point for the batch processing. # This is especially useful if you want multiple workers dealing with # the same processing queue. You can make worker 1 handle all the records # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond # (by setting the +:start+ option on that worker). # # # Let's process the next 2000 records # Person.find_in_batches(start: 2000, batch_size: 2000) do |group| # group.each { |person| person.party_all_night! } # end # # NOTE: It's not possible to set the order. That is automatically set to # ascending on the primary key ("id ASC") to make the batch ordering # work. This also means that this method only works with integer-based # primary keys. # # NOTE: You can't set the limit either, that's used to control # the batch sizes. def find_in_batches(options = {}) options.assert_valid_keys(:start, :batch_size) relation = self start = options[:start] batch_size = options[:batch_size] || 1000 unless block_given? return to_enum(:find_in_batches, options) do total = start ? where(table[primary_key].gteq(start)).size : size (total - 1).div(batch_size) + 1 end end if logger && (arel.orders.present? || arel.taken.present?) logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") end relation = relation.reorder(batch_order).limit(batch_size) records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a while records.any? records_size = records.size primary_key_offset = records.last.id raise "Primary key not included in the custom select clause" unless primary_key_offset yield records break if records_size < batch_size records = relation.where(table[primary_key].gt(primary_key_offset)).to_a end end private def batch_order "#{quoted_table_name}.#{quoted_primary_key} ASC" end end end rails-4.2.6/activerecord/lib/active_record/relation/calculations.rb000066400000000000000000000343031266740050600255220ustar00rootroot00000000000000module ActiveRecord module Calculations # Count the records. # # Person.count # # => the total count of all people # # Person.count(:age) # # => returns the total count of all people whose age is present in database # # Person.count(:all) # # => performs a COUNT(*) (:all is an alias for '*') # # Person.distinct.count(:age) # # => counts the number of different age values # # If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column, # and the values are the respective amounts: # # Person.group(:city).count # # => { 'Rome' => 5, 'Paris' => 3 } # # If +count+ is used with +group+ for multiple columns, it returns a Hash whose # keys are an array containing the individual values of each column and the value # of each key would be the +count+. # # Article.group(:status, :category).count # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, # ["published", "business"]=>0, ["published", "technology"]=>2} # # If +count+ is used with +select+, it will count the selected columns: # # Person.select(:age).count # # => counts the number of different age values # # Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ # between databases. In invalid cases, an error from the database is thrown. def count(column_name = nil, options = {}) # TODO: Remove options argument as soon we remove support to # activerecord-deprecated_finders. column_name, options = nil, column_name if column_name.is_a?(Hash) calculate(:count, column_name, options) end # Calculates the average value on a given column. Returns +nil+ if there's # no row. See +calculate+ for examples with options. # # Person.average(:age) # => 35.8 def average(column_name, options = {}) # TODO: Remove options argument as soon we remove support to # activerecord-deprecated_finders. calculate(:average, column_name, options) end # Calculates the minimum value on a given column. The value is returned # with the same data type of the column, or +nil+ if there's no row. See # +calculate+ for examples with options. # # Person.minimum(:age) # => 7 def minimum(column_name, options = {}) # TODO: Remove options argument as soon we remove support to # activerecord-deprecated_finders. calculate(:minimum, column_name, options) end # Calculates the maximum value on a given column. The value is returned # with the same data type of the column, or +nil+ if there's no row. See # +calculate+ for examples with options. # # Person.maximum(:age) # => 93 def maximum(column_name, options = {}) # TODO: Remove options argument as soon we remove support to # activerecord-deprecated_finders. calculate(:maximum, column_name, options) end # Calculates the sum of values on a given column. The value is returned # with the same data type of the column, 0 if there's no row. See # +calculate+ for examples with options. # # Person.sum(:age) # => 4562 def sum(*args) calculate(:sum, *args) end # This calculates aggregate values in the given column. Methods for count, sum, average, # minimum, and maximum have been added as shortcuts. # # There are two basic forms of output: # # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float # for AVG, and the given column's type for everything else. # # * Grouped values: This returns an ordered hash of the values and groups them. It # takes either a column name, or the name of a belongs_to association. # # values = Person.group('last_name').maximum(:age) # puts values["Drake"] # # => 43 # # drake = Family.find_by(last_name: 'Drake') # values = Person.group(:family).maximum(:age) # Person belongs_to :family # puts values[drake] # # => 43 # # values.each do |family, max_age| # ... # end # # Person.calculate(:count, :all) # The same as Person.count # Person.average(:age) # SELECT AVG(age) FROM people... # # # Selects the minimum age for any family without any minors # Person.group(:last_name).having("min(age) > 17").minimum(:age) # # Person.sum("2 * age") def calculate(operation, column_name, options = {}) # TODO: Remove options argument as soon we remove support to # activerecord-deprecated_finders. if column_name.is_a?(Symbol) && attribute_alias?(column_name) column_name = attribute_alias(column_name) end if has_include?(column_name) construct_relation_for_association_calculations.calculate(operation, column_name, options) else perform_calculation(operation, column_name, options) end end # Use pluck as a shortcut to select one or more attributes without # loading a bunch of records just to grab the attributes you want. # # Person.pluck(:name) # # instead of # # Person.all.map(&:name) # # Pluck returns an Array of attribute values type-casted to match # the plucked column names, if they can be deduced. Plucking an SQL fragment # returns String values by default. # # Person.pluck(:id) # # SELECT people.id FROM people # # => [1, 2, 3] # # Person.pluck(:id, :name) # # SELECT people.id, people.name FROM people # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] # # Person.pluck('DISTINCT role') # # SELECT DISTINCT role FROM people # # => ['admin', 'member', 'guest'] # # Person.where(age: 21).limit(5).pluck(:id) # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5 # # => [2, 3] # # Person.pluck('DATEDIFF(updated_at, created_at)') # # SELECT DATEDIFF(updated_at, created_at) FROM people # # => ['0', '27761', '173'] # def pluck(*column_names) column_names.map! do |column_name| if column_name.is_a?(Symbol) && attribute_alias?(column_name) attribute_alias(column_name) else column_name.to_s end end if has_include?(column_names.first) construct_relation_for_association_calculations.pluck(*column_names) else relation = spawn relation.select_values = column_names.map { |cn| columns_hash.key?(cn) ? arel_table[cn] : cn } result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values) result.cast_values(klass.column_types) end end # Pluck all the ID's for the relation using the table's primary key # # Person.ids # SELECT people.id FROM people # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id def ids pluck primary_key end private def has_include?(column_name) eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?)) end def perform_calculation(operation, column_name, options = {}) # TODO: Remove options argument as soon we remove support to # activerecord-deprecated_finders. operation = operation.to_s.downcase # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count) distinct = self.distinct_value if operation == "count" column_name ||= select_for_count unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? distinct = true end column_name = primary_key if column_name == :all && distinct distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i end if group_values.any? execute_grouped_calculation(operation, column_name, distinct) else execute_simple_calculation(operation, column_name, distinct) end end def aggregate_column(column_name) if @klass.column_names.include?(column_name.to_s) Arel::Attribute.new(@klass.unscoped.table, column_name) else Arel.sql(column_name == :all ? "*" : column_name.to_s) end end def operation_over_aggregate_column(column, operation, distinct) operation == 'count' ? column.count(distinct) : column.send(operation) end def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY relation = unscope(:order) column_alias = column_name bind_values = nil if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. return 0 if relation.limit_value == 0 query_builder = build_count_subquery(relation, column_name, distinct) bind_values = query_builder.bind_values + relation.bind_values else column = aggregate_column(column_name) select_value = operation_over_aggregate_column(column, operation, distinct) column_alias = select_value.alias column_alias ||= @klass.connection.column_name_for_operation(operation, select_value) relation.select_values = [select_value] query_builder = relation.arel bind_values = query_builder.bind_values + relation.bind_values end result = @klass.connection.select_all(query_builder, nil, bind_values) row = result.first value = row && row.values.first column = result.column_types.fetch(column_alias) do type_for(column_name) end type_cast_calculated_value(value, column, operation) end def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: group_attrs = group_values if group_attrs.first.respond_to?(:to_sym) association = @klass._reflect_on_association(group_attrs.first) associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations group_fields = Array(associated ? association.foreign_key : group_attrs) else group_fields = group_attrs end group_aliases = group_fields.map { |field| column_alias_for(field) } group_columns = group_aliases.zip(group_fields).map { |aliaz,field| [aliaz, field] } group = group_fields if operation == 'count' && column_name == :all aggregate_alias = 'count_all' else aggregate_alias = column_alias_for([operation, column_name].join(' ')) end select_values = [ operation_over_aggregate_column( aggregate_column(column_name), operation, distinct).as(aggregate_alias) ] select_values += select_values unless having_values.empty? select_values.concat group_fields.zip(group_aliases).map { |field,aliaz| if field.respond_to?(:as) field.as(aliaz) else "#{field} AS #{aliaz}" end } relation = except(:group) relation.group_values = group relation.select_values = select_values calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values) if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } key_records = association.klass.base_class.find(key_ids) key_records = Hash[key_records.map { |r| [r.id, r] }] end Hash[calculated_data.map do |row| key = group_columns.map { |aliaz, col_name| column = calculated_data.column_types.fetch(aliaz) do type_for(col_name) end type_cast_calculated_value(row[aliaz], column) } key = key.first if key.size == 1 key = key_records[key] if associated column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) } [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)] end] end # Converts the given keys to the value that the database adapter returns as # a usable column name: # # column_alias_for("users.id") # => "users_id" # column_alias_for("sum(id)") # => "sum_id" # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" # column_alias_for("count(*)") # => "count_all" # column_alias_for("count", "id") # => "count_id" def column_alias_for(keys) if keys.respond_to? :name keys = "#{keys.relation.name}.#{keys.name}" end table_name = keys.to_s.downcase table_name.gsub!(/\*/, 'all') table_name.gsub!(/\W+/, ' ') table_name.strip! table_name.gsub!(/ +/, '_') @klass.connection.table_alias_for(table_name) end def type_for(field) field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last @klass.type_for_attribute(field_name) end def type_cast_calculated_value(value, type, operation = nil) case operation when 'count' then value.to_i when 'sum' then type.type_cast_from_database(value || 0) when 'average' then value.respond_to?(:to_d) ? value.to_d : value else type.type_cast_from_database(value) end end # TODO: refactor to allow non-string `select_values` (eg. Arel nodes). def select_for_count if select_values.present? select_values.join(", ") else :all end end def build_count_subquery(relation, column_name, distinct) column_alias = Arel.sql('count_column') subquery_alias = Arel.sql('subquery_for_count') aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) relation.select_values = [aliased_column] arel = relation.arel subquery = arel.as(subquery_alias) sm = Arel::SelectManager.new relation.engine sm.bind_values = arel.bind_values select_value = operation_over_aggregate_column(column_alias, 'count', distinct) sm.project(select_value).from(subquery) end end end rails-4.2.6/activerecord/lib/active_record/relation/delegation.rb000066400000000000000000000100421266740050600251460ustar00rootroot00000000000000require 'set' require 'active_support/concern' require 'active_support/deprecation' module ActiveRecord module Delegation # :nodoc: module DelegateCache def relation_delegate_class(klass) # :nodoc: @relation_delegate_cache[klass] end def initialize_relation_delegate_cache # :nodoc: @relation_delegate_cache = cache = {} [ ActiveRecord::Relation, ActiveRecord::Associations::CollectionProxy, ActiveRecord::AssociationRelation ].each do |klass| delegate = Class.new(klass) { include ClassSpecificRelation } const_set klass.name.gsub('::', '_'), delegate cache[klass] = delegate end end def inherited(child_class) child_class.initialize_relation_delegate_cache super end end extend ActiveSupport::Concern # This module creates compiled delegation methods dynamically at runtime, which makes # subsequent calls to that method faster by avoiding method_missing. The delegations # may vary depending on the klass of a relation, so we create a subclass of Relation # for each different klass, and the delegations are compiled into that subclass only. BLACKLISTED_ARRAY_METHODS = [ :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!, :shuffle!, :slice!, :sort!, :sort_by!, :delete_if, :keep_if, :pop, :shift, :delete_at, :select! ].to_set # :nodoc: delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, :to => :klass module ClassSpecificRelation # :nodoc: extend ActiveSupport::Concern included do @delegation_mutex = Mutex.new end module ClassMethods # :nodoc: def name superclass.name end def delegate_to_scoped_klass(method) @delegation_mutex.synchronize do return if method_defined?(method) if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/ module_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method}(*args, &block) scoping { @klass.#{method}(*args, &block) } end RUBY else define_method method do |*args, &block| scoping { @klass.public_send(method, *args, &block) } end end end end def delegate(method, opts = {}) @delegation_mutex.synchronize do return if method_defined?(method) super end end end protected def method_missing(method, *args, &block) if @klass.respond_to?(method) self.class.delegate_to_scoped_klass(method) scoping { @klass.public_send(method, *args, &block) } elsif arel.respond_to?(method) self.class.delegate method, :to => :arel arel.public_send(method, *args, &block) else super end end end module ClassMethods # :nodoc: def create(klass, *args) relation_class_for(klass).new(klass, *args) end private def relation_class_for(klass) klass.relation_delegate_class(self) end end def respond_to?(method, include_private = false) super || @klass.respond_to?(method, include_private) || array_delegable?(method) || arel.respond_to?(method, include_private) end protected def array_delegable?(method) Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method) end def method_missing(method, *args, &block) if @klass.respond_to?(method) scoping { @klass.public_send(method, *args, &block) } elsif array_delegable?(method) to_a.public_send(method, *args, &block) elsif arel.respond_to?(method) arel.public_send(method, *args, &block) else super end end end end rails-4.2.6/activerecord/lib/active_record/relation/finder_methods.rb000066400000000000000000000437531266740050600260440ustar00rootroot00000000000000require 'active_support/deprecation' require 'active_support/core_ext/string/filters' module ActiveRecord module FinderMethods ONE_AS_ONE = '1 AS one' # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key # is an integer, find by id coerces its arguments using +to_i+. # # Person.find(1) # returns the object for ID = 1 # Person.find("1") # returns the object for ID = 1 # Person.find("31-sarah") # returns the object for ID = 31 # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6) # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17) # Person.find([1]) # returns an array for the object with ID = 1 # Person.where("administrator = 1").order("created_on DESC").find(1) # # ActiveRecord::RecordNotFound will be raised if one or more ids are not found. # # NOTE: The returned records may not be in the same order as the ids you # provide since database rows are unordered. You'd need to provide an explicit order # option if you want the results are sorted. # # ==== Find with lock # # Example for find with a lock: Imagine two concurrent transactions: # each will read person.visits == 2, add 1 to it, and save, resulting # in two saves of person.visits = 3. By locking the row, the second # transaction has to wait until the first is finished; we get the # expected person.visits == 4. # # Person.transaction do # person = Person.lock(true).find(1) # person.visits += 1 # person.save! # end # # ==== Variations of +find+ # # Person.where(name: 'Spartacus', rating: 4) # # returns a chainable list (which can be empty). # # Person.find_by(name: 'Spartacus', rating: 4) # # returns the first item or nil. # # Person.where(name: 'Spartacus', rating: 4).first_or_initialize # # returns the first item or returns a new instance (requires you call .save to persist against the database). # # Person.where(name: 'Spartacus', rating: 4).first_or_create # # returns the first item or creates it and returns it, available since Rails 3.2.1. # # ==== Alternatives for +find+ # # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none) # # returns a boolean indicating if any record with the given conditions exist. # # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3") # # returns a chainable list of instances with only the mentioned fields. # # Person.where(name: 'Spartacus', rating: 4).ids # # returns an Array of ids, available since Rails 3.2.1. # # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2) # # returns an Array of the required fields, available since Rails 3.1. def find(*args) if block_given? to_a.find(*args) { |*block_args| yield(*block_args) } else find_with_ids(*args) end end # Finds the first record matching the specified conditions. There # is no implied ordering so if order matters, you should specify it # yourself. # # If no record is found, returns nil. # # Post.find_by name: 'Spartacus', rating: 4 # Post.find_by "published_at < ?", 2.weeks.ago def find_by(*args) where(*args).take rescue RangeError nil end # Like find_by, except that if no record is found, raises # an ActiveRecord::RecordNotFound error. def find_by!(*args) where(*args).take! rescue RangeError raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value" end # Gives a record (or N records if a parameter is supplied) without any implied # order. The order will depend on the database implementation. # If an order is supplied it will be respected. # # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1 # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5 # Person.where(["name LIKE '%?'", name]).take def take(limit = nil) limit ? limit(limit).to_a : find_take end # Same as +take+ but raises ActiveRecord::RecordNotFound if no record # is found. Note that take! accepts no arguments. def take! take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") end # Find the first record (or first N records if a parameter is supplied). # If no order is defined it will order by primary key. # # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1 # Person.where(["user_name = ?", user_name]).first # Person.where(["user_name = :u", { u: user_name }]).first # Person.order("created_on DESC").offset(5).first # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3 # def first(limit = nil) if limit find_nth_with_limit(offset_index, limit) else find_nth(0, offset_index) end end # Same as +first+ but raises ActiveRecord::RecordNotFound if no record # is found. Note that first! accepts no arguments. def first! find_nth! 0 end # Find the last record (or last N records if a parameter is supplied). # If no order is defined it will order by primary key. # # Person.last # returns the last object fetched by SELECT * FROM people # Person.where(["user_name = ?", user_name]).last # Person.order("created_on DESC").offset(5).last # Person.last(3) # returns the last three objects fetched by SELECT * FROM people. # # Take note that in that last case, the results are sorted in ascending order: # # [#, #, #] # # and not: # # [#, #, #] def last(limit = nil) if limit if order_values.empty? && primary_key order(arel_table[primary_key].desc).limit(limit).reverse else to_a.last(limit) end else find_last end end # Same as +last+ but raises ActiveRecord::RecordNotFound if no record # is found. Note that last! accepts no arguments. def last! last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") end # Find the second record. # If no order is defined it will order by primary key. # # Person.second # returns the second object fetched by SELECT * FROM people # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4) # Person.where(["user_name = :u", { u: user_name }]).second def second find_nth(1, offset_index) end # Same as +second+ but raises ActiveRecord::RecordNotFound if no record # is found. def second! find_nth! 1 end # Find the third record. # If no order is defined it will order by primary key. # # Person.third # returns the third object fetched by SELECT * FROM people # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5) # Person.where(["user_name = :u", { u: user_name }]).third def third find_nth(2, offset_index) end # Same as +third+ but raises ActiveRecord::RecordNotFound if no record # is found. def third! find_nth! 2 end # Find the fourth record. # If no order is defined it will order by primary key. # # Person.fourth # returns the fourth object fetched by SELECT * FROM people # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6) # Person.where(["user_name = :u", { u: user_name }]).fourth def fourth find_nth(3, offset_index) end # Same as +fourth+ but raises ActiveRecord::RecordNotFound if no record # is found. def fourth! find_nth! 3 end # Find the fifth record. # If no order is defined it will order by primary key. # # Person.fifth # returns the fifth object fetched by SELECT * FROM people # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7) # Person.where(["user_name = :u", { u: user_name }]).fifth def fifth find_nth(4, offset_index) end # Same as +fifth+ but raises ActiveRecord::RecordNotFound if no record # is found. def fifth! find_nth! 4 end # Find the forty-second record. Also known as accessing "the reddit". # If no order is defined it will order by primary key. # # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44) # Person.where(["user_name = :u", { u: user_name }]).forty_two def forty_two find_nth(41, offset_index) end # Same as +forty_two+ but raises ActiveRecord::RecordNotFound if no record # is found. def forty_two! find_nth! 41 end # Returns +true+ if a record exists in the table that matches the +id+ or # conditions given, or +false+ otherwise. The argument can take six forms: # # * Integer - Finds the record with this primary key. # * String - Finds the record with a primary key corresponding to this # string (such as '5'). # * Array - Finds the record that matches these +find+-style conditions # (such as ['name LIKE ?', "%#{query}%"]). # * Hash - Finds the record that matches these +find+-style conditions # (such as {name: 'David'}). # * +false+ - Returns always +false+. # * No args - Returns +false+ if the table is empty, +true+ otherwise. # # For more information about specifying conditions as a hash or array, # see the Conditions section in the introduction to ActiveRecord::Base. # # Note: You can't pass in a condition as a string (like name = # 'Jamie'), since it would be sanitized and then queried against # the primary key column, like id = 'name = \'Jamie\''. # # Person.exists?(5) # Person.exists?('5') # Person.exists?(['name LIKE ?', "%#{query}%"]) # Person.exists?(id: [1, 4, 8]) # Person.exists?(name: 'David') # Person.exists?(false) # Person.exists? def exists?(conditions = :none) if Base === conditions conditions = conditions.id ActiveSupport::Deprecation.warn(<<-MSG.squish) You are passing an instance of ActiveRecord::Base to `exists?`. Please pass the id of the object by calling `.id` MSG end return false if !conditions relation = apply_join_dependency(self, construct_join_dependency) return false if ActiveRecord::NullRelation === relation relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1) case conditions when Array, Hash relation = relation.where(conditions) else unless conditions == :none relation = relation.where(primary_key => conditions) end end connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false end # This method is called whenever no records are found with either a single # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception. # # The error message is different depending on whether a single id or # multiple ids are provided. If multiple ids are provided, then the number # of results obtained should be provided in the +result_size+ argument and # the expected number of results should be provided in the +expected_size+ # argument. def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc: conditions = arel.where_sql conditions = " [#{conditions}]" if conditions if Array(ids).size == 1 error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}" else error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': " error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})" end raise RecordNotFound, error end private def offset_index offset_value || 0 end def find_with_associations # NOTE: the JoinDependency constructed here needs to know about # any joins already present in `self`, so pass them in # # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136 # incorrect SQL is generated. In that case, the join dependency for # SpecialCategorizations is constructed without knowledge of the # preexisting join in joins_values to categorizations (by way of # the `has_many :through` for categories). # join_dependency = construct_join_dependency(joins_values) aliases = join_dependency.aliases relation = select aliases.columns relation = apply_join_dependency(relation, join_dependency) if block_given? yield relation else if ActiveRecord::NullRelation === relation [] else arel = relation.arel rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values) join_dependency.instantiate(rows, aliases) end end end def construct_join_dependency(joins = []) including = eager_load_values + includes_values ActiveRecord::Associations::JoinDependency.new(@klass, including, joins) end def construct_relation_for_association_calculations from = arel.froms.first if Arel::Table === from apply_join_dependency(self, construct_join_dependency(joins_values)) else # FIXME: as far as I can tell, `from` will always be an Arel::Table. # There are no tests that test this branch, but presumably it's # possible for `from` to be a list? apply_join_dependency(self, construct_join_dependency(from)) end end def apply_join_dependency(relation, join_dependency) relation = relation.except(:includes, :eager_load, :preload) relation = relation.joins join_dependency if using_limitable_reflections?(join_dependency.reflections) relation else if relation.limit_value limited_ids = limited_ids_for(relation) limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids)) end relation.except(:limit, :offset) end end def limited_ids_for(relation) values = @klass.connection.columns_for_distinct( "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) relation = relation.except(:select).select(values).distinct! arel = relation.arel id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values) id_rows.map {|row| row[primary_key]} end def using_limitable_reflections?(reflections) reflections.none? { |r| r.collection? } end protected def find_with_ids(*ids) raise UnknownPrimaryKey.new(@klass) if primary_key.nil? expects_array = ids.first.kind_of?(Array) return ids.first if expects_array && ids.first.empty? ids = ids.flatten.compact.uniq case ids.size when 0 raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" when 1 result = find_one(ids.first) expects_array ? [ result ] : result else find_some(ids) end rescue RangeError raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" end def find_one(id) if ActiveRecord::Base === id id = id.id ActiveSupport::Deprecation.warn(<<-MSG.squish) You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id` MSG end relation = where(primary_key => id) record = relation.take raise_record_not_found_exception!(id, 0, 1) unless record record end def find_some(ids) result = where(primary_key => ids).to_a expected_size = if limit_value && ids.size > limit_value limit_value else ids.size end # 11 ids with limit 3, offset 9 should give 2 results. if offset_value && (ids.size - offset_value < expected_size) expected_size = ids.size - offset_value end if result.size == expected_size result else raise_record_not_found_exception!(ids, result.size, expected_size) end end def find_take if loaded? @records.first else @take ||= limit(1).to_a.first end end def find_nth(index, offset) if loaded? @records[index] else offset += index @offsets[offset] ||= find_nth_with_limit(offset, 1).first end end def find_nth!(index) find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") end def find_nth_with_limit(offset, limit) relation = if order_values.empty? && primary_key order(arel_table[primary_key].asc) else self end relation = relation.offset(offset) unless offset.zero? relation.limit(limit).to_a end def find_last if loaded? @records.last else @last ||= if limit_value to_a.last else reverse_order.limit(1).to_a.first end end end end end rails-4.2.6/activerecord/lib/active_record/relation/merger.rb000066400000000000000000000136221266740050600243230ustar00rootroot00000000000000require 'active_support/core_ext/hash/keys' require "set" module ActiveRecord class Relation class HashMerger # :nodoc: attr_reader :relation, :hash def initialize(relation, hash) hash.assert_valid_keys(*Relation::VALUE_METHODS) @relation = relation @hash = hash end def merge #:nodoc: Merger.new(relation, other).merge end # Applying values to a relation has some side effects. E.g. # interpolation might take place for where values. So we should # build a relation to merge in rather than directly merging # the values. def other other = Relation.create(relation.klass, relation.table) hash.each { |k, v| if k == :joins if Hash === v other.joins!(v) else other.joins!(*v) end elsif k == :select other._select!(v) else other.send("#{k}!", v) end } other end end class Merger # :nodoc: attr_reader :relation, :values, :other def initialize(relation, other) @relation = relation @values = other.values @other = other end NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS + Relation::MULTI_VALUE_METHODS - [:includes, :preload, :joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: def normal_values NORMAL_VALUES end def merge normal_values.each do |name| value = values[name] # The unless clause is here mostly for performance reasons (since the `send` call might be moderately # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values # don't fall through the cracks. unless value.nil? || (value.blank? && false != value) if name == :select relation._select!(*value) else relation.send("#{name}!", *value) end end end merge_multi_values merge_single_values merge_preloads merge_joins relation end private def merge_preloads return if other.preload_values.empty? && other.includes_values.empty? if other.klass == relation.klass relation.preload!(*other.preload_values) unless other.preload_values.empty? relation.includes!(other.includes_values) unless other.includes_values.empty? else reflection = relation.klass.reflect_on_all_associations.find do |r| r.class_name == other.klass.name end || return unless other.preload_values.empty? relation.preload! reflection.name => other.preload_values end unless other.includes_values.empty? relation.includes! reflection.name => other.includes_values end end end def merge_joins return if other.joins_values.blank? if other.klass == relation.klass relation.joins!(*other.joins_values) else joins_dependency, rest = other.joins_values.partition do |join| case join when Hash, Symbol, Array true else false end end join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass, joins_dependency, []) relation.joins! rest @relation = relation.joins join_dependency end end def merge_multi_values lhs_wheres = relation.where_values rhs_wheres = other.where_values lhs_binds = relation.bind_values rhs_binds = other.bind_values removed, kept = partition_overwrites(lhs_wheres, rhs_wheres) where_values = kept + rhs_wheres bind_values = filter_binds(lhs_binds, removed) + rhs_binds relation.where_values = where_values relation.bind_values = bind_values if other.reordering_value # override any order specified in the original relation relation.reorder! other.order_values elsif other.order_values # merge in order_values from relation relation.order! other.order_values end relation.extend(*other.extending_values) unless other.extending_values.blank? end def merge_single_values relation.from_value = other.from_value unless relation.from_value relation.lock_value = other.lock_value unless relation.lock_value unless other.create_with_value.blank? relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) end end def filter_binds(lhs_binds, removed_wheres) return lhs_binds if removed_wheres.empty? set = Set.new removed_wheres.map { |x| x.left.name.to_s } lhs_binds.dup.delete_if { |col,_| set.include? col.name } end # Remove equalities from the existing relation with a LHS which is # present in the relation being merged in. # returns [things_to_remove, things_to_keep] def partition_overwrites(lhs_wheres, rhs_wheres) if lhs_wheres.empty? || rhs_wheres.empty? return [[], lhs_wheres] end nodes = rhs_wheres.find_all do |w| w.respond_to?(:operator) && w.operator == :== end seen = Set.new(nodes) { |node| node.left } lhs_wheres.partition do |w| w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left) end end end end end rails-4.2.6/activerecord/lib/active_record/relation/predicate_builder.rb000066400000000000000000000112771266740050600265140ustar00rootroot00000000000000module ActiveRecord class PredicateBuilder # :nodoc: @handlers = [] autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler' autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler' def self.resolve_column_aliases(klass, hash) # This method is a hot spot, so for now, use Hash[] to dup the hash. # https://bugs.ruby-lang.org/issues/7166 hash = Hash[hash] hash.keys.grep(Symbol) do |key| if klass.attribute_alias? key hash[klass.attribute_alias(key)] = hash.delete key end end hash end def self.build_from_hash(klass, attributes, default_table) queries = [] attributes.each do |column, value| table = default_table if value.is_a?(Hash) if value.empty? queries << '1=0' else table = Arel::Table.new(column, default_table.engine) association = klass._reflect_on_association(column) value.each do |k, v| queries.concat expand(association && association.klass, table, k, v) end end else column = column.to_s if column.include?('.') table_name, column = column.split('.', 2) table = Arel::Table.new(table_name, default_table.engine) end queries.concat expand(klass, table, column, value) end end queries end def self.expand(klass, table, column, value) queries = [] # Find the foreign key when using queries such as: # Post.where(author: author) # # For polymorphic relationships, find the foreign key and type: # PriceEstimate.where(estimate_of: treasure) if klass && reflection = klass._reflect_on_association(column) base_class = polymorphic_base_class_from_value(value) if reflection.polymorphic? && base_class queries << build(table[reflection.foreign_type], base_class) end column = reflection.foreign_key if base_class primary_key = reflection.association_primary_key(base_class) value = convert_value_to_association_ids(value, primary_key) end end queries << build(table[column], value) queries end def self.polymorphic_base_class_from_value(value) case value when Relation value.klass.base_class when Array val = value.compact.first val.class.base_class if val.is_a?(Base) when Base value.class.base_class end end def self.references(attributes) attributes.map do |key, value| if value.is_a?(Hash) key else key = key.to_s key.split('.').first if key.include?('.') end end.compact end # Define how a class is converted to Arel nodes when passed to +where+. # The handler can be any object that responds to +call+, and will be used # for any value that +===+ the class given. For example: # # MyCustomDateRange = Struct.new(:start, :end) # handler = proc do |column, range| # Arel::Nodes::Between.new(column, # Arel::Nodes::And.new([range.start, range.end]) # ) # end # ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler) def self.register_handler(klass, handler) @handlers.unshift([klass, handler]) end BASIC_OBJECT_HANDLER = ->(attribute, value) { attribute.eq(value) } # :nodoc: register_handler(BasicObject, BASIC_OBJECT_HANDLER) # FIXME: I think we need to deprecate this behavior register_handler(Class, ->(attribute, value) { attribute.eq(value.name) }) register_handler(Base, ->(attribute, value) { attribute.eq(value.id) }) register_handler(Range, ->(attribute, value) { attribute.between(value) }) register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new) def self.build(attribute, value) handler_for(value).call(attribute, value) end private_class_method :build def self.handler_for(object) @handlers.detect { |klass, _| klass === object }.last end private_class_method :handler_for def self.convert_value_to_association_ids(value, primary_key) case value when Relation value.select(primary_key) when Array value.map { |v| convert_value_to_association_ids(v, primary_key) } when Base value._read_attribute(primary_key) else value end end def self.can_be_bound?(value) # :nodoc: !value.nil? && !value.is_a?(Hash) && handler_for(value) == BASIC_OBJECT_HANDLER end end end rails-4.2.6/activerecord/lib/active_record/relation/predicate_builder/000077500000000000000000000000001266740050600261575ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb000066400000000000000000000025601266740050600313220ustar00rootroot00000000000000require 'active_support/core_ext/string/filters' module ActiveRecord class PredicateBuilder class ArrayHandler # :nodoc: def call(attribute, value) values = value.map { |x| x.is_a?(Base) ? x.id : x } nils, values = values.partition(&:nil?) if values.any? { |val| val.is_a?(Array) } ActiveSupport::Deprecation.warn(<<-MSG.squish) Passing a nested array to Active Record finder methods is deprecated and will be removed. Flatten your array before using it for 'IN' conditions. MSG values = values.flatten end return attribute.in([]) if values.empty? && nils.empty? ranges, values = values.partition { |v| v.is_a?(Range) } values_predicate = case values.length when 0 then NullPredicate when 1 then attribute.eq(values.first) else attribute.in(values) end unless nils.empty? values_predicate = values_predicate.or(attribute.eq(nil)) end array_predicates = ranges.map { |range| attribute.between(range) } array_predicates.unshift(values_predicate) array_predicates.inject { |composite, predicate| composite.or(predicate) } end module NullPredicate # :nodoc: def self.or(other) other end end end end end rails-4.2.6/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb000066400000000000000000000004621266740050600320200ustar00rootroot00000000000000module ActiveRecord class PredicateBuilder class RelationHandler # :nodoc: def call(attribute, value) if value.select_values.empty? value = value.select(value.klass.arel_table[value.klass.primary_key]) end attribute.in(value.arel) end end end end rails-4.2.6/activerecord/lib/active_record/relation/query_methods.rb000066400000000000000000001144441266740050600257360ustar00rootroot00000000000000require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/filters' require 'active_model/forbidden_attributes_protection' module ActiveRecord module QueryMethods extend ActiveSupport::Concern include ActiveModel::ForbiddenAttributesProtection # WhereChain objects act as placeholder for queries in which #where does not have any parameter. # In this case, #where must be chained with #not to return a new relation. class WhereChain def initialize(scope) @scope = scope end # Returns a new relation expressing WHERE + NOT condition according to # the conditions in the arguments. # # +not+ accepts conditions as a string, array, or hash. See #where for # more details on each format. # # User.where.not("name = 'Jon'") # # SELECT * FROM users WHERE NOT (name = 'Jon') # # User.where.not(["name = ?", "Jon"]) # # SELECT * FROM users WHERE NOT (name = 'Jon') # # User.where.not(name: "Jon") # # SELECT * FROM users WHERE name != 'Jon' # # User.where.not(name: nil) # # SELECT * FROM users WHERE name IS NOT NULL # # User.where.not(name: %w(Ko1 Nobu)) # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu') # # User.where.not(name: "Jon", role: "admin") # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin' def not(opts, *rest) where_value = @scope.send(:build_where, opts, rest).map do |rel| case rel when NilClass raise ArgumentError, 'Invalid argument for .where.not(), got nil.' when Arel::Nodes::In Arel::Nodes::NotIn.new(rel.left, rel.right) when Arel::Nodes::Equality Arel::Nodes::NotEqual.new(rel.left, rel.right) when String Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel)) else Arel::Nodes::Not.new(rel) end end @scope.references!(PredicateBuilder.references(opts)) if Hash === opts @scope.where_values += where_value @scope end end Relation::MULTI_VALUE_METHODS.each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}_values # def select_values @values[:#{name}] || [] # @values[:select] || [] end # end # def #{name}_values=(values) # def select_values=(values) raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded check_cached_relation @values[:#{name}] = values # @values[:select] = values end # end CODE end (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}_value # def readonly_value @values[:#{name}] # @values[:readonly] end # end CODE end Relation::SINGLE_VALUE_METHODS.each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}_value=(value) # def readonly_value=(value) raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded check_cached_relation @values[:#{name}] = value # @values[:readonly] = value end # end CODE end def check_cached_relation # :nodoc: if defined?(@arel) && @arel @arel = nil ActiveSupport::Deprecation.warn(<<-MSG.squish) Modifying already cached Relation. The cache will be reset. Use a cloned Relation to prevent this warning. MSG end end def create_with_value # :nodoc: @values[:create_with] || {} end alias extensions extending_values # Specify relationships to be included in the result set. For # example: # # users = User.includes(:address) # users.each do |user| # user.address.city # end # # allows you to access the +address+ attribute of the +User+ model without # firing an additional query. This will often result in a # performance improvement over a simple +join+. # # You can also specify multiple relationships, like this: # # users = User.includes(:address, :friends) # # Loading nested relationships is possible using a Hash: # # users = User.includes(:address, friends: [:address, :followers]) # # === conditions # # If you want to add conditions to your included models you'll have # to explicitly reference them. For example: # # User.includes(:posts).where('posts.name = ?', 'example') # # Will throw an error, but this will work: # # User.includes(:posts).where('posts.name = ?', 'example').references(:posts) # # Note that +includes+ works with association names while +references+ needs # the actual table name. def includes(*args) check_if_method_has_arguments!(:includes, args) spawn.includes!(*args) end def includes!(*args) # :nodoc: args.reject!(&:blank?) args.flatten! self.includes_values |= args self end # Forces eager loading by performing a LEFT OUTER JOIN on +args+: # # User.eager_load(:posts) # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = # "users"."id" def eager_load(*args) check_if_method_has_arguments!(:eager_load, args) spawn.eager_load!(*args) end def eager_load!(*args) # :nodoc: self.eager_load_values += args self end # Allows preloading of +args+, in the same way that +includes+ does: # # User.preload(:posts) # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3) def preload(*args) check_if_method_has_arguments!(:preload, args) spawn.preload!(*args) end def preload!(*args) # :nodoc: self.preload_values += args self end # Use to indicate that the given +table_names+ are referenced by an SQL string, # and should therefore be JOINed in any query rather than loaded separately. # This method only works in conjunction with +includes+. # See #includes for more details. # # User.includes(:posts).where("posts.name = 'foo'") # # => Doesn't JOIN the posts table, resulting in an error. # # User.includes(:posts).where("posts.name = 'foo'").references(:posts) # # => Query now knows the string references posts, so adds a JOIN def references(*table_names) check_if_method_has_arguments!(:references, table_names) spawn.references!(*table_names) end def references!(*table_names) # :nodoc: table_names.flatten! table_names.map!(&:to_s) self.references_values |= table_names self end # Works in two unique ways. # # First: takes a block so it can be used just like Array#select. # # Model.all.select { |m| m.field == value } # # This will build an array of objects from the database for the scope, # converting them into an array and iterating through them using Array#select. # # Second: Modifies the SELECT statement for the query so that only certain # fields are retrieved: # # Model.select(:field) # # => [#] # # Although in the above example it looks as though this method returns an # array, it actually returns a relation object and can have other query # methods appended to it, such as the other methods in ActiveRecord::QueryMethods. # # The argument to the method can also be an array of fields. # # Model.select(:field, :other_field, :and_one_more) # # => [#] # # You can also use one or more strings, which will be used unchanged as SELECT fields. # # Model.select('field AS field_one', 'other_field AS field_two') # # => [#] # # If an alias was specified, it will be accessible from the resulting objects: # # Model.select('field AS field_one').first.field_one # # => "value" # # Accessing attributes of an object that do not have fields retrieved by a select # except +id+ will throw ActiveModel::MissingAttributeError: # # Model.select(:field).first.other_field # # => ActiveModel::MissingAttributeError: missing attribute: other_field def select(*fields) if block_given? to_a.select { |*block_args| yield(*block_args) } else raise ArgumentError, 'Call this with at least one field' if fields.empty? spawn._select!(*fields) end end def _select!(*fields) # :nodoc: fields.flatten! fields.map! do |field| klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field end self.select_values += fields self end # Allows to specify a group attribute: # # User.group(:name) # => SELECT "users".* FROM "users" GROUP BY name # # Returns an array with distinct records based on the +group+ attribute: # # User.select([:id, :name]) # => [#, #, # # # User.group(:name) # => [#, #] # # User.group('name AS grouped_name, age') # => [#, #, #] # # Passing in an array of attributes to group by is also supported. # User.select([:id, :first_name]).group(:id, :first_name).first(3) # => [#, #, #] def group(*args) check_if_method_has_arguments!(:group, args) spawn.group!(*args) end def group!(*args) # :nodoc: args.flatten! self.group_values += args self end # Allows to specify an order attribute: # # User.order(:name) # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC # # User.order(email: :desc) # => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC # # User.order(:name, email: :desc) # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC # # User.order('name') # => SELECT "users".* FROM "users" ORDER BY name # # User.order('name DESC') # => SELECT "users".* FROM "users" ORDER BY name DESC # # User.order('name DESC, email') # => SELECT "users".* FROM "users" ORDER BY name DESC, email def order(*args) check_if_method_has_arguments!(:order, args) spawn.order!(*args) end def order!(*args) # :nodoc: preprocess_order_args(args) self.order_values += args self end # Replaces any existing order defined on the relation with the specified order. # # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC' # # Subsequent calls to order on the same relation will be appended. For example: # # User.order('email DESC').reorder('id ASC').order('name ASC') # # generates a query with 'ORDER BY id ASC, name ASC'. def reorder(*args) check_if_method_has_arguments!(:reorder, args) spawn.reorder!(*args) end def reorder!(*args) # :nodoc: preprocess_order_args(args) self.reordering_value = true self.order_values = args self end VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock, :limit, :offset, :joins, :includes, :from, :readonly, :having]) # Removes an unwanted relation that is already defined on a chain of relations. # This is useful when passing around chains of relations and would like to # modify the relations without reconstructing the entire chain. # # User.order('email DESC').unscope(:order) == User.all # # The method arguments are symbols which correspond to the names of the methods # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES. # The method can also be called with multiple arguments. For example: # # User.order('email DESC').select('id').where(name: "John") # .unscope(:order, :select, :where) == User.all # # One can additionally pass a hash as an argument to unscope specific :where values. # This is done by passing a hash with a single key-value pair. The key should be # :where and the value should be the where value to unscope. For example: # # User.where(name: "John", active: true).unscope(where: :name) # == User.where(active: true) # # This method is similar to except, but unlike # except, it persists across merges: # # User.order('email').merge(User.except(:order)) # == User.order('email') # # User.order('email').merge(User.unscope(:order)) # == User.all # # This means it can be used in association definitions: # # has_many :comments, -> { unscope where: :trashed } # def unscope(*args) check_if_method_has_arguments!(:unscope, args) spawn.unscope!(*args) end def unscope!(*args) # :nodoc: args.flatten! self.unscope_values += args args.each do |scope| case scope when Symbol symbol_unscoping(scope) when Hash scope.each do |key, target_value| if key != :where raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key." end Array(target_value).each do |val| where_unscoping(val) end end else raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example." end end self end # Performs a joins on +args+: # # User.joins(:posts) # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" # # You can use strings in order to customize your joins: # # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id") # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id def joins(*args) check_if_method_has_arguments!(:joins, args) spawn.joins!(*args) end def joins!(*args) # :nodoc: args.compact! args.flatten! self.joins_values += args self end def bind(value) # :nodoc: spawn.bind!(value) end def bind!(value) # :nodoc: self.bind_values += [value] self end # Returns a new relation, which is the result of filtering the current relation # according to the conditions in the arguments. # # #where accepts conditions in one of several formats. In the examples below, the resulting # SQL is given as an illustration; the actual query generated may be different depending # on the database adapter. # # === string # # A single string, without additional arguments, is passed to the query # constructor as an SQL fragment, and used in the where clause of the query. # # Client.where("orders_count = '2'") # # SELECT * from clients where orders_count = '2'; # # Note that building your own string from user input may expose your application # to injection attacks if not done properly. As an alternative, it is recommended # to use one of the following methods. # # === array # # If an array is passed, then the first element of the array is treated as a template, and # the remaining elements are inserted into the template to generate the condition. # Active Record takes care of building the query to avoid injection attacks, and will # convert from the ruby type to the database type where needed. Elements are inserted # into the string in the order in which they appear. # # User.where(["name = ? and email = ?", "Joe", "joe@example.com"]) # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; # # Alternatively, you can use named placeholders in the template, and pass a hash as the # second element of the array. The names in the template are replaced with the corresponding # values from the hash. # # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }]) # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; # # This can make for more readable code in complex queries. # # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently # than the previous methods; you are responsible for ensuring that the values in the template # are properly quoted. The values are passed to the connector for quoting, but the caller # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting, # the values are inserted using the same escapes as the Ruby core method Kernel::sprintf. # # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"]) # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; # # If #where is called with multiple arguments, these are treated as if they were passed as # the elements of a single array. # # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" }) # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; # # When using strings to specify conditions, you can use any operator available from # the database. While this provides the most flexibility, you can also unintentionally introduce # dependencies on the underlying database. If your code is intended for general consumption, # test with multiple database backends. # # === hash # # #where will also accept a hash condition, in which the keys are fields and the values # are values to be searched for. # # Fields can be symbols or strings. Values can be single values, arrays, or ranges. # # User.where({ name: "Joe", email: "joe@example.com" }) # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com' # # User.where({ name: ["Alice", "Bob"]}) # # SELECT * FROM users WHERE name IN ('Alice', 'Bob') # # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight }) # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000') # # In the case of a belongs_to relationship, an association key can be used # to specify the model if an ActiveRecord object is used as the value. # # author = Author.find(1) # # # The following queries will be equivalent: # Post.where(author: author) # Post.where(author_id: author) # # This also works with polymorphic belongs_to relationships: # # treasure = Treasure.create(name: 'gold coins') # treasure.price_estimates << PriceEstimate.create(price: 125) # # # The following queries will be equivalent: # PriceEstimate.where(estimate_of: treasure) # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure) # # === Joins # # If the relation is the result of a join, you may create a condition which uses any of the # tables in the join. For string and array conditions, use the table name in the condition. # # User.joins(:posts).where("posts.created_at < ?", Time.now) # # For hash conditions, you can either use the table name in the key, or use a sub-hash. # # User.joins(:posts).where({ "posts.published" => true }) # User.joins(:posts).where({ posts: { published: true } }) # # === no argument # # If no argument is passed, #where returns a new instance of WhereChain, that # can be chained with #not to return a new relation that negates the where clause. # # User.where.not(name: "Jon") # # SELECT * FROM users WHERE name != 'Jon' # # See WhereChain for more details on #not. # # === blank condition # # If the condition is any blank-ish object, then #where is a no-op and returns # the current relation. def where(opts = :chain, *rest) if opts == :chain WhereChain.new(spawn) elsif opts.blank? self else spawn.where!(opts, *rest) end end def where!(opts, *rest) # :nodoc: if Hash === opts opts = sanitize_forbidden_attributes(opts) references!(PredicateBuilder.references(opts)) end self.where_values += build_where(opts, rest) self end # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition. # # Post.where(trashed: true).where(trashed: false) # => WHERE `trashed` = 1 AND `trashed` = 0 # Post.where(trashed: true).rewhere(trashed: false) # => WHERE `trashed` = 0 # Post.where(active: true).where(trashed: true).rewhere(trashed: false) # => WHERE `active` = 1 AND `trashed` = 0 # # This is short-hand for unscope(where: conditions.keys).where(conditions). Note that unlike reorder, we're only unscoping # the named conditions -- not the entire where statement. def rewhere(conditions) unscope(where: conditions.keys).where(conditions) end # Allows to specify a HAVING clause. Note that you can't use HAVING # without also specifying a GROUP clause. # # Order.having('SUM(price) > 30').group('user_id') def having(opts, *rest) opts.blank? ? self : spawn.having!(opts, *rest) end def having!(opts, *rest) # :nodoc: references!(PredicateBuilder.references(opts)) if Hash === opts self.having_values += build_where(opts, rest) self end # Specifies a limit for the number of records to retrieve. # # User.limit(10) # generated SQL has 'LIMIT 10' # # User.limit(10).limit(20) # generated SQL has 'LIMIT 20' def limit(value) spawn.limit!(value) end def limit!(value) # :nodoc: self.limit_value = value self end # Specifies the number of rows to skip before returning rows. # # User.offset(10) # generated SQL has "OFFSET 10" # # Should be used with order. # # User.offset(10).order("name ASC") def offset(value) spawn.offset!(value) end def offset!(value) # :nodoc: self.offset_value = value self end # Specifies locking settings (default to +true+). For more information # on locking, please see +ActiveRecord::Locking+. def lock(locks = true) spawn.lock!(locks) end def lock!(locks = true) # :nodoc: case locks when String, TrueClass, NilClass self.lock_value = locks || true else self.lock_value = false end self end # Returns a chainable relation with zero records. # # The returned relation implements the Null Object pattern. It is an # object with defined null behavior and always returns an empty array of # records without querying the database. # # Any subsequent condition chained to the returned relation will continue # generating an empty relation and will not fire any query to the database. # # Used in cases where a method or scope could return zero records but the # result needs to be chainable. # # For example: # # @posts = current_user.visible_posts.where(name: params[:name]) # # => the visible_posts method is expected to return a chainable Relation # # def visible_posts # case role # when 'Country Manager' # Post.where(country: country) # when 'Reviewer' # Post.published # when 'Bad User' # Post.none # It can't be chained if [] is returned. # end # end # def none where("1=0").extending!(NullRelation) end def none! # :nodoc: where!("1=0").extending!(NullRelation) end # Sets readonly attributes for the returned relation. If value is # true (default), attempting to update a record will result in an error. # # users = User.readonly # users.first.save # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord def readonly(value = true) spawn.readonly!(value) end def readonly!(value = true) # :nodoc: self.readonly_value = value self end # Sets attributes to be used when creating new records from a # relation object. # # users = User.where(name: 'Oscar') # users.new.name # => 'Oscar' # # users = users.create_with(name: 'DHH') # users.new.name # => 'DHH' # # You can pass +nil+ to +create_with+ to reset attributes: # # users = users.create_with(nil) # users.new.name # => 'Oscar' def create_with(value) spawn.create_with!(value) end def create_with!(value) # :nodoc: if value value = sanitize_forbidden_attributes(value) self.create_with_value = create_with_value.merge(value) else self.create_with_value = {} end self end # Specifies table from which the records will be fetched. For example: # # Topic.select('title').from('posts') # # => SELECT title FROM posts # # Can accept other relation objects. For example: # # Topic.select('title').from(Topic.approved) # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery # # Topic.select('a.title').from(Topic.approved, :a) # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a # def from(value, subquery_name = nil) spawn.from!(value, subquery_name) end def from!(value, subquery_name = nil) # :nodoc: self.from_value = [value, subquery_name] if value.is_a? Relation self.bind_values = value.arel.bind_values + value.bind_values + bind_values end self end # Specifies whether the records should be unique or not. For example: # # User.select(:name) # # => Might return two records with the same name # # User.select(:name).distinct # # => Returns 1 record per distinct name # # User.select(:name).distinct.distinct(false) # # => You can also remove the uniqueness def distinct(value = true) spawn.distinct!(value) end alias uniq distinct # Like #distinct, but modifies relation in place. def distinct!(value = true) # :nodoc: self.distinct_value = value self end alias uniq! distinct! # Used to extend a scope with additional methods, either through # a module or through a block provided. # # The object returned is a relation, which can be further extended. # # === Using a module # # module Pagination # def page(number) # # pagination code goes here # end # end # # scope = Model.all.extending(Pagination) # scope.page(params[:page]) # # You can also pass a list of modules: # # scope = Model.all.extending(Pagination, SomethingElse) # # === Using a block # # scope = Model.all.extending do # def page(number) # # pagination code goes here # end # end # scope.page(params[:page]) # # You can also use a block and a module list: # # scope = Model.all.extending(Pagination) do # def per_page(number) # # pagination code goes here # end # end def extending(*modules, &block) if modules.any? || block spawn.extending!(*modules, &block) else self end end def extending!(*modules, &block) # :nodoc: modules << Module.new(&block) if block modules.flatten! self.extending_values += modules extend(*extending_values) if extending_values.any? self end # Reverse the existing order clause on the relation. # # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC' def reverse_order spawn.reverse_order! end def reverse_order! # :nodoc: orders = order_values.uniq orders.reject!(&:blank?) self.order_values = reverse_sql_order(orders) self end # Returns the Arel object associated with the relation. def arel # :nodoc: @arel ||= build_arel end private def build_arel arel = Arel::SelectManager.new(table.engine, table) build_joins(arel, joins_values.flatten) unless joins_values.empty? collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty? arel.take(connection.sanitize_limit(limit_value)) if limit_value arel.skip(offset_value.to_i) if offset_value arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? build_order(arel) build_select(arel) arel.distinct(distinct_value) arel.from(build_from) if from_value arel.lock(lock_value) if lock_value arel end def symbol_unscoping(scope) if !VALID_UNSCOPING_VALUES.include?(scope) raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." end single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope) unscope_code = "#{scope}_value#{'s' unless single_val_method}=" case scope when :order result = [] when :where self.bind_values = [] else result = [] unless single_val_method end self.send(unscope_code, result) end def where_unscoping(target_value) target_value = target_value.to_s self.where_values = where_values.reject do |rel| case rel when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) subrelation.name == target_value end end bind_values.reject! { |col,_| col.name == target_value } end def custom_join_ast(table, joins) joins = joins.reject(&:blank?) return [] if joins.empty? joins.map! do |join| case join when Array join = Arel.sql(join.join(' ')) if array_of_strings?(join) when String join = Arel.sql(join) end table.create_string_join(join) end end def collapse_wheres(arel, wheres) predicates = wheres.map do |where| next where if ::Arel::Nodes::Equality === where where = Arel.sql(where) if String === where Arel::Nodes::Grouping.new(where) end arel.where(Arel::Nodes::And.new(predicates)) if predicates.present? end def build_where(opts, other = []) case opts when String, Array [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash opts = PredicateBuilder.resolve_column_aliases(klass, opts) tmp_opts, bind_values = create_binds(opts) self.bind_values += bind_values attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts) add_relations_to_bind_values(attributes) PredicateBuilder.build_from_hash(klass, attributes, table) else [opts] end end def create_binds(opts) bindable, non_binds = opts.partition do |column, value| PredicateBuilder.can_be_bound?(value) && @klass.columns_hash.include?(column.to_s) && !@klass.reflect_on_aggregation(column) end association_binds, non_binds = non_binds.partition do |column, value| value.is_a?(Hash) && association_for_table(column) end new_opts = {} binds = [] connection = self.connection bindable.each do |(column,value)| binds.push [@klass.columns_hash[column.to_s], value] new_opts[column] = connection.substitute_at(column) end association_binds.each do |(column, value)| association_relation = association_for_table(column).klass.send(:relation) association_new_opts, association_bind = association_relation.send(:create_binds, value) new_opts[column] = association_new_opts binds += association_bind end non_binds.each { |column,value| new_opts[column] = value } [new_opts, binds] end def association_for_table(table_name) table_name = table_name.to_s @klass._reflect_on_association(table_name) || @klass._reflect_on_association(table_name.singularize) end def build_from opts, name = from_value case opts when Relation name ||= 'subquery' opts.arel.as(name.to_s) else opts end end def build_joins(manager, joins) buckets = joins.group_by do |join| case join when String :string_join when Hash, Symbol, Array :association_join when ActiveRecord::Associations::JoinDependency :stashed_join when Arel::Nodes::Join :join_node else raise 'unknown class: %s' % join.class.name end end association_joins = buckets[:association_join] || [] stashed_association_joins = buckets[:stashed_join] || [] join_nodes = (buckets[:join_node] || []).uniq string_joins = (buckets[:string_join] || []).map(&:strip).uniq join_list = join_nodes + custom_join_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( @klass, association_joins, join_list ) join_infos = join_dependency.join_constraints stashed_association_joins join_infos.each do |info| info.joins.each { |join| manager.from(join) } manager.bind_values.concat info.binds end manager.join_sources.concat(join_list) manager end def build_select(arel) if select_values.any? arel.project(*arel_columns(select_values.uniq)) else arel.project(@klass.arel_table[Arel.star]) end end def arel_columns(columns) columns.map do |field| if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_value arel_table[field] elsif Symbol === field connection.quote_table_name(field.to_s) else field end end end def reverse_sql_order(order_query) order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty? order_query.flat_map do |o| case o when Arel::Nodes::Ordering o.reverse when String o.to_s.split(',').map! do |s| s.strip! s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') end else o end end end def array_of_strings?(o) o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) } end def build_order(arel) orders = order_values.uniq orders.reject!(&:blank?) arel.order(*orders) unless orders.empty? end VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, 'asc', 'desc', 'ASC', 'DESC'] # :nodoc: def validate_order_args(args) args.each do |arg| next unless arg.is_a?(Hash) arg.each do |_key, value| raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \ "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value) end end end def preprocess_order_args(order_args) order_args.flatten! validate_order_args(order_args) references = order_args.grep(String) references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! references!(references) if references.any? # if a symbol is given we prepend the quoted table name order_args.map! do |arg| case arg when Symbol arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg) table[arg].asc when Hash arg.map { |field, dir| field = klass.attribute_alias(field) if klass.attribute_alias?(field) table[field].send(dir.downcase) } else arg end end.flatten! end # Checks to make sure that the arguments are not blank. Note that if some # blank-like object were initially passed into the query method, then this # method will not raise an error. # # Example: # # Post.references() # => raises an error # Post.references([]) # => does not raise an error # # This particular method should be called with a method_name and the args # passed into that method as an input. For example: # # def references(*args) # check_if_method_has_arguments!("references", args) # ... # end def check_if_method_has_arguments!(method_name, args) if args.blank? raise ArgumentError, "The method .#{method_name}() must contain arguments." end end def add_relations_to_bind_values(attributes) if attributes.is_a?(Hash) attributes.each_value do |value| if value.is_a?(ActiveRecord::Relation) self.bind_values += value.arel.bind_values + value.bind_values else add_relations_to_bind_values(value) end end end end end end rails-4.2.6/activerecord/lib/active_record/relation/spawn_methods.rb000066400000000000000000000053071266740050600257160ustar00rootroot00000000000000require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/slice' require 'active_record/relation/merger' module ActiveRecord module SpawnMethods # This is overridden by Associations::CollectionProxy def spawn #:nodoc: clone end # Merges in the conditions from other, if other is an ActiveRecord::Relation. # Returns an array representing the intersection of the resulting records with other, if other is an array. # # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) ) # # Performs a single join query with both where conditions. # # recent_posts = Post.order('created_at DESC').first(5) # Post.where(published: true).merge(recent_posts) # # Returns the intersection of all published posts with the 5 most recently created posts. # # (This is just an example. You'd probably want to do this with a single query!) # # Procs will be evaluated by merge: # # Post.where(published: true).merge(-> { joins(:comments) }) # # => Post.where(published: true).joins(:comments) # # This is mainly intended for sharing common conditions between multiple associations. def merge(other) if other.is_a?(Array) to_a & other elsif other spawn.merge!(other) else self end end def merge!(other) # :nodoc: if other.is_a?(Hash) Relation::HashMerger.new(self, other).merge elsif other.is_a?(Relation) Relation::Merger.new(self, other).merge elsif other.respond_to?(:to_proc) instance_exec(&other) else raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation" end end # Removes from the query the condition(s) specified in +skips+. # # Post.order('id asc').except(:order) # discards the order condition # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order def except(*skips) relation_with values.except(*skips) end # Removes any condition from the query other than the one(s) specified in +onlies+. # # Post.order('id asc').only(:where) # discards the order condition # Post.order('id asc').only(:where, :order) # uses the specified order def only(*onlies) if onlies.any? { |o| o == :where } onlies << :bind end relation_with values.slice(*onlies) end private def relation_with(values) # :nodoc: result = Relation.create(klass, table, values) result.extend(*extending_values) if extending_values.any? result end end end rails-4.2.6/activerecord/lib/active_record/result.rb000066400000000000000000000062641266740050600225470ustar00rootroot00000000000000module ActiveRecord ### # This class encapsulates a Result returned from calling +exec_query+ on any # database connection adapter. For example: # # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts') # result # => # # # # Get the column names of the result: # result.columns # # => ["id", "title", "body"] # # # Get the record values of the result: # result.rows # # => [[1, "title_1", "body_1"], # [2, "title_2", "body_2"], # ... # ] # # # Get an array of hashes representing the result (column => value): # result.to_hash # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"}, # {"id" => 2, "title" => "title_2", "body" => "body_2"}, # ... # ] # # # ActiveRecord::Result also includes Enumerable. # result.each do |row| # puts row['title'] + " " + row['body'] # end class Result include Enumerable IDENTITY_TYPE = Type::Value.new # :nodoc: attr_reader :columns, :rows, :column_types def initialize(columns, rows, column_types = {}) @columns = columns @rows = rows @hash_rows = nil @column_types = column_types end def length @rows.length end def each if block_given? hash_rows.each { |row| yield row } else hash_rows.to_enum { @rows.size } end end def to_hash hash_rows end alias :map! :map alias :collect! :map # Returns true if there are no records. def empty? rows.empty? end def to_ary hash_rows end def [](idx) hash_rows[idx] end def last hash_rows.last end def cast_values(type_overrides = {}) # :nodoc: types = columns.map { |name| column_type(name, type_overrides) } result = rows.map do |values| types.zip(values).map { |type, value| type.type_cast_from_database(value) } end columns.one? ? result.map!(&:first) : result end def initialize_copy(other) @columns = columns.dup @rows = rows.dup @column_types = column_types.dup @hash_rows = nil end private def column_type(name, type_overrides = {}) type_overrides.fetch(name) do column_types.fetch(name, IDENTITY_TYPE) end end def hash_rows @hash_rows ||= begin # We freeze the strings to prevent them getting duped when # used as keys in ActiveRecord::Base's @attributes hash columns = @columns.map { |c| c.dup.freeze } @rows.map { |row| # In the past we used Hash[columns.zip(row)] # though elegant, the verbose way is much more efficient # both time and memory wise cause it avoids a big array allocation # this method is called a lot and needs to be micro optimised hash = {} index = 0 length = columns.length while index < length hash[columns[index]] = row[index] index += 1 end hash } end end end end rails-4.2.6/activerecord/lib/active_record/runtime_registry.rb000066400000000000000000000013741266740050600246410ustar00rootroot00000000000000require 'active_support/per_thread_registry' module ActiveRecord # This is a thread locals registry for Active Record. For example: # # ActiveRecord::RuntimeRegistry.connection_handler # # returns the connection handler local to the current thread. # # See the documentation of ActiveSupport::PerThreadRegistry # for further details. class RuntimeRegistry # :nodoc: extend ActiveSupport::PerThreadRegistry attr_accessor :connection_handler, :sql_runtime, :connection_id [:connection_handler, :sql_runtime, :connection_id].each do |val| class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__ class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__ end end end rails-4.2.6/activerecord/lib/active_record/sanitization.rb000066400000000000000000000167121266740050600237440ustar00rootroot00000000000000module ActiveRecord module Sanitization extend ActiveSupport::Concern module ClassMethods def quote_value(value, column) #:nodoc: connection.quote(value, column) end # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to connection.quote. def sanitize(object) #:nodoc: connection.quote(object) end protected # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a WHERE clause. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" # { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'" # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'" def sanitize_sql_for_conditions(condition, table_name = self.table_name) return nil if condition.blank? case condition when Array; sanitize_sql_array(condition) when Hash; sanitize_sql_hash_for_conditions(condition, table_name) else condition end end alias_method :sanitize_sql, :sanitize_sql_for_conditions alias_method :sanitize_conditions, :sanitize_sql # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a SET clause. # { name: nil, group_id: 4 } returns "name = NULL , group_id='4'" def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) case assignments when Array; sanitize_sql_array(assignments) when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) else assignments end end # Accepts a hash of SQL conditions and replaces those attributes # that correspond to a +composed_of+ relationship with their expanded # aggregate attribute values. # Given: # class Person < ActiveRecord::Base # composed_of :address, class_name: "Address", # mapping: [%w(address_street street), %w(address_city city)] # end # Then: # { address: Address.new("813 abc st.", "chicago") } # # => { address_street: "813 abc st.", address_city: "chicago" } def expand_hash_conditions_for_aggregates(attrs) expanded_attrs = {} attrs.each do |attr, value| if aggregation = reflect_on_aggregation(attr.to_sym) mapping = aggregation.mapping mapping.each do |field_attr, aggregate_attr| if mapping.size == 1 && !value.respond_to?(aggregate_attr) expanded_attrs[field_attr] = value else expanded_attrs[field_attr] = value.send(aggregate_attr) end end else expanded_attrs[attr] = value end end expanded_attrs end # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause. # { name: "foo'bar", group_id: 4 } # # => "name='foo''bar' and group_id= 4" # { status: nil, group_id: [1,2,3] } # # => "status IS NULL and group_id IN (1,2,3)" # { age: 13..18 } # # => "age BETWEEN 13 AND 18" # { 'other_records.id' => 7 } # # => "`other_records`.`id` = 7" # { other_records: { id: 7 } } # # => "`other_records`.`id` = 7" # And for value objects on a composed_of relationship: # { address: Address.new("123 abc st.", "chicago") } # # => "address_street='123 abc st.' and address_city='chicago'" def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) ActiveSupport::Deprecation.warn(<<-EOWARN) sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0 EOWARN attrs = PredicateBuilder.resolve_column_aliases self, attrs attrs = expand_hash_conditions_for_aggregates(attrs) table = Arel::Table.new(table_name, arel_engine).alias(default_table_name) PredicateBuilder.build_from_hash(self, attrs, table).map { |b| connection.visitor.compile b }.join(' AND ') end alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. # { status: nil, group_id: 1 } # # => "status = NULL , group_id = 1" def sanitize_sql_hash_for_assignment(attrs, table) c = connection attrs.map do |attr, value| "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}" end.join(', ') end # Sanitizes a +string+ so that it is safe to use within an SQL # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%" def sanitize_sql_like(string, escape_character = "\\") pattern = Regexp.union(escape_character, "%", "_") string.gsub(pattern) { |x| [escape_character, x].join } end # Accepts an array of conditions. The array has each value # sanitized and interpolated into the SQL statement. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" def sanitize_sql_array(ary) statement, *values = ary if values.first.is_a?(Hash) && statement =~ /:\w+/ replace_named_bind_variables(statement, values.first) elsif statement.include?('?') replace_bind_variables(statement, values) elsif statement.blank? statement else statement % values.collect { |value| connection.quote_string(value.to_s) } end end def replace_bind_variables(statement, values) #:nodoc: raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size) bound = values.dup c = connection statement.gsub(/\?/) do replace_bind_variable(bound.shift, c) end end def replace_bind_variable(value, c = connection) #:nodoc: if ActiveRecord::Relation === value value.to_sql else quote_bound_value(value, c) end end def replace_named_bind_variables(statement, bind_vars) #:nodoc: statement.gsub(/(:?):([a-zA-Z]\w*)/) do if $1 == ':' # skip postgresql casts $& # return the whole match elsif bind_vars.include?(match = $2.to_sym) replace_bind_variable(bind_vars[match]) else raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" end end end def quote_bound_value(value, c = connection, column = nil) #:nodoc: if column c.quote(value, column) elsif value.respond_to?(:map) && !value.acts_like?(:string) if value.respond_to?(:empty?) && value.empty? c.quote(nil) else value.map { |v| c.quote(v) }.join(',') end else c.quote(value) end end def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc: unless expected == provided raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}" end end end # TODO: Deprecate this def quoted_id self.class.quote_value(id, column_for_attribute(self.class.primary_key)) end end end rails-4.2.6/activerecord/lib/active_record/schema.rb000066400000000000000000000036261266740050600224700ustar00rootroot00000000000000module ActiveRecord # = Active Record Schema # # Allows programmers to programmatically define a schema in a portable # DSL. This means you can define tables, indexes, etc. without using SQL # directly, so your applications can more easily support multiple # databases. # # Usage: # # ActiveRecord::Schema.define do # create_table :authors do |t| # t.string :name, null: false # end # # add_index :authors, :name, :unique # # create_table :posts do |t| # t.integer :author_id, null: false # t.string :subject # t.text :body # t.boolean :private, default: false # end # # add_index :posts, :author_id # end # # ActiveRecord::Schema is only supported by database adapters that also # support migrations, the two features being very similar. class Schema < Migration # Returns the migrations paths. # # ActiveRecord::Schema.new.migrations_paths # # => ["db/migrate"] # Rails migration path by default. def migrations_paths ActiveRecord::Migrator.migrations_paths end def define(info, &block) # :nodoc: instance_eval(&block) unless info[:version].blank? initialize_schema_migrations_table connection.assume_migrated_upto_version(info[:version], migrations_paths) end end # Eval the given block. All methods available to the current connection # adapter are available within the block, so you can easily use the # database definition DSL to build up your schema (+create_table+, # +add_index+, etc.). # # The +info+ hash is optional, and if given is used to define metadata # about the current schema (currently, only the schema's version): # # ActiveRecord::Schema.define(version: 20380119000001) do # ... # end def self.define(info={}, &block) new.define(info, &block) end end end rails-4.2.6/activerecord/lib/active_record/schema_dumper.rb000066400000000000000000000206571266740050600240470ustar00rootroot00000000000000require 'stringio' require 'active_support/core_ext/big_decimal' module ActiveRecord # = Active Record Schema Dumper # # This class is used to dump the database schema for some connection to some # output format (i.e., ActiveRecord::Schema). class SchemaDumper #:nodoc: private_class_method :new ## # :singleton-method: # A list of tables which should not be dumped to the schema. # Acceptable values are strings as well as regexp. # This setting is only used if ActiveRecord::Base.schema_format == :ruby cattr_accessor :ignore_tables @@ignore_tables = [] class << self def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base) new(connection, generate_options(config)).dump(stream) stream end private def generate_options(config) { table_name_prefix: config.table_name_prefix, table_name_suffix: config.table_name_suffix } end end def dump(stream) header(stream) extensions(stream) tables(stream) trailer(stream) stream end private def initialize(connection, options = {}) @connection = connection @types = @connection.native_database_types @version = Migrator::current_version rescue nil @options = options end def header(stream) define_params = @version ? "version: #{@version}" : "" if stream.respond_to?(:external_encoding) && stream.external_encoding stream.puts "# encoding: #{stream.external_encoding.name}" end stream.puts <
    e stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}" stream.puts "# #{e.message}" stream.puts end stream end def indexes(table, stream) if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| statement_parts = [ "add_index #{remove_prefix_and_suffix(index.table).inspect}", index.columns.inspect, "name: #{index.name.inspect}", ] statement_parts << 'unique: true' if index.unique index_lengths = (index.lengths || []).compact statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any? index_orders = index.orders || {} statement_parts << "order: #{index.orders.inspect}" if index_orders.any? statement_parts << "where: #{index.where.inspect}" if index.where statement_parts << "using: #{index.using.inspect}" if index.using statement_parts << "type: #{index.type.inspect}" if index.type " #{statement_parts.join(', ')}" end stream.puts add_index_statements.sort.join("\n") stream.puts end end def foreign_keys(table, stream) if (foreign_keys = @connection.foreign_keys(table)).any? add_foreign_key_statements = foreign_keys.map do |foreign_key| parts = [ "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}", remove_prefix_and_suffix(foreign_key.to_table).inspect, ] if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table) parts << "column: #{foreign_key.column.inspect}" end if foreign_key.custom_primary_key? parts << "primary_key: #{foreign_key.primary_key.inspect}" end if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/ parts << "name: #{foreign_key.name.inspect}" end parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete " #{parts.join(', ')}" end stream.puts add_foreign_key_statements.sort.join("\n") end end def remove_prefix_and_suffix(table) table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2") end def ignored?(table_name) ['schema_migrations', ignore_tables].flatten.any? do |ignored| ignored === remove_prefix_and_suffix(table_name) end end end end rails-4.2.6/activerecord/lib/active_record/schema_migration.rb000066400000000000000000000026051266740050600245350ustar00rootroot00000000000000require 'active_record/scoping/default' require 'active_record/scoping/named' require 'active_record/base' module ActiveRecord class SchemaMigration < ActiveRecord::Base class << self def primary_key nil end def table_name "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" end def index_name "#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" end def table_exists? connection.table_exists?(table_name) end def create_table(limit=nil) unless table_exists? version_options = {null: false} version_options[:limit] = limit if limit connection.create_table(table_name, id: false) do |t| t.column :version, :string, version_options end connection.add_index table_name, :version, unique: true, name: index_name end end def drop_table if table_exists? connection.remove_index table_name, name: index_name connection.drop_table(table_name) end end def normalize_migration_number(number) "%.3d" % number.to_i end def normalized_versions pluck(:version).map { |v| normalize_migration_number v } end end def version super.to_i end end end rails-4.2.6/activerecord/lib/active_record/scoping.rb000066400000000000000000000053771266740050600226770ustar00rootroot00000000000000require 'active_support/per_thread_registry' module ActiveRecord module Scoping extend ActiveSupport::Concern included do include Default include Named end module ClassMethods def current_scope #:nodoc: ScopeRegistry.value_for(:current_scope, base_class.to_s) end def current_scope=(scope) #:nodoc: ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope) end end def populate_with_current_scope_attributes return unless self.class.scope_attributes? self.class.scope_attributes.each do |att,value| send("#{att}=", value) if respond_to?("#{att}=") end end def initialize_internals_callback super populate_with_current_scope_attributes end # This class stores the +:current_scope+ and +:ignore_default_scope+ values # for different classes. The registry is stored as a thread local, which is # accessed through +ScopeRegistry.current+. # # This class allows you to store and get the scope values on different # classes and different types of scopes. For example, if you are attempting # to get the current_scope for the +Board+ model, then you would use the # following code: # # registry = ActiveRecord::Scoping::ScopeRegistry # registry.set_value_for(:current_scope, "Board", some_new_scope) # # Now when you run: # # registry.value_for(:current_scope, "Board") # # You will obtain whatever was defined in +some_new_scope+. The +value_for+ # and +set_value_for+ methods are delegated to the current +ScopeRegistry+ # object, so the above example code can also be called as: # # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope, # "Board", some_new_scope) class ScopeRegistry # :nodoc: extend ActiveSupport::PerThreadRegistry VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope] def initialize @registry = Hash.new { |hash, key| hash[key] = {} } end # Obtains the value for a given +scope_name+ and +variable_name+. def value_for(scope_type, variable_name) raise_invalid_scope_type!(scope_type) @registry[scope_type][variable_name] end # Sets the +value+ for a given +scope_type+ and +variable_name+. def set_value_for(scope_type, variable_name, value) raise_invalid_scope_type!(scope_type) @registry[scope_type][variable_name] = value end private def raise_invalid_scope_type!(scope_type) if !VALID_SCOPE_TYPES.include?(scope_type) raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" end end end end end rails-4.2.6/activerecord/lib/active_record/scoping/000077500000000000000000000000001266740050600223365ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/scoping/default.rb000066400000000000000000000114131266740050600243070ustar00rootroot00000000000000module ActiveRecord module Scoping module Default extend ActiveSupport::Concern included do # Stores the default scope for the class. class_attribute :default_scopes, instance_writer: false, instance_predicate: false self.default_scopes = [] end module ClassMethods # Returns a scope for the model without the previously set scopes. # # class Post < ActiveRecord::Base # def self.default_scope # where published: true # end # end # # Post.all # Fires "SELECT * FROM posts WHERE published = true" # Post.unscoped.all # Fires "SELECT * FROM posts" # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts" # # This method also accepts a block. All queries inside the block will # not use the previously set scopes. # # Post.unscoped { # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" # } def unscoped block_given? ? relation.scoping { yield } : relation end def before_remove_const #:nodoc: self.current_scope = nil end protected # Use this macro in your model to set a default scope for all operations on # the model. # # class Article < ActiveRecord::Base # default_scope { where(published: true) } # end # # Article.all # => SELECT * FROM articles WHERE published = true # # The +default_scope+ is also applied while creating/building a record. # It is not applied while updating a record. # # Article.new.published # => true # Article.create.published # => true # # (You can also pass any object which responds to +call+ to the # +default_scope+ macro, and it will be called when building the # default scope.) # # If you use multiple +default_scope+ declarations in your model then # they will be merged together: # # class Article < ActiveRecord::Base # default_scope { where(published: true) } # default_scope { where(rating: 'G') } # end # # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' # # This is also the case with inheritance and module includes where the # parent or module defines a +default_scope+ and the child or including # class defines a second one. # # If you need to do more complex things with a default scope, you can # alternatively define it as a class method: # # class Article < ActiveRecord::Base # def self.default_scope # # Should return a scope, you can call 'super' here etc. # end # end def default_scope(scope = nil) scope = Proc.new if block_given? if scope.is_a?(Relation) || !scope.respond_to?(:call) raise ArgumentError, "Support for calling #default_scope without a block is removed. For example instead " \ "of `default_scope where(color: 'red')`, please use " \ "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ "self.default_scope.)" end self.default_scopes += [scope] end def build_default_scope(base_rel = relation) # :nodoc: return if abstract_class? if !Base.is_a?(method(:default_scope).owner) # The user has defined their own default scope method, so call that evaluate_default_scope { default_scope } elsif default_scopes.any? evaluate_default_scope do default_scopes.inject(base_rel) do |default_scope, scope| default_scope.merge(base_rel.scoping { scope.call }) end end end end def ignore_default_scope? # :nodoc: ScopeRegistry.value_for(:ignore_default_scope, self) end def ignore_default_scope=(ignore) # :nodoc: ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore) end # The ignore_default_scope flag is used to prevent an infinite recursion # situation where a default scope references a scope which has a default # scope which references a scope... def evaluate_default_scope # :nodoc: return if ignore_default_scope? begin self.ignore_default_scope = true yield ensure self.ignore_default_scope = false end end end end end end rails-4.2.6/activerecord/lib/active_record/scoping/named.rb000066400000000000000000000143101266740050600237460ustar00rootroot00000000000000require 'active_support/core_ext/array' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/kernel/singleton_class' module ActiveRecord # = Active Record \Named \Scopes module Scoping module Named extend ActiveSupport::Concern module ClassMethods # Returns an ActiveRecord::Relation scope object. # # posts = Post.all # posts.size # Fires "select count(*) from posts" and returns the count # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects # # fruits = Fruit.all # fruits = fruits.where(color: 'red') if options[:red_only] # fruits = fruits.limit(10) if limited? # # You can define a scope that applies to all finders using # ActiveRecord::Base.default_scope. def all if current_scope current_scope.clone else default_scoped end end def default_scoped # :nodoc: relation.merge(build_default_scope) end # Collects attributes from scopes that should be applied when creating # an AR instance for the particular class this is called on. def scope_attributes # :nodoc: all.scope_for_create end # Are there default attributes associated with this scope? def scope_attributes? # :nodoc: current_scope || default_scopes.any? end # Adds a class method for retrieving and querying objects. A \scope # represents a narrowing of a database query, such as # where(color: :red).select('shirts.*').includes(:washing_instructions). # # class Shirt < ActiveRecord::Base # scope :red, -> { where(color: 'red') } # scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) } # end # # The above calls to +scope+ define class methods Shirt.red and # Shirt.dry_clean_only. Shirt.red, in effect, # represents the query Shirt.where(color: 'red'). # # You should always pass a callable object to the scopes defined # with +scope+. This ensures that the scope is re-evaluated each # time it is called. # # Note that this is simply 'syntactic sugar' for defining an actual # class method: # # class Shirt < ActiveRecord::Base # def self.red # where(color: 'red') # end # end # # Unlike Shirt.find(...), however, the object returned by # Shirt.red is not an Array; it resembles the association object # constructed by a +has_many+ declaration. For instance, you can invoke # Shirt.red.first, Shirt.red.count, # Shirt.red.where(size: 'small'). Also, just as with the # association objects, named \scopes act like an Array, implementing # Enumerable; Shirt.red.each(&block), Shirt.red.first, # and Shirt.red.inject(memo, &block) all behave as if # Shirt.red really was an Array. # # These named \scopes are composable. For instance, # Shirt.red.dry_clean_only will produce all shirts that are # both red and dry clean only. Nested finds and calculations also work # with these compositions: Shirt.red.dry_clean_only.count # returns the number of garments for which these criteria obtain. # Similarly with Shirt.red.dry_clean_only.average(:thread_count). # # All scopes are available as class methods on the ActiveRecord::Base # descendant upon which the \scopes were defined. But they are also # available to +has_many+ associations. If, # # class Person < ActiveRecord::Base # has_many :shirts # end # # then elton.shirts.red.dry_clean_only will return all of # Elton's red, dry clean only shirts. # # \Named scopes can also have extensions, just as with +has_many+ # declarations: # # class Shirt < ActiveRecord::Base # scope :red, -> { where(color: 'red') } do # def dom_id # 'red_shirts' # end # end # end # # Scopes can also be used while creating/building a record. # # class Article < ActiveRecord::Base # scope :published, -> { where(published: true) } # end # # Article.published.new.published # => true # Article.published.create.published # => true # # \Class methods on your model are automatically available # on scopes. Assuming the following setup: # # class Article < ActiveRecord::Base # scope :published, -> { where(published: true) } # scope :featured, -> { where(featured: true) } # # def self.latest_article # order('published_at desc').first # end # # def self.titles # pluck(:title) # end # end # # We are able to call the methods like this: # # Article.published.featured.latest_article # Article.featured.titles def scope(name, body, &block) unless body.respond_to?(:call) raise ArgumentError, 'The scope body needs to be callable.' end if dangerous_class_method?(name) raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ "on the model \"#{self.name}\", but Active Record already defined " \ "a class method with the same name." end extension = Module.new(&block) if block singleton_class.send(:define_method, name) do |*args| scope = all.scoping { body.call(*args) } scope = scope.extending(extension) if extension scope || all end end end end end end rails-4.2.6/activerecord/lib/active_record/serialization.rb000066400000000000000000000010301266740050600240700ustar00rootroot00000000000000module ActiveRecord #:nodoc: # = Active Record Serialization module Serialization extend ActiveSupport::Concern include ActiveModel::Serializers::JSON included do self.include_root_in_json = false end def serializable_hash(options = nil) options = options.try(:clone) || {} options[:except] = Array(options[:except]).map { |n| n.to_s } options[:except] |= Array(self.class.inheritance_column) super(options) end end end require 'active_record/serializers/xml_serializer' rails-4.2.6/activerecord/lib/active_record/serializers/000077500000000000000000000000001266740050600232305ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/serializers/xml_serializer.rb000066400000000000000000000160341266740050600266120ustar00rootroot00000000000000require 'active_support/core_ext/hash/conversions' module ActiveRecord #:nodoc: module Serialization include ActiveModel::Serializers::Xml # Builds an XML document to represent the model. Some configuration is # available through +options+. However more complicated cases should # override ActiveRecord::Base#to_xml. # # By default the generated XML document will include the processing # instruction and all the object's attributes. For example: # # # # The First Topic # David # 1 # false # 0 # 2000-01-01T08:28:00+12:00 # 2003-07-16T09:28:00+1200 # Have a nice day # david@loudthinking.com # # 2004-04-15 # # # This behavior can be controlled with :only, :except, # :skip_instruct, :skip_types, :dasherize and :camelize . # The :only and :except options are the same as for the # +attributes+ method. The default is to dasherize all column names, but you # can disable this setting :dasherize to +false+. Setting :camelize # to +true+ will camelize all column names - this also overrides :dasherize. # To not have the column type included in the XML output set :skip_types to +true+. # # For instance: # # topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ]) # # # The First Topic # David # false # Have a nice day # david@loudthinking.com # # 2004-04-15 # # # To include first level associations use :include: # # firm.to_xml include: [ :account, :clients ] # # # # 1 # 1 # 37signals # # # 1 # Summit # # # 1 # Microsoft # # # # 1 # 50 # # # # Additionally, the record being serialized will be passed to a Proc's second # parameter. This allows for ad hoc additions to the resultant document that # incorporate the context of the record being serialized. And by leveraging the # closure created by a Proc, to_xml can be used to add elements that normally fall # outside of the scope of the model -- for example, generating and appending URLs # associated with models. # # proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) } # firm.to_xml procs: [ proc ] # # # # ... normal attributes as shown above ... # slangis73 # # # To include deeper levels of associations pass a hash like this: # # firm.to_xml include: {account: {}, clients: {include: :address}} # # # 1 # 1 # 37signals # # # 1 # Summit #
    # ... #
    #
    # # 1 # Microsoft #
    # ... #
    #
    #
    # # 1 # 50 # #
    # # To include any methods on the model being called use :methods: # # firm.to_xml methods: [ :calculated_earnings, :real_earnings ] # # # # ... normal attributes as shown above ... # 100000000000000000 # 5 # # # To call any additional Procs use :procs. The Procs are passed a # modified version of the options hash that was given to +to_xml+: # # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') } # firm.to_xml procs: [ proc ] # # # # ... normal attributes as shown above ... # def # # # Alternatively, you can yield the builder object as part of the +to_xml+ call: # # firm.to_xml do |xml| # xml.creator do # xml.first_name "David" # xml.last_name "Heinemeier Hansson" # end # end # # # # ... normal attributes as shown above ... # # David # Heinemeier Hansson # # # # As noted above, you may override +to_xml+ in your ActiveRecord::Base # subclasses to have complete control about what's generated. The general # form of doing this is: # # class IHaveMyOwnXML < ActiveRecord::Base # def to_xml(options = {}) # require 'builder' # options[:indent] ||= 2 # xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent]) # xml.instruct! unless options[:skip_instruct] # xml.level_one do # xml.tag!(:second_level, 'content') # end # end # end def to_xml(options = {}, &block) XmlSerializer.new(self, options).serialize(&block) end end class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc: class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: def compute_type klass = @serializable.class column = klass.columns_hash[name] || Type::Value.new type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type { :text => :string, :time => :datetime }[type] || type end protected :compute_type end end end rails-4.2.6/activerecord/lib/active_record/statement_cache.rb000066400000000000000000000060421266740050600243520ustar00rootroot00000000000000module ActiveRecord # Statement cache is used to cache a single statement in order to avoid creating the AST again. # Initializing the cache is done by passing the statement in the create block: # # cache = StatementCache.create(Book.connection) do |params| # Book.where(name: "my book").where("author_id > 3") # end # # The cached statement is executed by using the +execute+ method: # # cache.execute([], Book, Book.connection) # # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped. # Database is queried when +to_a+ is called on the relation. # # If you want to cache the statement without the values you can use the +bind+ method of the # block parameter. # # cache = StatementCache.create(Book.connection) do |params| # Book.where(name: params.bind) # end # # And pass the bind values as the first argument of +execute+ call. # # cache.execute(["my book"], Book, Book.connection) class StatementCache # :nodoc: class Substitute; end # :nodoc: class Query # :nodoc: def initialize(sql) @sql = sql end def sql_for(binds, connection) @sql end end class PartialQuery < Query # :nodoc: def initialize values @values = values @indexes = values.each_with_index.find_all { |thing,i| Arel::Nodes::BindParam === thing }.map(&:last) end def sql_for(binds, connection) val = @values.dup binds = binds.dup @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) } val.join end end def self.query(visitor, ast) Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value end def self.partial_query(visitor, ast, collector) collected = visitor.accept(ast, collector).value PartialQuery.new collected end class Params # :nodoc: def bind; Substitute.new; end end class BindMap # :nodoc: def initialize(bind_values) @indexes = [] @bind_values = bind_values bind_values.each_with_index do |(_, value), i| if Substitute === value @indexes << i end end end def bind(values) bvs = @bind_values.map { |pair| pair.dup } @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] } bvs end end attr_reader :bind_map, :query_builder def self.create(connection, block = Proc.new) relation = block.call Params.new bind_map = BindMap.new relation.bind_values query_builder = connection.cacheable_query relation.arel new query_builder, bind_map end def initialize(query_builder, bind_map) @query_builder = query_builder @bind_map = bind_map end def execute(params, klass, connection) bind_values = bind_map.bind params sql = query_builder.sql_for bind_values, connection klass.find_by_sql sql, bind_values end alias :call :execute end end rails-4.2.6/activerecord/lib/active_record/store.rb000066400000000000000000000157021266740050600223620ustar00rootroot00000000000000require 'active_support/core_ext/hash/indifferent_access' module ActiveRecord # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. # It's like a simple key/value store baked into your record when you don't care about being able to # query that store outside the context of a single record. # # You can then declare accessors to this store that are then accessible just like any other attribute # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's # already built around just accessing attributes on the model. # # Make sure that you declare the database column used for the serialized store as a text, so there's # plenty of room. # # You can set custom coder to encode/decode your serialized attributes to/from different formats. # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. # # NOTE - If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for # the serialization provided by +store+. Simply use +store_accessor+ instead to generate # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access # using a symbol. # # Examples: # # class User < ActiveRecord::Base # store :settings, accessors: [ :color, :homepage ], coder: JSON # end # # u = User.new(color: 'black', homepage: '37signals.com') # u.color # Accessor stored attribute # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor # # # There is no difference between strings and symbols for accessing custom attributes # u.settings[:country] # => 'Denmark' # u.settings['country'] # => 'Denmark' # # # Add additional accessors to an existing store through store_accessor # class SuperUser < User # store_accessor :settings, :privileges, :servants # end # # The stored attribute names can be retrieved using +stored_attributes+. # # User.stored_attributes[:settings] # [:color, :homepage] # # == Overwriting default accessors # # All stored values are automatically available through accessors on the Active Record # object, but sometimes you want to specialize this behavior. This can be done by overwriting # the default accessors (using the same name as the attribute) and calling super # to actually change things. # # class Song < ActiveRecord::Base # # Uses a stored integer to hold the volume adjustment of the song # store :settings, accessors: [:volume_adjustment] # # def volume_adjustment=(decibels) # super(decibels.to_i) # end # # def volume_adjustment # super.to_i # end # end module Store extend ActiveSupport::Concern included do class << self attr_accessor :local_stored_attributes end end module ClassMethods def store(store_attribute, options = {}) serialize store_attribute, IndifferentCoder.new(options[:coder]) store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors end def store_accessor(store_attribute, *keys) keys = keys.flatten _store_accessors_module.module_eval do keys.each do |key| define_method("#{key}=") do |value| write_store_attribute(store_attribute, key, value) end define_method(key) do read_store_attribute(store_attribute, key) end end end # assign new store attribute and create new hash to ensure that each class in the hierarchy # has its own hash of stored attributes. self.local_stored_attributes ||= {} self.local_stored_attributes[store_attribute] ||= [] self.local_stored_attributes[store_attribute] |= keys end def _store_accessors_module # :nodoc: @_store_accessors_module ||= begin mod = Module.new include mod mod end end def stored_attributes parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {} if self.local_stored_attributes parent.merge!(self.local_stored_attributes) { |k, a, b| a | b } end parent end end protected def read_store_attribute(store_attribute, key) accessor = store_accessor_for(store_attribute) accessor.read(self, store_attribute, key) end def write_store_attribute(store_attribute, key, value) accessor = store_accessor_for(store_attribute) accessor.write(self, store_attribute, key, value) end private def store_accessor_for(store_attribute) type_for_attribute(store_attribute.to_s).accessor end class HashAccessor # :nodoc: def self.read(object, attribute, key) prepare(object, attribute) object.public_send(attribute)[key] end def self.write(object, attribute, key, value) prepare(object, attribute) if value != read(object, attribute, key) object.public_send :"#{attribute}_will_change!" object.public_send(attribute)[key] = value end end def self.prepare(object, attribute) object.public_send :"#{attribute}=", {} unless object.send(attribute) end end class StringKeyedHashAccessor < HashAccessor # :nodoc: def self.read(object, attribute, key) super object, attribute, key.to_s end def self.write(object, attribute, key, value) super object, attribute, key.to_s, value end end class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc: def self.prepare(object, store_attribute) attribute = object.send(store_attribute) unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess) attribute = IndifferentCoder.as_indifferent_hash(attribute) object.send :"#{store_attribute}=", attribute end attribute end end class IndifferentCoder # :nodoc: def initialize(coder_or_class_name) @coder = if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump) coder_or_class_name else ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object) end end def dump(obj) @coder.dump self.class.as_indifferent_hash(obj) end def load(yaml) self.class.as_indifferent_hash(@coder.load(yaml || '')) end def self.as_indifferent_hash(obj) case obj when ActiveSupport::HashWithIndifferentAccess obj when Hash obj.with_indifferent_access else ActiveSupport::HashWithIndifferentAccess.new end end end end end rails-4.2.6/activerecord/lib/active_record/tasks/000077500000000000000000000000001266740050600220215ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/tasks/database_tasks.rb000066400000000000000000000244001266740050600253170ustar00rootroot00000000000000require 'active_support/core_ext/string/filters' module ActiveRecord module Tasks # :nodoc: class DatabaseAlreadyExists < StandardError; end # :nodoc: class DatabaseNotSupported < StandardError; end # :nodoc: # ActiveRecord::Tasks::DatabaseTasks is a utility class, which encapsulates # logic behind common tasks used to manage database and migrations. # # The tasks defined here are used with Rake tasks provided by Active Record. # # In order to use DatabaseTasks, a few config values need to be set. All the needed # config values are set by Rails already, so it's necessary to do it only if you # want to change the defaults or when you want to use Active Record outside of Rails # (in such case after configuring the database tasks, you can also use the rake tasks # defined in Active Record). # # The possible config values are: # # * +env+: current environment (like Rails.env). # * +database_configuration+: configuration of your databases (as in +config/database.yml+). # * +db_dir+: your +db+ directory. # * +fixtures_path+: a path to fixtures directory. # * +migrations_paths+: a list of paths to directories with migrations. # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method. # * +root+: a path to the root of the application. # # Example usage of +DatabaseTasks+ outside Rails could look as such: # # include ActiveRecord::Tasks # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml') # DatabaseTasks.db_dir = 'db' # # other settings... # # DatabaseTasks.create_current('production') module DatabaseTasks extend self attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader attr_accessor :database_configuration LOCAL_HOSTS = ['127.0.0.1', 'localhost'] def register_task(pattern, task) @tasks ||= {} @tasks[pattern] = task end register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks) register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks) register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks) def db_dir @db_dir ||= Rails.application.config.paths["db"].first end def migrations_paths @migrations_paths ||= Rails.application.paths['db/migrate'].to_a end def fixtures_path @fixtures_path ||= if ENV['FIXTURES_PATH'] File.join(root, ENV['FIXTURES_PATH']) else File.join(root, 'test', 'fixtures') end end def root @root ||= Rails.root end def env @env ||= Rails.env end def seed_loader @seed_loader ||= Rails.application end def current_config(options = {}) options.reverse_merge! :env => env if options.has_key?(:config) @current_config = options[:config] else @current_config ||= ActiveRecord::Base.configurations[options[:env]] end end def create(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).create rescue DatabaseAlreadyExists $stderr.puts "#{configuration['database']} already exists" rescue Exception => error $stderr.puts error, *(error.backtrace) $stderr.puts "Couldn't create database for #{configuration.inspect}" end def create_all each_local_configuration { |configuration| create configuration } end def create_current(environment = env) each_current_configuration(environment) { |configuration| create configuration } ActiveRecord::Base.establish_connection(environment.to_sym) end def drop(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).drop rescue ActiveRecord::NoDatabaseError $stderr.puts "Database '#{configuration['database']}' does not exist" rescue Exception => error $stderr.puts error, *(error.backtrace) $stderr.puts "Couldn't drop #{configuration['database']}" end def drop_all each_local_configuration { |configuration| drop configuration } end def drop_current(environment = env) each_current_configuration(environment) { |configuration| drop configuration } end def migrate verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil scope = ENV['SCOPE'] verbose_was, Migration.verbose = Migration.verbose, verbose Migrator.migrate(migrations_paths, version) do |migration| scope.blank? || scope == migration.scope end ensure Migration.verbose = verbose_was end def charset_current(environment = env) charset ActiveRecord::Base.configurations[environment] end def charset(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).charset end def collation_current(environment = env) collation ActiveRecord::Base.configurations[environment] end def collation(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).collation end def purge(configuration) class_for_adapter(configuration['adapter']).new(configuration).purge end def purge_all each_local_configuration { |configuration| purge configuration } end def purge_current(environment = env) each_current_configuration(environment) { |configuration| purge configuration } ActiveRecord::Base.establish_connection(environment.to_sym) end def structure_dump(*arguments) configuration = arguments.first filename = arguments.delete_at 1 class_for_adapter(configuration['adapter']).new(*arguments).structure_dump(filename) end def structure_load(*arguments) configuration = arguments.first filename = arguments.delete_at 1 class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename) end def load_schema(format = ActiveRecord::Base.schema_format, file = nil) ActiveSupport::Deprecation.warn(<<-MSG.squish) This method will act on a specific connection in the future. To act on the current connection, use `load_schema_current` instead. MSG load_schema_current(format, file) end def schema_file(format = ActiveRecord::Base.schema_format) case format when :ruby File.join(db_dir, "schema.rb") when :sql File.join(db_dir, "structure.sql") end end # This method is the successor of +load_schema+. We should rename it # after +load_schema+ went through a deprecation cycle. (Rails > 4.2) def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: file ||= schema_file(format) case format when :ruby check_schema_file(file) ActiveRecord::Base.establish_connection(configuration) load(file) when :sql check_schema_file(file) structure_load(configuration, file) else raise ArgumentError, "unknown format #{format.inspect}" end end def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env) if File.exist?(file || schema_file(format)) load_schema_current(format, file, environment) end end def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env) each_current_configuration(environment) { |configuration| load_schema_for configuration, format, file } ActiveRecord::Base.establish_connection(environment.to_sym) end def check_schema_file(filename) unless File.exist?(filename) message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.} message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails) Kernel.abort message end end def load_seed if seed_loader seed_loader.load_seed else raise "You tried to load seed data, but no seed loader is specified. Please specify seed " + "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" + "Seed loader should respond to load_seed method" end end private def class_for_adapter(adapter) key = @tasks.keys.detect { |pattern| adapter[pattern] } unless key raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" end @tasks[key] end def each_current_configuration(environment) environments = [environment] # add test environment only if no RAILS_ENV was specified. environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil? configurations = ActiveRecord::Base.configurations.values_at(*environments) configurations.compact.each do |configuration| yield configuration unless configuration['database'].blank? end end def each_local_configuration ActiveRecord::Base.configurations.each_value do |configuration| next unless configuration['database'] if local_database?(configuration) yield configuration else $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host." end end end def local_database?(configuration) configuration['host'].blank? || LOCAL_HOSTS.include?(configuration['host']) end end end end rails-4.2.6/activerecord/lib/active_record/tasks/mysql_database_tasks.rb000066400000000000000000000117431266740050600265520ustar00rootroot00000000000000module ActiveRecord module Tasks # :nodoc: class MySQLDatabaseTasks # :nodoc: DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8' DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci' ACCESS_DENIED_ERROR = 1045 delegate :connection, :establish_connection, to: ActiveRecord::Base def initialize(configuration) @configuration = configuration end def create establish_connection configuration_without_database connection.create_database configuration['database'], creation_options establish_connection configuration rescue ActiveRecord::StatementInvalid => error if /database exists/ === error.message raise DatabaseAlreadyExists else raise end rescue error_class => error if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR $stdout.print error.error establish_connection root_configuration_without_database connection.create_database configuration['database'], creation_options if configuration['username'] != 'root' connection.execute grant_statement.gsub(/\s+/, ' ').strip end establish_connection configuration else $stderr.puts error.inspect $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding'] end end def drop establish_connection configuration connection.drop_database configuration['database'] end def purge establish_connection configuration connection.recreate_database configuration['database'], creation_options end def charset connection.charset end def collation connection.collation end def structure_dump(filename) args = prepare_command_options args.concat(["--result-file", "#{filename}"]) args.concat(["--no-data"]) args.concat(["#{configuration['database']}"]) run_cmd('mysqldump', args, 'dumping') end def structure_load(filename) args = prepare_command_options args.concat(['--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}]) args.concat(["--database", "#{configuration['database']}"]) run_cmd('mysql', args, 'loading') end private def configuration @configuration end def configuration_without_database configuration.merge('database' => nil) end def creation_options Hash.new.tap do |options| options[:charset] = configuration['encoding'] if configuration.include? 'encoding' options[:collation] = configuration['collation'] if configuration.include? 'collation' # Set default charset only when collation isn't set. options[:charset] ||= DEFAULT_CHARSET unless options[:collation] # Set default collation only when charset is also default. options[:collation] ||= DEFAULT_COLLATION if options[:charset] == DEFAULT_CHARSET end end def error_class if configuration['adapter'] =~ /jdbc/ require 'active_record/railties/jdbcmysql_error' ArJdbcMySQL::Error elsif defined?(Mysql2) Mysql2::Error elsif defined?(Mysql) Mysql::Error else StandardError end end def grant_statement <<-SQL GRANT ALL PRIVILEGES ON #{configuration['database']}.* TO '#{configuration['username']}'@'localhost' IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; SQL end def root_configuration_without_database configuration_without_database.merge( 'username' => 'root', 'password' => root_password ) end def root_password $stdout.print "Please provide the root password for your MySQL installation\n>" $stdin.gets.strip end def prepare_command_options args = [] args.concat(['--user', configuration['username']]) if configuration['username'] args << "--password=#{configuration['password']}" if configuration['password'] args.concat(['--default-character-set', configuration['encoding']]) if configuration['encoding'] configuration.slice('host', 'port', 'socket').each do |k, v| args.concat([ "--#{k}", v.to_s ]) if v end args end def run_cmd(cmd, args, action) fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) end def run_cmd_error(cmd, args, action) msg = "failed to execute:\n" msg << "#{cmd}" msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" msg end end end end rails-4.2.6/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb000066400000000000000000000056731266740050600276150ustar00rootroot00000000000000module ActiveRecord module Tasks # :nodoc: class PostgreSQLDatabaseTasks # :nodoc: DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8' delegate :connection, :establish_connection, :clear_active_connections!, to: ActiveRecord::Base def initialize(configuration) @configuration = configuration end def create(master_established = false) establish_master_connection unless master_established connection.create_database configuration['database'], configuration.merge('encoding' => encoding) establish_connection configuration rescue ActiveRecord::StatementInvalid => error if /database .* already exists/ === error.message raise DatabaseAlreadyExists else raise end end def drop establish_master_connection connection.drop_database configuration['database'] end def charset connection.encoding end def collation connection.collation end def purge clear_active_connections! drop create true end def structure_dump(filename) set_psql_env args = ['-s', '-x', '-O', '-f', filename] search_path = configuration['schema_search_path'] unless search_path.blank? args += search_path.split(',').map do |part| "--schema=#{part.strip}" end end args << configuration['database'] run_cmd('pg_dump', args, 'dumping') File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" } end def structure_load(filename) set_psql_env args = [ '-q', '-f', filename, configuration['database'] ] run_cmd('psql', args, 'loading') end private def configuration @configuration end def encoding configuration['encoding'] || DEFAULT_ENCODING end def establish_master_connection establish_connection configuration.merge( 'database' => 'postgres', 'schema_search_path' => 'public' ) end def set_psql_env ENV['PGHOST'] = configuration['host'] if configuration['host'] ENV['PGPORT'] = configuration['port'].to_s if configuration['port'] ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password'] ENV['PGUSER'] = configuration['username'].to_s if configuration['username'] end def run_cmd(cmd, args, action) fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) end def run_cmd_error(cmd, args, action) msg = "failed to execute:\n" msg << "#{cmd} #{args.join(' ')}\n\n" msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" msg end end end end rails-4.2.6/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb000066400000000000000000000022741266740050600267050ustar00rootroot00000000000000module ActiveRecord module Tasks # :nodoc: class SQLiteDatabaseTasks # :nodoc: delegate :connection, :establish_connection, to: ActiveRecord::Base def initialize(configuration, root = ActiveRecord::Tasks::DatabaseTasks.root) @configuration, @root = configuration, root end def create raise DatabaseAlreadyExists if File.exist?(configuration['database']) establish_connection configuration connection end def drop require 'pathname' path = Pathname.new configuration['database'] file = path.absolute? ? path.to_s : File.join(root, path) FileUtils.rm(file) if File.exist?(file) end def purge drop create end def charset connection.encoding end def structure_dump(filename) dbfile = configuration['database'] `sqlite3 #{dbfile} .schema > #{filename}` end def structure_load(filename) dbfile = configuration['database'] `sqlite3 #{dbfile} < "#{filename}"` end private def configuration @configuration end def root @root end end end end rails-4.2.6/activerecord/lib/active_record/timestamp.rb000066400000000000000000000066361266740050600232370ustar00rootroot00000000000000module ActiveRecord # = Active Record Timestamp # # Active Record automatically timestamps create and update operations if the # table has fields named created_at/created_on or # updated_at/updated_on. # # Timestamping can be turned off by setting: # # config.active_record.record_timestamps = false # # Timestamps are in UTC by default but you can use the local timezone by setting: # # config.active_record.default_timezone = :local # # == Time Zone aware attributes # # By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code. # # config.active_record.time_zone_aware_attributes = true # # This feature can easily be turned off by assigning value false . # # If your attributes are time zone aware and you desire to skip time zone conversion to the current Time.zone # when reading certain attributes then you can do following: # # class Topic < ActiveRecord::Base # self.skip_time_zone_conversion_for_attributes = [:written_on] # end module Timestamp extend ActiveSupport::Concern included do class_attribute :record_timestamps self.record_timestamps = true end def initialize_dup(other) # :nodoc: super clear_timestamp_attributes end private def _create_record if self.record_timestamps current_time = current_time_from_proper_timezone all_timestamp_attributes.each do |column| column = column.to_s if has_attribute?(column) && !attribute_present?(column) write_attribute(column, current_time) end end end super end def _update_record(*args) if should_record_timestamps? current_time = current_time_from_proper_timezone timestamp_attributes_for_update_in_model.each do |column| column = column.to_s next if attribute_changed?(column) write_attribute(column, current_time) end end super end def should_record_timestamps? self.record_timestamps && (!partial_writes? || changed?) end def timestamp_attributes_for_create_in_model timestamp_attributes_for_create.select { |c| self.class.column_names.include?(c.to_s) } end def timestamp_attributes_for_update_in_model timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c.to_s) } end def all_timestamp_attributes_in_model timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model end def timestamp_attributes_for_update [:updated_at, :updated_on] end def timestamp_attributes_for_create [:created_at, :created_on] end def all_timestamp_attributes timestamp_attributes_for_create + timestamp_attributes_for_update end def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update) timestamp_names .map { |attr| self[attr] } .compact .map(&:to_time) .max end def current_time_from_proper_timezone self.class.default_timezone == :utc ? Time.now.utc : Time.now end # Clear attributes and changed_attributes def clear_timestamp_attributes all_timestamp_attributes_in_model.each do |attribute_name| self[attribute_name] = nil clear_attribute_changes([attribute_name]) end end end end rails-4.2.6/activerecord/lib/active_record/transactions.rb000066400000000000000000000423551266740050600237420ustar00rootroot00000000000000module ActiveRecord # See ActiveRecord::Transactions::ClassMethods for documentation. module Transactions extend ActiveSupport::Concern #:nodoc: ACTIONS = [:create, :destroy, :update] #:nodoc: CALLBACK_WARN_MESSAGE = "Currently, Active Record suppresses errors raised " \ "within `after_rollback`/`after_commit` callbacks and only print them to " \ "the logs. In the next version, these errors will no longer be suppressed. " \ "Instead, the errors will propagate normally just like in other Active " \ "Record callbacks.\n" \ "\n" \ "You can opt into the new behavior and remove this warning by setting:\n" \ "\n" \ " config.active_record.raise_in_transactional_callbacks = true\n\n" included do define_callbacks :commit, :rollback, terminator: ->(_, result) { result == false }, scope: [:kind, :name] mattr_accessor :raise_in_transactional_callbacks, instance_writer: false self.raise_in_transactional_callbacks = false end # = Active Record Transactions # # Transactions are protective blocks where SQL statements are only permanent # if they can all succeed as one atomic action. The classic example is a # transfer between two accounts where you can only have a deposit if the # withdrawal succeeded and vice versa. Transactions enforce the integrity of # the database and guard the data against program errors or database # break-downs. So basically you should use transaction blocks whenever you # have a number of statements that must be executed together or not at all. # # For example: # # ActiveRecord::Base.transaction do # david.withdrawal(100) # mary.deposit(100) # end # # This example will only take money from David and give it to Mary if neither # +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a # ROLLBACK that returns the database to the state before the transaction # began. Be aware, though, that the objects will _not_ have their instance # data returned to their pre-transactional state. # # == Different Active Record classes in a single transaction # # Though the transaction class method is called on some Active Record class, # the objects within the transaction block need not all be instances of # that class. This is because transactions are per-database connection, not # per-model. # # In this example a +balance+ record is transactionally saved even # though +transaction+ is called on the +Account+ class: # # Account.transaction do # balance.save! # account.save! # end # # The +transaction+ method is also available as a model instance method. # For example, you can also do this: # # balance.transaction do # balance.save! # account.save! # end # # == Transactions are not distributed across database connections # # A transaction acts on a single database connection. If you have # multiple class-specific databases, the transaction will not protect # interaction among them. One workaround is to begin a transaction # on each class whose models you alter: # # Student.transaction do # Course.transaction do # course.enroll(student) # student.units += course.units # end # end # # This is a poor solution, but fully distributed transactions are beyond # the scope of Active Record. # # == +save+ and +destroy+ are automatically wrapped in a transaction # # Both +save+ and +destroy+ come wrapped in a transaction that ensures # that whatever you do in validations or callbacks will happen under its # protected cover. So you can use validations to check for values that # the transaction depends on or you can raise exceptions in the callbacks # to rollback, including after_* callbacks. # # As a consequence changes to the database are not seen outside your connection # until the operation is complete. For example, if you try to update the index # of a search engine in +after_save+ the indexer won't see the updated record. # The +after_commit+ callback is the only one that is triggered once the update # is committed. See below. # # == Exception handling and rolling back # # Also have in mind that exceptions thrown within a transaction block will # be propagated (after triggering the ROLLBACK), so you should be ready to # catch those in your application code. # # One exception is the ActiveRecord::Rollback exception, which will trigger # a ROLLBACK when raised, but not be re-raised by the transaction block. # # *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions # inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an # error occurred at the database level, for example when a unique constraint # is violated. On some database systems, such as PostgreSQL, database errors # inside a transaction cause the entire transaction to become unusable # until it's restarted from the beginning. Here is an example which # demonstrates the problem: # # # Suppose that we have a Number model with a unique column called 'i'. # Number.transaction do # Number.create(i: 0) # begin # # This will raise a unique constraint error... # Number.create(i: 0) # rescue ActiveRecord::StatementInvalid # # ...which we ignore. # end # # # On PostgreSQL, the transaction is now unusable. The following # # statement will cause a PostgreSQL error, even though the unique # # constraint is no longer violated: # Number.create(i: 1) # # => "PGError: ERROR: current transaction is aborted, commands # # ignored until end of transaction block" # end # # One should restart the entire transaction if an # ActiveRecord::StatementInvalid occurred. # # == Nested transactions # # +transaction+ calls can be nested. By default, this makes all database # statements in the nested transaction block become part of the parent # transaction. For example, the following behavior may be surprising: # # User.transaction do # User.create(username: 'Kotori') # User.transaction do # User.create(username: 'Nemu') # raise ActiveRecord::Rollback # end # end # # creates both "Kotori" and "Nemu". Reason is the ActiveRecord::Rollback # exception in the nested block does not issue a ROLLBACK. Since these exceptions # are captured in transaction blocks, the parent block does not see it and the # real transaction is committed. # # In order to get a ROLLBACK for the nested transaction you may ask for a real # sub-transaction by passing requires_new: true. If anything goes wrong, # the database rolls back to the beginning of the sub-transaction without rolling # back the parent transaction. If we add it to the previous example: # # User.transaction do # User.create(username: 'Kotori') # User.transaction(requires_new: true) do # User.create(username: 'Nemu') # raise ActiveRecord::Rollback # end # end # # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it. # # Most databases don't support true nested transactions. At the time of # writing, the only database that we're aware of that supports true nested # transactions, is MS-SQL. Because of this, Active Record emulates nested # transactions by using savepoints on MySQL and PostgreSQL. See # http://dev.mysql.com/doc/refman/5.6/en/savepoint.html # for more information about savepoints. # # === Callbacks # # There are two types of callbacks associated with committing and rolling back transactions: # +after_commit+ and +after_rollback+. # # +after_commit+ callbacks are called on every record saved or destroyed within a # transaction immediately after the transaction is committed. +after_rollback+ callbacks # are called on every record saved or destroyed within a transaction immediately after the # transaction or savepoint is rolled back. # # These callbacks are useful for interacting with other systems since you will be guaranteed # that the callback is only executed when the database is in a permanent state. For example, # +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from # within a transaction could trigger the cache to be regenerated before the database is updated. # # === Caveats # # If you're on MySQL, then do not use DDL operations in nested transactions # blocks that are emulated with savepoints. That is, do not execute statements # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically # releases all savepoints upon executing a DDL operation. When +transaction+ # is finished and tries to release the savepoint it created earlier, a # database error will occur because the savepoint has already been # automatically released. The following example demonstrates the problem: # # Model.connection.transaction do # BEGIN # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 # Model.connection.create_table(...) # active_record_1 now automatically released # end # RELEASE savepoint active_record_1 # # ^^^^ BOOM! database error! # end # # Note that "TRUNCATE" is also a MySQL DDL statement! module ClassMethods # See ActiveRecord::Transactions::ClassMethods for detailed documentation. def transaction(options = {}, &block) # See the ConnectionAdapters::DatabaseStatements#transaction API docs. connection.transaction(options, &block) end # This callback is called after a record has been created, updated, or destroyed. # # You can specify that the callback should only be fired by a certain action with # the +:on+ option: # # after_commit :do_foo, on: :create # after_commit :do_bar, on: :update # after_commit :do_baz, on: :destroy # # after_commit :do_foo_bar, on: [:create, :update] # after_commit :do_bar_baz, on: [:update, :destroy] # # Note that transactional fixtures do not play well with this feature. Please # use the +test_after_commit+ gem to have these hooks fired in tests. def after_commit(*args, &block) set_options_for_callbacks!(args) set_callback(:commit, :after, *args, &block) unless ActiveRecord::Base.raise_in_transactional_callbacks ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE) end end # This callback is called after a create, update, or destroy are rolled back. # # Please check the documentation of +after_commit+ for options. def after_rollback(*args, &block) set_options_for_callbacks!(args) set_callback(:rollback, :after, *args, &block) unless ActiveRecord::Base.raise_in_transactional_callbacks ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE) end end private def set_options_for_callbacks!(args) options = args.last if options.is_a?(Hash) && options[:on] fire_on = Array(options[:on]) assert_valid_transaction_action(fire_on) options[:if] = Array(options[:if]) options[:if] << "transaction_include_any_action?(#{fire_on})" end end def assert_valid_transaction_action(actions) if (actions - ACTIONS).any? raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}" end end end # See ActiveRecord::Transactions::ClassMethods for detailed documentation. def transaction(options = {}, &block) self.class.transaction(options, &block) end def destroy #:nodoc: with_transaction_returning_status { super } end def save(*) #:nodoc: rollback_active_record_state! do with_transaction_returning_status { super } end end def save!(*) #:nodoc: with_transaction_returning_status { super } end def touch(*) #:nodoc: with_transaction_returning_status { super } end # Reset id and @new_record if the transaction rolls back. def rollback_active_record_state! remember_transaction_record_state yield rescue Exception restore_transaction_record_state raise ensure clear_transaction_record_state end # Call the +after_commit+ callbacks. # # Ensure that it is not called if the object was never persisted (failed create), # but call it after the commit of a destroyed object. def committed!(should_run_callbacks = true) #:nodoc: _run_commit_callbacks if should_run_callbacks && destroyed? || persisted? ensure force_clear_transaction_record_state end # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record # state should be rolled back to the beginning or just to the last savepoint. def rolledback!(force_restore_state = false, should_run_callbacks = true) #:nodoc: _run_rollback_callbacks if should_run_callbacks ensure restore_transaction_record_state(force_restore_state) clear_transaction_record_state end # Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks # can be called. def add_to_transaction if has_transactional_callbacks? self.class.connection.add_transaction_record(self) else sync_with_transaction_state set_transaction_state(self.class.connection.transaction_state) end remember_transaction_record_state end # Executes +method+ within a transaction and captures its return value as a # status flag. If the status is true the transaction is committed, otherwise # a ROLLBACK is issued. In any case the status flag is returned. # # This method is available within the context of an ActiveRecord::Base # instance. def with_transaction_returning_status status = nil self.class.transaction do add_to_transaction begin status = yield rescue ActiveRecord::Rollback clear_transaction_record_state status = nil end raise ActiveRecord::Rollback unless status end status ensure if @transaction_state && @transaction_state.committed? clear_transaction_record_state end end protected # Save the new record state and id of a record so it can be restored later if a transaction fails. def remember_transaction_record_state #:nodoc: @_start_transaction_state[:id] = id @_start_transaction_state.reverse_merge!( new_record: @new_record, destroyed: @destroyed, frozen?: frozen?, ) @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 end # Clear the new record state and id of a record. def clear_transaction_record_state #:nodoc: @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 end # Force to clear the transaction record state. def force_clear_transaction_record_state #:nodoc: @_start_transaction_state.clear end # Restore the new record state and id of a record that was previously saved by a call to save_record_state. def restore_transaction_record_state(force = false) #:nodoc: unless @_start_transaction_state.empty? transaction_level = (@_start_transaction_state[:level] || 0) - 1 if transaction_level < 1 || force restore_state = @_start_transaction_state thaw @new_record = restore_state[:new_record] @destroyed = restore_state[:destroyed] pk = self.class.primary_key if pk && read_attribute(pk) != restore_state[:id] write_attribute(pk, restore_state[:id]) end freeze if restore_state[:frozen?] end end end # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed. def transaction_record_state(state) #:nodoc: @_start_transaction_state[state] end # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. def transaction_include_any_action?(actions) #:nodoc: actions.any? do |action| case action when :create transaction_record_state(:new_record) when :destroy destroyed? when :update !(transaction_record_state(:new_record) || destroyed?) end end end end end rails-4.2.6/activerecord/lib/active_record/translation.rb000066400000000000000000000007511266740050600235620ustar00rootroot00000000000000module ActiveRecord module Translation include ActiveModel::Translation # Set the lookup ancestors for ActiveModel. def lookup_ancestors #:nodoc: klass = self classes = [klass] return classes if klass == ActiveRecord::Base while klass != klass.base_class classes << klass = klass.superclass end classes end # Set the i18n scope to overwrite ActiveModel. def i18n_scope #:nodoc: :activerecord end end end rails-4.2.6/activerecord/lib/active_record/type.rb000066400000000000000000000014571266740050600222110ustar00rootroot00000000000000require 'active_record/type/decorator' require 'active_record/type/mutable' require 'active_record/type/numeric' require 'active_record/type/time_value' require 'active_record/type/value' require 'active_record/type/big_integer' require 'active_record/type/binary' require 'active_record/type/boolean' require 'active_record/type/date' require 'active_record/type/date_time' require 'active_record/type/decimal' require 'active_record/type/decimal_without_scale' require 'active_record/type/float' require 'active_record/type/integer' require 'active_record/type/serialized' require 'active_record/type/string' require 'active_record/type/text' require 'active_record/type/time' require 'active_record/type/unsigned_integer' require 'active_record/type/type_map' require 'active_record/type/hash_lookup_type_map' rails-4.2.6/activerecord/lib/active_record/type/000077500000000000000000000000001266740050600216555ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/type/big_integer.rb000066400000000000000000000003121266740050600244540ustar00rootroot00000000000000require 'active_record/type/integer' module ActiveRecord module Type class BigInteger < Integer # :nodoc: private def max_value ::Float::INFINITY end end end end rails-4.2.6/activerecord/lib/active_record/type/binary.rb000066400000000000000000000015511266740050600234700ustar00rootroot00000000000000module ActiveRecord module Type class Binary < Value # :nodoc: def type :binary end def binary? true end def type_cast(value) if value.is_a?(Data) value.to_s else super end end def type_cast_for_database(value) return if value.nil? Data.new(super) end def changed_in_place?(raw_old_value, value) old_value = type_cast_from_database(raw_old_value) old_value != value end class Data # :nodoc: def initialize(value) @value = value.to_s end def to_s @value end alias_method :to_str, :to_s def hex @value.unpack('H*')[0] end def ==(other) other == to_s || super end end end end end rails-4.2.6/activerecord/lib/active_record/type/boolean.rb000066400000000000000000000016641266740050600236300ustar00rootroot00000000000000module ActiveRecord module Type class Boolean < Value # :nodoc: def type :boolean end private def cast_value(value) if value == '' nil elsif ConnectionAdapters::Column::TRUE_VALUES.include?(value) true else if !ConnectionAdapters::Column::FALSE_VALUES.include?(value) ActiveSupport::Deprecation.warn(<<-MSG.squish) You attempted to assign a value which is not explicitly `true` or `false` (#{value.inspect}) to a boolean column. Currently this value casts to `false`. This will change to match Ruby's semantics, and will cast to `true` in Rails 5. If you would like to maintain the current behavior, you should explicitly handle the values you would like cast to `false`. MSG end false end end end end end rails-4.2.6/activerecord/lib/active_record/type/date.rb000066400000000000000000000017301266740050600231200ustar00rootroot00000000000000module ActiveRecord module Type class Date < Value # :nodoc: def type :date end def klass ::Date end def type_cast_for_schema(value) "'#{value.to_s(:db)}'" end private def cast_value(value) if value.is_a?(::String) return if value.empty? fast_string_to_date(value) || fallback_string_to_date(value) elsif value.respond_to?(:to_date) value.to_date else value end end def fast_string_to_date(string) if string =~ ConnectionAdapters::Column::Format::ISO_DATE new_date $1.to_i, $2.to_i, $3.to_i end end def fallback_string_to_date(string) new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) end def new_date(year, mon, mday) if year && year != 0 ::Date.new(year, mon, mday) rescue nil end end end end end rails-4.2.6/activerecord/lib/active_record/type/date_time.rb000066400000000000000000000026241266740050600241410ustar00rootroot00000000000000module ActiveRecord module Type class DateTime < Value # :nodoc: include TimeValue def type :datetime end def type_cast_for_database(value) return super unless value.acts_like?(:time) zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal if value.respond_to?(zone_conversion_method) value = value.send(zone_conversion_method) end return value unless has_precision? result = value.to_s(:db) if value.respond_to?(:usec) && (1..6).cover?(precision) "#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}" else result end end private alias has_precision? precision def cast_value(string) return string unless string.is_a?(::String) return if string.empty? fast_string_to_time(string) || fallback_string_to_time(string) end # '0.123456' -> 123456 # '1.123456' -> 123456 def microseconds(time) time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 end def fallback_string_to_time(string) time_hash = ::Date._parse(string) time_hash[:sec_fraction] = microseconds(time_hash) new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) end end end end rails-4.2.6/activerecord/lib/active_record/type/decimal.rb000066400000000000000000000017351266740050600236060ustar00rootroot00000000000000module ActiveRecord module Type class Decimal < Value # :nodoc: include Numeric def type :decimal end def type_cast_for_schema(value) value.to_s end private def cast_value(value) casted_value = case value when ::Float convert_float_to_big_decimal(value) when ::Numeric, ::String BigDecimal(value, precision.to_i) else if value.respond_to?(:to_d) value.to_d else cast_value(value.to_s) end end scale ? casted_value.round(scale) : casted_value end def convert_float_to_big_decimal(value) if precision BigDecimal(value, float_precision) else value.to_d end end def float_precision if precision.to_i > ::Float::DIG + 1 ::Float::DIG + 1 else precision.to_i end end end end end rails-4.2.6/activerecord/lib/active_record/type/decimal_without_scale.rb000066400000000000000000000002751266740050600265360ustar00rootroot00000000000000require 'active_record/type/big_integer' module ActiveRecord module Type class DecimalWithoutScale < BigInteger # :nodoc: def type :decimal end end end end rails-4.2.6/activerecord/lib/active_record/type/decorator.rb000066400000000000000000000004071266740050600241650ustar00rootroot00000000000000module ActiveRecord module Type module Decorator # :nodoc: def init_with(coder) @subtype = coder['subtype'] __setobj__(@subtype) end def encode_with(coder) coder['subtype'] = __getobj__ end end end end rails-4.2.6/activerecord/lib/active_record/type/float.rb000066400000000000000000000004141266740050600233060ustar00rootroot00000000000000module ActiveRecord module Type class Float < Value # :nodoc: include Numeric def type :float end alias type_cast_for_database type_cast private def cast_value(value) value.to_f end end end end rails-4.2.6/activerecord/lib/active_record/type/hash_lookup_type_map.rb000066400000000000000000000007011266740050600264120ustar00rootroot00000000000000module ActiveRecord module Type class HashLookupTypeMap < TypeMap # :nodoc: def alias_type(type, alias_type) register_type(type) { |_, *args| lookup(alias_type, *args) } end def key?(key) @mapping.key?(key) end def keys @mapping.keys end private def perform_fetch(type, *args, &block) @mapping.fetch(type, block).call(type, *args) end end end end rails-4.2.6/activerecord/lib/active_record/type/integer.rb000066400000000000000000000021141266740050600236350ustar00rootroot00000000000000module ActiveRecord module Type class Integer < Value # :nodoc: include Numeric def initialize(*) super @range = min_value...max_value end def type :integer end def type_cast_from_database(value) return if value.nil? value.to_i end def type_cast_for_database(value) result = type_cast(value) if result ensure_in_range(result) end result end protected attr_reader :range private def cast_value(value) case value when true then 1 when false then 0 else value.to_i rescue nil end end def ensure_in_range(value) unless range.cover?(value) raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || 4}" end end def max_value limit = self.limit || 4 1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign end def min_value -max_value end end end end rails-4.2.6/activerecord/lib/active_record/type/mutable.rb000066400000000000000000000007351266740050600236400ustar00rootroot00000000000000module ActiveRecord module Type module Mutable # :nodoc: def type_cast_from_user(value) type_cast_from_database(type_cast_for_database(value)) end # +raw_old_value+ will be the `_before_type_cast` version of the # value (likely a string). +new_value+ will be the current, type # cast value. def changed_in_place?(raw_old_value, new_value) raw_old_value != type_cast_for_database(new_value) end end end end rails-4.2.6/activerecord/lib/active_record/type/numeric.rb000066400000000000000000000016561266740050600236540ustar00rootroot00000000000000module ActiveRecord module Type module Numeric # :nodoc: def number? true end def type_cast(value) value = case value when true then 1 when false then 0 when ::String then value.presence else value end super(value) end def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc: super || number_to_non_number?(old_value, new_value_before_type_cast) end private def number_to_non_number?(old_value, new_value_before_type_cast) old_value != nil && non_numeric_string?(new_value_before_type_cast) end def non_numeric_string?(value) # 'wibble'.to_i will give zero, we want to make sure # that we aren't marking int zero to string zero as # changed. value.to_s !~ /\A-?\d+\.?\d*\z/ end end end end rails-4.2.6/activerecord/lib/active_record/type/serialized.rb000066400000000000000000000024621266740050600243410ustar00rootroot00000000000000module ActiveRecord module Type class Serialized < DelegateClass(Type::Value) # :nodoc: include Mutable include Decorator attr_reader :subtype, :coder def initialize(subtype, coder) @subtype = subtype @coder = coder super(subtype) end def type_cast_from_database(value) if default_value?(value) value else coder.load(super) end end def type_cast_for_database(value) return if value.nil? unless default_value?(value) super coder.dump(value) end end def inspect Kernel.instance_method(:inspect).bind(self).call end def changed_in_place?(raw_old_value, value) return false if value.nil? raw_new_value = type_cast_for_database(value) raw_old_value.nil? != raw_new_value.nil? || subtype.changed_in_place?(raw_old_value, raw_new_value) end def accessor ActiveRecord::Store::IndifferentHashAccessor end def init_with(coder) @coder = coder['coder'] super end def encode_with(coder) coder['coder'] = @coder super end private def default_value?(value) value == coder.load(nil) end end end end rails-4.2.6/activerecord/lib/active_record/type/string.rb000066400000000000000000000014551266740050600235150ustar00rootroot00000000000000module ActiveRecord module Type class String < Value # :nodoc: def type :string end def changed_in_place?(raw_old_value, new_value) if new_value.is_a?(::String) raw_old_value != new_value end end def type_cast_for_database(value) case value when ::Numeric, ActiveSupport::Duration then value.to_s when ::String then ::String.new(value) when true then "t" when false then "f" else super end end def text? true end private def cast_value(value) case value when true then "t" when false then "f" # String.new is slightly faster than dup else ::String.new(value.to_s) end end end end end rails-4.2.6/activerecord/lib/active_record/type/text.rb000066400000000000000000000002421266740050600231640ustar00rootroot00000000000000require 'active_record/type/string' module ActiveRecord module Type class Text < String # :nodoc: def type :text end end end end rails-4.2.6/activerecord/lib/active_record/type/time.rb000066400000000000000000000011211266740050600231330ustar00rootroot00000000000000module ActiveRecord module Type class Time < Value # :nodoc: include TimeValue def type :time end private def cast_value(value) return value unless value.is_a?(::String) return if value.empty? dummy_time_value = "2000-01-01 #{value}" fast_string_to_time(dummy_time_value) || begin time_hash = ::Date._parse(dummy_time_value) return if time_hash[:hour].nil? new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) end end end end end rails-4.2.6/activerecord/lib/active_record/type/time_value.rb000066400000000000000000000020501266740050600243310ustar00rootroot00000000000000module ActiveRecord module Type module TimeValue # :nodoc: def klass ::Time end def type_cast_for_schema(value) "'#{value.to_s(:db)}'" end private def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) # Treat 0000-00-00 00:00:00 as nil. return if year.nil? || (year == 0 && mon == 0 && mday == 0) if offset time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil return unless time time -= offset Base.default_timezone == :utc ? time : time.getlocal else ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil end end # Doesn't handle time zones. def fast_string_to_time(string) if string =~ ConnectionAdapters::Column::Format::ISO_DATETIME microsec = ($7.to_r * 1_000_000).to_i new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec end end end end end rails-4.2.6/activerecord/lib/active_record/type/type_map.rb000066400000000000000000000026251266740050600240250ustar00rootroot00000000000000require 'thread_safe' module ActiveRecord module Type class TypeMap # :nodoc: def initialize @mapping = {} @cache = ThreadSafe::Cache.new do |h, key| h.fetch_or_store(key, ThreadSafe::Cache.new) end end def lookup(lookup_key, *args) fetch(lookup_key, *args) { default_value } end def fetch(lookup_key, *args, &block) @cache[lookup_key].fetch_or_store(args) do perform_fetch(lookup_key, *args, &block) end end def register_type(key, value = nil, &block) raise ::ArgumentError unless value || block @cache.clear if block @mapping[key] = block else @mapping[key] = proc { value } end end def alias_type(key, target_key) register_type(key) do |sql_type, *args| metadata = sql_type[/\(.*\)/, 0] lookup("#{target_key}#{metadata}", *args) end end def clear @mapping.clear end private def perform_fetch(lookup_key, *args) matching_pair = @mapping.reverse_each.detect do |key, _| key === lookup_key end if matching_pair matching_pair.last.call(lookup_key, *args) else yield lookup_key, *args end end def default_value @default_value ||= Value.new end end end end rails-4.2.6/activerecord/lib/active_record/type/unsigned_integer.rb000066400000000000000000000003121266740050600255270ustar00rootroot00000000000000module ActiveRecord module Type class UnsignedInteger < Integer # :nodoc: private def max_value super * 2 end def min_value 0 end end end end rails-4.2.6/activerecord/lib/active_record/type/value.rb000066400000000000000000000064361266740050600233270ustar00rootroot00000000000000module ActiveRecord module Type class Value # :nodoc: attr_reader :precision, :scale, :limit # Valid options are +precision+, +scale+, and +limit+. They are only # used when dumping schema. def initialize(options = {}) options.assert_valid_keys(:precision, :scale, :limit) @precision = options[:precision] @scale = options[:scale] @limit = options[:limit] end # The simplified type that this object represents. Returns a symbol such # as +:string+ or +:integer+ def type; end # Type casts a string from the database into the appropriate ruby type. # Classes which do not need separate type casting behavior for database # and user provided values should override +cast_value+ instead. def type_cast_from_database(value) type_cast(value) end # Type casts a value from user input (e.g. from a setter). This value may # be a string from the form builder, or an already type cast value # provided manually to a setter. # # Classes which do not need separate type casting behavior for database # and user provided values should override +type_cast+ or +cast_value+ # instead. def type_cast_from_user(value) type_cast(value) end # Cast a value from the ruby type to a type that the database knows how # to understand. The returned value from this method should be a # +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or # +nil+ def type_cast_for_database(value) value end # Type cast a value for schema dumping. This method is private, as we are # hoping to remove it entirely. def type_cast_for_schema(value) # :nodoc: value.inspect end # These predicates are not documented, as I need to look further into # their use, and see if they can be removed entirely. def text? # :nodoc: false end def number? # :nodoc: false end def binary? # :nodoc: false end def klass # :nodoc: end # Determines whether a value has changed for dirty checking. +old_value+ # and +new_value+ will always be type-cast. Types should not need to # override this method. def changed?(old_value, new_value, _new_value_before_type_cast) old_value != new_value end # Determines whether the mutable value has been modified since it was # read. Returns +false+ by default. This method should not be overridden # directly. Types which return a mutable value should include # +Type::Mutable+, which will define this method. def changed_in_place?(*) false end def ==(other) self.class == other.class && precision == other.precision && scale == other.scale && limit == other.limit end private def type_cast(value) cast_value(value) unless value.nil? end # Convenience method for types which do not need separate type casting # behavior for user and database inputs. Called by # `type_cast_from_database` and `type_cast_from_user` for all values # except `nil`. def cast_value(value) # :doc: value end end end end rails-4.2.6/activerecord/lib/active_record/validations.rb000066400000000000000000000065131266740050600235430ustar00rootroot00000000000000module ActiveRecord # = Active Record RecordInvalid # # Raised by save! and create! when the record is invalid. Use the # +record+ method to retrieve the record which did not validate. # # begin # complex_operation_that_internally_calls_save! # rescue ActiveRecord::RecordInvalid => invalid # puts invalid.record.errors # end class RecordInvalid < ActiveRecordError attr_reader :record def initialize(record) @record = record errors = @record.errors.full_messages.join(", ") super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid")) end end # = Active Record Validations # # Active Record includes the majority of its validations from ActiveModel::Validations # all of which accept the :on argument to define the context where the # validations are active. Active Record will always supply either the context of # :create or :update dependent on whether the model is a # new_record?. module Validations extend ActiveSupport::Concern include ActiveModel::Validations # The validation process on save can be skipped by passing validate: false. # The regular Base#save method is replaced with this when the validations # module is mixed in, which it is by default. def save(options={}) perform_validations(options) ? super : false end # Attempts to save the record just like Base#save but will raise a +RecordInvalid+ # exception instead of returning +false+ if the record is not valid. def save!(options={}) perform_validations(options) ? super : raise_record_invalid end # Runs all the validations within the specified context. Returns +true+ if # no errors are found, +false+ otherwise. # # Aliased as validate. # # If the argument is +false+ (default is +nil+), the context is set to :create if # new_record? is +true+, and to :update if it is not. # # Validations with no :on option will run no matter the context. Validations with # some :on option will only run in the specified context. def valid?(context = nil) context ||= (new_record? ? :create : :update) output = super(context) errors.empty? && output end alias_method :validate, :valid? # Runs all the validations within the specified context. Returns +true+ if # no errors are found, raises +RecordInvalid+ otherwise. # # If the argument is +false+ (default is +nil+), the context is set to :create if # new_record? is +true+, and to :update if it is not. # # Validations with no :on option will run no matter the context. Validations with # some :on option will only run in the specified context. def validate!(context = nil) valid?(context) || raise_record_invalid end protected def raise_record_invalid raise(RecordInvalid.new(self)) end def perform_validations(options={}) # :nodoc: options[:validate] == false || valid?(options[:context]) end end end require "active_record/validations/associated" require "active_record/validations/uniqueness" require "active_record/validations/presence" rails-4.2.6/activerecord/lib/active_record/validations/000077500000000000000000000000001266740050600232115ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/active_record/validations/associated.rb000066400000000000000000000045141266740050600256610ustar00rootroot00000000000000module ActiveRecord module Validations class AssociatedValidator < ActiveModel::EachValidator #:nodoc: def validate_each(record, attribute, value) if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any? record.errors.add(attribute, :invalid, options.merge(:value => value)) end end end module ClassMethods # Validates whether the associated object or objects are all valid. # Works with any kind of association. # # class Book < ActiveRecord::Base # has_many :pages # belongs_to :library # # validates_associated :pages, :library # end # # WARNING: This validation must not be used on both ends of an association. # Doing so will lead to a circular dependency and cause infinite recursion. # # NOTE: This validation will not fail if the association hasn't been # assigned. If you want to ensure that the association is both present and # guaranteed to be valid, you also need to use +validates_presence_of+. # # Configuration options: # # * :message - A custom error message (default is: "is invalid"). # * :on - Specifies the contexts where this validation is active. # Runs in all validation contexts by default (nil). You can pass a symbol # or an array of symbols. (e.g. on: :create or # on: :custom_validation_context or # on: [:create, :custom_validation_context]) # * :if - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. if: :allow_validation, # or if: Proc.new { |user| user.signup_step > 2 }). The method, # proc or string should return or evaluate to a +true+ or +false+ value. # * :unless - Specifies a method, proc or string to call to # determine if the validation should not occur (e.g. unless: :skip_validation, # or unless: Proc.new { |user| user.signup_step <= 2 }). The # method, proc or string should return or evaluate to a +true+ or +false+ # value. def validates_associated(*attr_names) validates_with AssociatedValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activerecord/lib/active_record/validations/presence.rb000066400000000000000000000063241266740050600253470ustar00rootroot00000000000000module ActiveRecord module Validations class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc: def validate(record) super attributes.each do |attribute| next unless record.class._reflect_on_association(attribute) associated_records = Array.wrap(record.send(attribute)) # Superclass validates presence. Ensure present records aren't about to be destroyed. if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? } record.errors.add(attribute, :blank, options) end end end end module ClassMethods # Validates that the specified attributes are not blank (as defined by # Object#blank?), and, if the attribute is an association, that the # associated object is not marked for destruction. Happens by default # on save. # # class Person < ActiveRecord::Base # has_one :face # validates_presence_of :face # end # # The face attribute must be in the object and it cannot be blank or marked # for destruction. # # If you want to validate the presence of a boolean field (where the real values # are true and false), you will want to use # validates_inclusion_of :field_name, in: [true, false]. # # This is due to the way Object#blank? handles boolean values: # false.blank? # => true. # # This validator defers to the ActiveModel validation for presence, adding the # check to see that an associated object is not marked for destruction. This # prevents the parent object from validating successfully and saving, which then # deletes the associated object, thus putting the parent object into an invalid # state. # # Configuration options: # * :message - A custom error message (default is: "can't be blank"). # * :on - Specifies the contexts where this validation is active. # Runs in all validation contexts by default (nil). You can pass a symbol # or an array of symbols. (e.g. on: :create or # on: :custom_validation_context or # on: [:create, :custom_validation_context]) # * :if - Specifies a method, proc or string to call to determine if # the validation should occur (e.g. if: :allow_validation, or # if: Proc.new { |user| user.signup_step > 2 }). The method, proc # or string should return or evaluate to a +true+ or +false+ value. # * :unless - Specifies a method, proc or string to call to determine # if the validation should not occur (e.g. unless: :skip_validation, # or unless: Proc.new { |user| user.signup_step <= 2 }). The method, # proc or string should return or evaluate to a +true+ or +false+ value. # * :strict - Specifies whether validation should be strict. # See ActiveModel::Validation#validates! for more information. def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activerecord/lib/active_record/validations/uniqueness.rb000066400000000000000000000260031266740050600257360ustar00rootroot00000000000000module ActiveRecord module Validations class UniquenessValidator < ActiveModel::EachValidator # :nodoc: def initialize(options) if options[:conditions] && !options[:conditions].respond_to?(:call) raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \ "Pass a callable instead: `conditions: -> { where(approved: true) }`" end super({ case_sensitive: true }.merge!(options)) @klass = options[:class] end def validate_each(record, attribute, value) finder_class = find_finder_class_for(record) table = finder_class.arel_table value = map_enum_attribute(finder_class, attribute, value) begin relation = build_relation(finder_class, table, attribute, value) if record.persisted? if finder_class.primary_key relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) else raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.") end end relation = scope_relation(record, table, relation) relation = finder_class.unscoped.where(relation) relation = relation.merge(options[:conditions]) if options[:conditions] rescue RangeError relation = finder_class.none end if relation.exists? error_options = options.except(:case_sensitive, :scope, :conditions) error_options[:value] = value record.errors.add(attribute, :taken, error_options) end end protected # The check for an existing value should be run from a class that # isn't abstract. This means working down from the current class # (self), to the first non-abstract class. Since classes don't know # their subclasses, we have to build the hierarchy between self and # the record's class. def find_finder_class_for(record) #:nodoc: class_hierarchy = [record.class] while class_hierarchy.first != @klass class_hierarchy.unshift(class_hierarchy.first.superclass) end class_hierarchy.detect { |klass| !klass.abstract_class? } end def build_relation(klass, table, attribute, value) #:nodoc: if reflection = klass._reflect_on_association(attribute) attribute = reflection.foreign_key value = value.attributes[reflection.klass.primary_key] unless value.nil? end attribute_name = attribute.to_s # the attribute may be an aliased attribute if klass.attribute_aliases[attribute_name] attribute = klass.attribute_aliases[attribute_name] attribute_name = attribute.to_s end column = klass.columns_hash[attribute_name] value = klass.connection.type_cast(value, column) if value.is_a?(String) && column.limit value = value.to_s[0, column.limit] end if !options[:case_sensitive] && value && column.text? # will use SQL LOWER function before comparison, unless it detects a case insensitive collation klass.connection.case_insensitive_comparison(table, attribute, column, value) else klass.connection.case_sensitive_comparison(table, attribute, column, value) end end def scope_relation(record, table, relation) Array(options[:scope]).each do |scope_item| if reflection = record.class._reflect_on_association(scope_item) scope_value = record.send(reflection.foreign_key) scope_item = reflection.foreign_key else scope_value = record._read_attribute(scope_item) end relation = relation.and(table[scope_item].eq(scope_value)) end relation end def map_enum_attribute(klass, attribute, value) mapping = klass.defined_enums[attribute.to_s] value = mapping[value] if value && mapping value end end module ClassMethods # Validates whether the value of the specified attributes are unique # across the system. Useful for making sure that only one user # can be named "davidhh". # # class Person < ActiveRecord::Base # validates_uniqueness_of :user_name # end # # It can also validate whether the value of the specified attributes are # unique based on a :scope parameter: # # class Person < ActiveRecord::Base # validates_uniqueness_of :user_name, scope: :account_id # end # # Or even multiple scope parameters. For example, making sure that a # teacher can only be on the schedule once per semester for a particular # class. # # class TeacherSchedule < ActiveRecord::Base # validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id] # end # # It is also possible to limit the uniqueness constraint to a set of # records matching certain conditions. In this example archived articles # are not being taken into consideration when validating uniqueness # of the title attribute: # # class Article < ActiveRecord::Base # validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') } # end # # When the record is created, a check is performed to make sure that no # record exists in the database with the given value for the specified # attribute (that maps to a column). When the record is updated, # the same check is made but disregarding the record itself. # # Configuration options: # # * :message - Specifies a custom error message (default is: # "has already been taken"). # * :scope - One or more columns by which to limit the scope of # the uniqueness constraint. # * :conditions - Specify the conditions to be included as a # WHERE SQL fragment to limit the uniqueness constraint lookup # (e.g. conditions: -> { where(status: 'active') }). # * :case_sensitive - Looks for an exact match. Ignored by # non-text columns (+true+ by default). # * :allow_nil - If set to +true+, skips this validation if the # attribute is +nil+ (default is +false+). # * :allow_blank - If set to +true+, skips this validation if the # attribute is blank (default is +false+). # * :if - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. if: :allow_validation, # or if: Proc.new { |user| user.signup_step > 2 }). The method, # proc or string should return or evaluate to a +true+ or +false+ value. # * :unless - Specifies a method, proc or string to call to # determine if the validation should not occur (e.g. unless: :skip_validation, # or unless: Proc.new { |user| user.signup_step <= 2 }). The # method, proc or string should return or evaluate to a +true+ or +false+ # value. # # === Concurrency and integrity # # Using this validation method in conjunction with ActiveRecord::Base#save # does not guarantee the absence of duplicate record insertions, because # uniqueness checks on the application level are inherently prone to race # conditions. For example, suppose that two users try to post a Comment at # the same time, and a Comment's title must be unique. At the database-level, # the actions performed by these users could be interleaved in the following manner: # # User 1 | User 2 # ------------------------------------+-------------------------------------- # # User 1 checks whether there's | # # already a comment with the title | # # 'My Post'. This is not the case. | # SELECT * FROM comments | # WHERE title = 'My Post' | # | # | # User 2 does the same thing and also # | # infers that their title is unique. # | SELECT * FROM comments # | WHERE title = 'My Post' # | # # User 1 inserts their comment. | # INSERT INTO comments | # (title, content) VALUES | # ('My Post', 'hi!') | # | # | # User 2 does the same thing. # | INSERT INTO comments # | (title, content) VALUES # | ('My Post', 'hello!') # | # | # ^^^^^^ # | # Boom! We now have a duplicate # | # title! # # This could even happen if you use transactions with the 'serializable' # isolation level. The best way to work around this problem is to add a unique # index to the database table using # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the # rare case that a race condition occurs, the database will guarantee # the field's uniqueness. # # When the database catches such a duplicate insertion, # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid # exception. You can either choose to let this error propagate (which # will result in the default Rails exception page being shown), or you # can catch it and restart the transaction (e.g. by telling the user # that the title already exists, and asking them to re-enter the title). # This technique is also known as # {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control]. # # The bundled ActiveRecord::ConnectionAdapters distinguish unique index # constraint errors from other types of database errors by throwing an # ActiveRecord::RecordNotUnique exception. For other adapters you will # have to parse the (database-specific) exception message to detect such # a case. # # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception: # # * ActiveRecord::ConnectionAdapters::MysqlAdapter. # * ActiveRecord::ConnectionAdapters::Mysql2Adapter. # * ActiveRecord::ConnectionAdapters::SQLite3Adapter. # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter. def validates_uniqueness_of(*attr_names) validates_with UniquenessValidator, _merge_attributes(attr_names) end end end end rails-4.2.6/activerecord/lib/active_record/version.rb000066400000000000000000000002711266740050600227060ustar00rootroot00000000000000require_relative 'gem_version' module ActiveRecord # Returns the version of the currently loaded ActiveRecord as a Gem::Version def self.version gem_version end end rails-4.2.6/activerecord/lib/rails/000077500000000000000000000000001266740050600171755ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/rails/generators/000077500000000000000000000000001266740050600213465ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/rails/generators/active_record.rb000066400000000000000000000007231266740050600245060ustar00rootroot00000000000000require 'rails/generators/named_base' require 'rails/generators/active_model' require 'rails/generators/active_record/migration' require 'active_record' module ActiveRecord module Generators # :nodoc: class Base < Rails::Generators::NamedBase # :nodoc: include ActiveRecord::Generators::Migration # Set the current directory as base for the inherited generators. def self.base_root File.dirname(__FILE__) end end end end rails-4.2.6/activerecord/lib/rails/generators/active_record/000077500000000000000000000000001266740050600241575ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/rails/generators/active_record/migration.rb000066400000000000000000000010161266740050600264730ustar00rootroot00000000000000require 'rails/generators/migration' module ActiveRecord module Generators # :nodoc: module Migration extend ActiveSupport::Concern include Rails::Generators::Migration module ClassMethods # Implement the required interface for Rails::Generators::Migration. def next_migration_number(dirname) next_migration_number = current_migration_number(dirname) + 1 ActiveRecord::Migration.next_migration_number(next_migration_number) end end end end end rails-4.2.6/activerecord/lib/rails/generators/active_record/migration/000077500000000000000000000000001266740050600261505ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb000066400000000000000000000042711266740050600325400ustar00rootroot00000000000000require 'rails/generators/active_record' module ActiveRecord module Generators # :nodoc: class MigrationGenerator < Base # :nodoc: argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" def create_migration_file set_local_assigns! validate_file_name! migration_template @migration_template, "db/migrate/#{file_name}.rb" end protected attr_reader :migration_action, :join_tables # sets the default migration template that is being used for the generation of the migration # depending on the arguments which would be sent out in the command line, the migration template # and the table name instance variables are setup. def set_local_assigns! @migration_template = "migration.rb" case file_name when /^(add|remove)_.*_(?:to|from)_(.*)/ @migration_action = $1 @table_name = normalize_table_name($2) when /join_table/ if attributes.length == 2 @migration_action = 'join' @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) set_index_names end when /^create_(.+)/ @table_name = normalize_table_name($1) @migration_template = "create_table_migration.rb" end end def set_index_names attributes.each_with_index do |attr, i| attr.index_name = [attr, attributes[i - 1]].map{ |a| index_name_for(a) } end end def index_name_for(attribute) if attribute.foreign_key? attribute.name else attribute.name.singularize.foreign_key end.to_sym end private def attributes_with_index attributes.select { |a| !a.reference? && a.has_index? } end def validate_file_name! unless file_name =~ /^[_a-z0-9]+$/ raise IllegalMigrationNameError.new(file_name) end end def normalize_table_name(_table_name) pluralize_table_names? ? _table_name.pluralize : _table_name.singularize end end end end rails-4.2.6/activerecord/lib/rails/generators/active_record/migration/templates/000077500000000000000000000000001266740050600301465ustar00rootroot00000000000000create_table_migration.rb000066400000000000000000000011561266740050600351020ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/rails/generators/active_record/migration/templatesclass <%= migration_class_name %> < ActiveRecord::Migration def change create_table :<%= table_name %> do |t| <% attributes.each do |attribute| -%> <% if attribute.password_digest? -%> t.string :password_digest<%= attribute.inject_options %> <% else -%> t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> <% end -%> <% end -%> <% if options[:timestamps] %> t.timestamps null: false <% end -%> end <% attributes_with_index.each do |attribute| -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <% end -%> end end rails-4.2.6/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb000066400000000000000000000027121266740050600324660ustar00rootroot00000000000000class <%= migration_class_name %> < ActiveRecord::Migration <%- if migration_action == 'add' -%> def change <% attributes.each do |attribute| -%> <%- if attribute.reference? -%> add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> <%- else -%> add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- if attribute.has_index? -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <%- end -%> <%- end -%> <%- end -%> end <%- elsif migration_action == 'join' -%> def change create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| <%- attributes.each do |attribute| -%> <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> <%- end -%> end end <%- else -%> def change <% attributes.each do |attribute| -%> <%- if migration_action -%> <%- if attribute.reference? -%> remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> <%- else -%> <%- if attribute.has_index? -%> remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <%- end -%> remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- end -%> <%- end -%> <%- end -%> end <%- end -%> end rails-4.2.6/activerecord/lib/rails/generators/active_record/model/000077500000000000000000000000001266740050600252575ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/rails/generators/active_record/model/model_generator.rb000066400000000000000000000033551266740050600307600ustar00rootroot00000000000000require 'rails/generators/active_record' module ActiveRecord module Generators # :nodoc: class ModelGenerator < Base # :nodoc: argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" check_class_collision class_option :migration, :type => :boolean class_option :timestamps, :type => :boolean class_option :parent, :type => :string, :desc => "The parent class for the generated model" class_option :indexes, :type => :boolean, :default => true, :desc => "Add indexes for references and belongs_to columns" # creates the migration file for the model. def create_migration_file return unless options[:migration] && options[:parent].nil? attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false migration_template "../../migration/templates/create_table_migration.rb", "db/migrate/create_#{table_name}.rb" end def create_model_file template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb") end def create_module_file return if regular_class_path.empty? template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke end def attributes_with_index attributes.select { |a| !a.reference? && a.has_index? } end def accessible_attributes attributes.reject(&:reference?) end hook_for :test_framework protected # Used by the migration template to determine the parent name of the model def parent_class_name options[:parent] || "ActiveRecord::Base" end end end end rails-4.2.6/activerecord/lib/rails/generators/active_record/model/templates/000077500000000000000000000000001266740050600272555ustar00rootroot00000000000000rails-4.2.6/activerecord/lib/rails/generators/active_record/model/templates/model.rb000066400000000000000000000006071266740050600307050ustar00rootroot00000000000000<% module_namespacing do -%> class <%= class_name %> < <%= parent_class_name.classify %> <% attributes.select(&:reference?).each do |attribute| -%> belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %><%= ', required: true' if attribute.required? %> <% end -%> <% if attributes.any?(&:password_digest?) -%> has_secure_password <% end -%> end <% end -%> rails-4.2.6/activerecord/lib/rails/generators/active_record/model/templates/module.rb000066400000000000000000000003261266740050600310700ustar00rootroot00000000000000<% module_namespacing do -%> module <%= class_path.map(&:camelize).join('::') %> def self.table_name_prefix '<%= namespaced? ? namespaced_class_path.join('_') : class_path.join('_') %>_' end end <% end -%> rails-4.2.6/activerecord/test/000077500000000000000000000000001266740050600162745ustar00rootroot00000000000000rails-4.2.6/activerecord/test/.gitignore000066400000000000000000000000141266740050600202570ustar00rootroot00000000000000/config.yml rails-4.2.6/activerecord/test/active_record/000077500000000000000000000000001266740050600211055ustar00rootroot00000000000000rails-4.2.6/activerecord/test/active_record/connection_adapters/000077500000000000000000000000001266740050600251275ustar00rootroot00000000000000rails-4.2.6/activerecord/test/active_record/connection_adapters/fake_adapter.rb000066400000000000000000000020101266740050600300530ustar00rootroot00000000000000module ActiveRecord module ConnectionHandling def fake_connection(config) ConnectionAdapters::FakeAdapter.new nil, logger end end module ConnectionAdapters class FakeAdapter < AbstractAdapter attr_accessor :tables, :primary_keys @columns = Hash.new { |h,k| h[k] = [] } class << self attr_reader :columns end def initialize(connection, logger) super @tables = [] @primary_keys = {} @columns = self.class.columns end def primary_key(table) @primary_keys[table] end def merge_column(table_name, name, sql_type = nil, options = {}) @columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new( name.to_s, options[:default], lookup_cast_type(sql_type.to_s), sql_type.to_s, options[:null]) end def columns(table_name) @columns[table_name] end def active? true end end end end rails-4.2.6/activerecord/test/assets/000077500000000000000000000000001266740050600175765ustar00rootroot00000000000000rails-4.2.6/activerecord/test/assets/example.log000066400000000000000000000001111266740050600217250ustar00rootroot00000000000000# Logfile created on Wed Oct 31 16:05:13 +0000 2007 by logger.rb/1.5.2.9 rails-4.2.6/activerecord/test/assets/flowers.jpg000066400000000000000000000133121266740050600217610ustar00rootroot00000000000000ÿØÿàJFIFHHÿÛC    ! ''**''555556666666666ÿÛC &&,$ $,(+&&&+(//,,//666666666666666ÿÀ¯ƒÿÄÿÄ@ !1A"Qa2q‘#BR±ÁÑ3b¡’$Cáð%5Ers‚¢²ñÿÄÿÄ*!1AQ"a2q¡BR‘±ÁÿÚ ?úuJ”(Æ`£'`:“@{t.š¡a >ëá?:M-çYÀ•¡6¥œõoQ+ò4pí#œ»³|ê ‘Ù.àŽ,´’†P3€1ƒŸ èdZüYÈé!Šâ/yÄ)ñ/Š´*AÆ\m:gû“øªSò'œ0Î3óÇÄU’[@€%J”(P  æ™ Mnvýh-ÅÑ»m2÷nñõ è|êu„ð ,¸¢C+dÁ¥Ž3Ûó ùœŠ›ÜªfÕ;Ÿx6êNØÏáùR`Že˜…rÉžëÖÉÃÒ$»Žbïn_™»åÊïŸñUY_áñi’ìJÑ3i €‰ >t懹ÌQ33cñ>äý*”DˆÚ]w ëBbh{mw­„‘±ð'ÈV¤SP  @€%W4Ë eÛ ÿ4Žêå¥nd‡°ò“e$ . 'ßõñ¤0N-7³ÅÒŒˆd ÄvCá}¾ àWfS¨Áó EW_C¯ºß‡àÕ_Ãk{¨’â)Bæåäž™ÁÁøiÚœ2/ä<—ÞÎïo™PJQÕ‘FÙñƒ¿­[v$[ñØÒ(ŒO:H 3¾¶ÜöôøUY#k ÞgÝIýAÐþoùÕÆV*ª(P  Æç¥cn~ÐÞ__û5­±åå„%öÕ§«|ûTsÁT k‘Ìâ;·áN~%zÕ$+ðvü‡‘áCìQØÔÑH,_ …ĺ„3š@dj « î?Šˆ«-°Ks}iqu˜ÕÂ0BÛe 籓AöíDu±“äWù£K ¼‹Ä€_,ÆYœ2¬[nÝÃÒ³ª•ö×w—œi"%—K‘¶CõÚ­Ž! ÄgmýŽm÷ΓßåN˜‰í—²0Úáºä1çšTxø¨¤hãRp@:è?Zzh›;ƒ†Í"¬ÆòEoyt(ÛË®iÆ=…ôjm&æÄ2rãgøùüëBKèP |r壄@e÷Žq…wõ¨›ØqXGŽ#`í«ñL —úÔÌäЃ÷¨‡eK ."R׈¬ó7ûµÚr›;’?wê¤Ò‘Qãô3’ábTcœ1`OZÓ¢RäI*I%ε(€K¦6'Þ÷¶øïÚ²­ObÆžÕk±æíuï’Àô­*І6ä—‘e§Új‘WÆFÕ¨)ð®Ù8ïI3¾~“GaðÞÛ™QXâYÒH#§\“Šg>_NÚmtYv|*=sô¦q"Ø6‰ §„ù ¶Ÿ• oÂvo…0P  @þ.žÑ"¶Ûg¨ÎÛTL¨‹ía {_{ò€z*˜”Çu©™É4¾ÐâFÿËûÔC²¥ÂûA$-DçI« ƒÀíŠOÇ‚±ýO~¬]r¾©ñ½Æ¯¼ÑØyU–£BÇšYR9 p‘3d x\ʹ*Sç Ú-Ù&—ÙˆÓ6´VÐ# ¿Õ9bÌF:Ó“T؇—·>])Y`n§G„q‚7øÒ[îCuš?(ü¯Â÷ VÆ2ʰ†R¸ÔòøIѾ<>Tþ„¥'¾÷ý$.aŒ>®g,ëÕYÆ7Óµ)ôyÙ)ÎMp›(•}žæ€ð«“< ¢b»~_Âiu0*¹m1Ÿ3µ âhI_ `À®Ö¢EDZÈ×vãC 8ß鹩‹)öh jfTdQ >22 ¥} ·Àþµ8ù5«¤'â’\ÉwˆãÕ‰¦B?-kí»q:a‡E¯(÷‚Kí6¡›SJFªàdl m¨¯Š¶L“Ò–É_ÝK¼‘K4J¼ß‡rq¸ÇZÎ]™Ém·;ÔkH¢Œn­ã“câØmž†¹ó)mOƒØW±ºÕ—™£†Ùµ{8Fã8lì{Uád׿Ùo÷s‡Œ«£Âwéò«fŠzX\ÇÇíêh—(…Ø]hA(Ž4±]˜N–ʲ7šä§Á¥­ ¼mÕ~t£Š{‘ºžº¼¿¶¢eG‘\Xö¸6Aã¤ÍDy-ð6º¿†Ô3Jt…ÿò¶4ÅéÜ¿‘g¾ˆÞµÄDè„9{ >ž~µž‹ž«'6m­ùâ·ëméñɺè§lÿ´!M»³§*¦ÅSÝp÷Ÿ—urÛD0W!äxzäkG8n^LðÞ ‘qó ¿*ÞFÇ”Ûa–ÉÚ¹Ž >>Ëîx”“ç".؃ßtµo¹¤ª¶{’YÚáRÝÏÝb¬ç>uœßHÉüº·–[)|4œÕ "¿›t#åYÛŒ’]¹Ã8´ÕÌôÕô\WlqÒm³«ØrWt[ñ]ÈÆ31ƒð¡AM6aø6H' êi§¨I‘§ãS`k IK‚2¯ŸyÇCQÙ¥lk•µGC¸­LÀ.eoM¨”Æ«žµ*"¶ºËPSœúÔG’Ð/K›f’95L“+(ÈÏŒôÓô­S Ò­šáwæÒF·}P¯,¡oĽÃ| `¤ÔÝ«<éê–Wµü…¼Byð|GVç#ÈõÇʪZµ7{2½Lrc“ß‘T<¡&¹NÈAÑù½)£–?e÷æ+™9–Éá2hy:SEKñT]i,dblº.Hé¼µVRŽâðÇ0øB±º`!wÂ3šxö¯&‹ûeÅã–ŠÃ#œKøYCo;ÒÉñvCå GâÎÇ”ÿÒψ®ÌWÖªR”•^Å{Ò{>–·«—·;HÇ@¸ó­çšKAèJqÐ’ ׯB¨>LÃÂqÓõ®i9É=?ÙÁ,oyA„Ôú¤‘FwÆ}·9©Çó&Û2<¶º—´üw­ ?Ÿ›g¶ÌFÿ-«TÌÁ¤9v>¦˜ñAþíž„‰8ýj2p4gVõ#V,ƒÑ5‡ŸŸÖ¹°äzÜd¿FþŸò©–p[†šqí=XžK9Ü,÷;WgìëõqŒbœ^þøÄ–×H®ê–öþ`<ω[Óʈ¸·¥òŽj*KœÉÜÜ. Ûx0WX÷µF:®ÿ˜WCÆ™¬Ü²GK*½û9îdy9*HOwƞĊ~ăôÐLá¾ÍÙÂ&kPQåVDŒœ§ˆiß9>´¥t)zeN¶o²Ñ[[.¢ÓÈ ™ ;ÿh Ö2ÅFôõEvp"lQ½¥¥1ÔýÖ­ÇÖ£I:R°PÑÜj0ê›Ã§˜Ã7ÉéÒ¹rÛ’¤dþO€GHc i˸ÜgÌç½;Ô—B®’r´Ih":ßc+u@¾Xï]‘Â’KþOF8¶ö6‰a’áá2á×Âz{¹Íi J-׋Šã‘”71‚bv ã #Nšœ£“6 ÝÛ$:…s¹êãö®Z1pö i¶?Z´fÏjÄ ¼Î2|ýjeÀãɘã6¿r“,ž8»ã¯Ë¦?Z…Gf)Æœf¬8aÚF-Ê ù|)ûz£Í zmqר'‡-óÎÓs G‡,AÔ;®“úÖžš5Ø`í7ö6ö†“#o»`Gúz×iÙpn%bDJ[O½ŽÕÉ“Õ(º¥Ý­øêv=7í[ãȦ¬µÜ n0’ºÛÂÙÛïvÂwÒzJrèæÊúE/pÁË®Ü!g/°l’„þ•‹äç’¯Ò+Ž+;„of¹\êˆÁqÉïÞ¢PSO¯iµh/„ð”šbÓÅÐ×cÎ:ÖkµuHQ†ö1kX/Ì:#¸…ôŒû…—¢¶7Ç·S£¯ç;BKfžÜé¸+ËlÄɳ’Ë‚z«)ëåZjøÝîÍpß”uw® m \Äh9Õ·ƒ=}+/qïdcÏMÞ觇Ïp’+]†SÕ¶I=Tèò޲ߖc?>M µÃ4*U tÑÎV ^ …­ÉW/J‰p4fï‘ÖÄ`Ÿ"Ã?-ë-ýOËh‘b…¡øšŸ^Oè+ ™i[$O»*Ó{ð¹•JÛ2G¥Ï¾ùÏ ¿£ÍòPu¿ùvmé²S¡ ö Y–’÷Êà…ø‘µzÊ™èÆwÙX@¤Ç,s‚+—'¡ŒŽX£#h‡yÆ ÝsÓ>µ¾<+¢¸Tywm´2>y8²0 Ó¾œ·)¯³Ÿ/ŸvîHxŠÆÑI™ÎÁb¸÷]O¯•s³Ô·ì;„{M½¹…50*q¥ðz° ¿jQ|ìË;jç&¸ƒm©A+¤g½KŸB–EuÃqËø­­ýœ°”È4´-¾<Ž{Vsš1Ï–4×^? áòÚÜÊÀ8}$ääjð¹#¦v>U‰ÉqIy^¬]9_¾\e¾` Žˆ°ËKµ2;*ä ½3ŠEµÀ§íÅ.RH›—ÌS­UºöÉí¸®|ͦ¨åÌ÷äÏ<¥¥loúÚÉ&Îqß‘RÖèIºŸøG£mßëZF«“l_ƒ²Þ$v‰¢vQùJÂ|ôïS®+±^ÆÊÁ¡žÖ9bݧ„Žø­T‘×`‰[÷@døÅ—æ3¹[¨Âžå|ýk7|’Ó¸ðàá‡Ç"¡Éx†M¥rÄé˜AŸ˜ÿ5†µö5÷ÉÍèåÛ3ÈåøXwß§ZæŒ^´Š3 Ú7GÉÙ²:©ò9î=+кuÀ|óßÝ$³hT`Ê£ÝÈïŠo?òÍ}پʸŸ’öQ!# _O…-NNäDò90{{w¸"õÜ–ýéÓ›¢é-9(ÜÁ¾ß,ô¬Ú–¡J4[iÆç²´kEDtlâB0Ë«¯CƒéZÐ÷ˆ}¢áog²Ë^Å€Lˆ}Ü`ú¤²‰y­).òÌ{ŽŸJå“1{ž{8> ã$·Þ[5Dרtvò…ÔÊBùöòëQ¦_ÀÕð0´á²?‰”ã¶1õ&´†?%דèvQrmaò ÕD”ß.êß*`r¡áu=0jeØ{ÛxŒ2€ª¤©Æ¯­bhø`6–÷R4|íÜIŒ‘÷?¹¡&d¯‘½í¼Ú»of#,ØÜ¨énIߦõR]”c¯x`„ådÕΟ ÏñþiG#ÿQið «( ûxÓ©q8:t uÞª—&‘h]yìrÜÊÖßwoëû Ñ”¹Ø&xÜX©Û ­lg²ótªÉ#:‚q“ž»œÑ©¾· o“¡n’` Ôvg•EµÈ it™„gJ |kNPè1þåSR6HÛn§©®dµ7¸ ø-µµÊ –Tç¯Xßuè}ý+hc^D†—óJ!¸¶u:´¬ža—P ÜçmêþŠG¼99¯a[ÆÀ{Ÿ¾jW%> Þ+b ®“\GÌn>T±±¤ç¦(è Ýä Ë>&9ê2kš„/öû;VUÐ\m9^Õ·D'ÈÕâã岂˜Æ>)Ñ&WÙ%¦’êÊNNþŸ¥g-‹]‹"áW–bå€(ÑüMŒôÞšD¥©‰´ä‚6Èéò¡Š· ›{e:L’}ã¾òÇz™_] ™ Šæ-*Ì\‘·n¸©†Ïp\î%…žbxØ#‘2Gºg¸Èï½m$©ðÍœcÑ[_Ì‹qkŒK– Ù÷sŽ·ïS«FÜÑ:êÑe—âÚÆ#¹ ä¼z² ~ÛÏË­JŠ{®LÃíø-ÝœªòD—1û²rýý>€þÔ8ùܼE"†Å0@gP5dœnØñoÚ©ÒÄaön.lúÿ #?ûŽÂ”R5© 71òÙÓ¶øøQÐ[–nY¶5Îj6à‡þÍ‹ÓPÿäkeÂ3a¦¨BO´P‰#ˆžˆ?¯íYä訃à(ª2FøòñšMüEÃ3o‡oî;±Œ®T‰ìðFÉ¿aç]‰zí gÝõÅŸB «Hšv·ü…õ ŽýL—Kôqugì·\­Zå`ßÍ›sпɻ ÕÁoÈÇ( 'ªôø‘[Æ5E °ãC0EÿªÐ™ð8òhø ™µ±]Cïeñ¿Ï úQ°1•P‰@^í5yNôˆ¹ÀÊþRZó^†œþïâÿýk›ª³`Ûc;êë9ðTyÇyû7Ùô³>ã¦ÃÅžµ2 ò‘|ñ1n€œg>¾B®R¡QäjUƒ é;•;«|A­EE®e÷’ öJ £·fÒ\ÿŠR¾(ÈåÝÞ3KîeAùÒ¹æ¾[•[VæÜÕêDÑbÜÃù©êAAÙ/ša…ù’üÂüÍ¥¦  @€1ÿhlÖs*ŒÁ1$ÊÝJÿ”×e§ÐºÓ‰K xP…·ÆNäžôÔ¶‹ZêV÷œŸM… i˜°ëIŒuo¾æ„ˆúH×ß?*Ý0 Šê8F¯ZÓQ%rÞHzéÇ–(Ô- \É$r26êÄ5.EÐ\1ý|ëò0ÄÍ]RJâ4\»ë@¬ØpÛ±· Üï#yµjˆ ¦  @€)¹¶Šê†aª7"«ž / ••üq±&91ï#ëY8Ñ¢eZw¤,´ÀÔÐÓ. è5ucW<Ðã ÛÏö#z°=b1ø¶þñûÑ`KxõÈ4¾¿äÔÉ€Ú8°:Vb/HË0QÔû w  G ábÑy²ïpÃý#ÈV‰Æu@J”(P  ç‚+ˆÌr®¤=©™â<[R^0eƒÏºüGïPâRb¬mHÎY ?ÅW-·0c§ú|Ü>çW…sýÙ«Rž§»;êçF Øaoj ÛÅÞ¢Äkk%ËèKþCâiÐ/á1YøÛÇ?çòôZ´‰±…0%J”(P  @€»àö×9;Äçñ'î:R ±<ÿgîãÉŒ¬£ã¤ýóS¤«É ñ6—M?OÚxó…¤0¨xEõÆáFŸ6aûoE1XÊÛìÜ`ê¹}gò&ÃëÖ«H¬q 1Bº"P‹ä*ÄY@€%J”ÿÙrails-4.2.6/activerecord/test/assets/test.txt000066400000000000000000000000041266740050600213100ustar00rootroot00000000000000%00 rails-4.2.6/activerecord/test/cases/000077500000000000000000000000001266740050600173725ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/adapter_test.rb000066400000000000000000000233561266740050600224070ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require "models/book" require "models/post" require "models/author" module ActiveRecord class AdapterTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection end ## # PostgreSQL does not support null bytes in strings unless current_adapter?(:PostgreSQLAdapter) def test_update_prepared_statement b = Book.create(name: "my \x00 book") b.reload assert_equal "my \x00 book", b.name b.update_attributes(name: "my other \x00 book") b.reload assert_equal "my other \x00 book", b.name end end def test_tables tables = @connection.tables assert tables.include?("accounts") assert tables.include?("authors") assert tables.include?("tasks") assert tables.include?("topics") end def test_table_exists? assert @connection.table_exists?("accounts") assert !@connection.table_exists?("nonexistingtable") assert !@connection.table_exists?(nil) end def test_data_sources data_sources = @connection.data_sources assert data_sources.include?("accounts") assert data_sources.include?("authors") assert data_sources.include?("tasks") assert data_sources.include?("topics") end def test_data_source_exists? assert @connection.data_source_exists?("accounts") assert @connection.data_source_exists?(:accounts) assert_not @connection.data_source_exists?("nonexistingtable") assert_not @connection.data_source_exists?(nil) end def test_indexes idx_name = "accounts_idx" if @connection.respond_to?(:indexes) indexes = @connection.indexes("accounts") assert indexes.empty? @connection.add_index :accounts, :firm_id, :name => idx_name indexes = @connection.indexes("accounts") assert_equal "accounts", indexes.first.table assert_equal idx_name, indexes.first.name assert !indexes.first.unique assert_equal ["firm_id"], indexes.first.columns else warn "#{@connection.class} does not respond to #indexes" end ensure @connection.remove_index(:accounts, :name => idx_name) rescue nil end def test_current_database if @connection.respond_to?(:current_database) assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database end end if current_adapter?(:MysqlAdapter) def test_charset assert_not_nil @connection.charset assert_not_equal 'character_set_database', @connection.charset assert_equal @connection.show_variable('character_set_database'), @connection.charset end def test_collation assert_not_nil @connection.collation assert_not_equal 'collation_database', @connection.collation assert_equal @connection.show_variable('collation_database'), @connection.collation end def test_show_nonexistent_variable_returns_nil assert_nil @connection.show_variable('foo_bar_baz') end def test_not_specifying_database_name_for_cross_database_selects begin assert_nothing_raised do ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database)) config = ARTest.connection_config ActiveRecord::Base.connection.execute( "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \ "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses" ) end ensure ActiveRecord::Base.establish_connection :arunit end end end def test_table_alias def @connection.test_table_alias_length() 10; end class << @connection alias_method :old_table_alias_length, :table_alias_length alias_method :table_alias_length, :test_table_alias_length end assert_equal 'posts', @connection.table_alias_for('posts') assert_equal 'posts_comm', @connection.table_alias_for('posts_comments') assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts') class << @connection remove_method :table_alias_length alias_method :table_alias_length, :old_table_alias_length end end # test resetting sequences in odd tables in PostgreSQL if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) require 'models/movie' require 'models/subscriber' def test_reset_empty_table_with_custom_pk Movie.delete_all Movie.connection.reset_pk_sequence! 'movies' assert_equal 1, Movie.create(:name => 'fight club').id end def test_reset_table_with_non_integer_pk Subscriber.delete_all Subscriber.connection.reset_pk_sequence! 'subscribers' sub = Subscriber.new(:name => 'robert drake') sub.id = 'bob drake' assert_nothing_raised { sub.save! } end end def test_uniqueness_violations_are_translated_to_specific_exception @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" assert_raises(ActiveRecord::RecordNotUnique) do @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" end end unless current_adapter?(:SQLite3Adapter) def test_foreign_key_violations_are_translated_to_specific_exception assert_raises(ActiveRecord::InvalidForeignKey) do # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method if @connection.prefetch_primary_key? id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" else @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" end end end def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false klass_has_fk = Class.new(ActiveRecord::Base) do self.table_name = 'fk_test_has_fk' end assert_raises(ActiveRecord::InvalidForeignKey) do has_fk = klass_has_fk.new has_fk.fk_id = 1231231231 has_fk.save(validate: false) end end end def test_disable_referential_integrity assert_nothing_raised do @connection.disable_referential_integrity do # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method if @connection.prefetch_primary_key? id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" else @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" end # should delete created record as otherwise disable_referential_integrity will try to enable constraints after executed block # and will fail (at least on Oracle) @connection.execute "DELETE FROM fk_test_has_fk" end end end def test_select_all_always_return_activerecord_result result = @connection.select_all "SELECT * FROM posts" assert result.is_a?(ActiveRecord::Result) end def test_select_methods_passing_a_association_relation author = Author.create!(name: 'john') Post.create!(author: author, title: 'foo', body: 'bar') query = author.posts.where(title: 'foo').select(:title) assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values)) assert_equal({"title" => "foo"}, @connection.select_one(query)) assert @connection.select_all(query).is_a?(ActiveRecord::Result) assert_equal "foo", @connection.select_value(query) assert_equal ["foo"], @connection.select_values(query) end def test_select_methods_passing_a_relation Post.create!(title: 'foo', body: 'bar') query = Post.where(title: 'foo').select(:title) assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values)) assert_equal({"title" => "foo"}, @connection.select_one(query)) assert @connection.select_all(query).is_a?(ActiveRecord::Result) assert_equal "foo", @connection.select_value(query) assert_equal ["foo"], @connection.select_values(query) end test "type_to_sql returns a String for unmapped types" do assert_equal "special_db_type", @connection.type_to_sql(:special_db_type) end unless current_adapter?(:PostgreSQLAdapter) def test_log_invalid_encoding assert_raise ActiveRecord::StatementInvalid do @connection.send :log, "SELECT 'Ñ‹' FROM DUAL" do raise 'Ñ‹'.force_encoding(Encoding::ASCII_8BIT) end end end end end class AdapterTestWithoutTransaction < ActiveRecord::TestCase self.use_transactional_fixtures = false class Klass < ActiveRecord::Base end def setup Klass.establish_connection :arunit @connection = Klass.connection end teardown do Klass.remove_connection end unless in_memory_db? test "transaction state is reset after a reconnect" do @connection.begin_transaction assert @connection.transaction_open? @connection.reconnect! assert !@connection.transaction_open? end test "transaction state is reset after a disconnect" do @connection.begin_transaction assert @connection.transaction_open? @connection.disconnect! assert !@connection.transaction_open? end end end end rails-4.2.6/activerecord/test/cases/adapters/000077500000000000000000000000001266740050600211755ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/adapters/mysql/000077500000000000000000000000001266740050600223425ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/adapters/mysql/active_schema_test.rb000066400000000000000000000145501266740050600265260ustar00rootroot00000000000000require "cases/helper" require 'support/connection_helper' class ActiveSchemaTest < ActiveRecord::TestCase include ConnectionHelper def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute def execute(sql, name = nil) return sql end end end teardown do reset_connection end def test_add_index # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed def (ActiveRecord::Base.connection).table_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " assert_equal expected, add_index(:people, :last_name, :length => nil) expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) " assert_equal expected, add_index(:people, :last_name, :length => 10) expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15) expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15}) expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10}) %w(SPATIAL FULLTEXT UNIQUE).each do |type| expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) " assert_equal expected, add_index(:people, :last_name, :type => type) end %w(btree hash).each do |using| expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) " assert_equal expected, add_index(:people, :last_name, :using => using) end expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) " assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree) expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY" assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy) assert_raise ArgumentError do add_index(:people, :last_name, algorithm: :coyp) end expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) end def test_drop_table assert_equal "DROP TABLE `people`", drop_table(:people) end if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_create_mysql_database_with_encoding assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) end def test_recreate_mysql_database_with_encoding create_database(:luca, {:charset => 'latin1'}) assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) end end def test_add_column assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string) end def test_add_column_with_limit assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32) end def test_drop_table_with_specific_database assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people') end def test_add_timestamps with_real_execute do begin ActiveRecord::Base.connection.create_table :delete_me ActiveRecord::Base.connection.add_timestamps :delete_me, null: true assert column_present?('delete_me', 'updated_at', 'datetime') assert column_present?('delete_me', 'created_at', 'datetime') ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end end end def test_remove_timestamps with_real_execute do begin ActiveRecord::Base.connection.create_table :delete_me do |t| t.timestamps null: true end ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true } assert !column_present?('delete_me', 'updated_at', 'datetime') assert !column_present?('delete_me', 'created_at', 'datetime') ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end end end def test_indexes_in_create ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| t.index :zip end assert_equal expected, actual end private def with_real_execute ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_with_stub, :execute remove_method :execute alias_method :execute, :execute_without_stub end yield ensure ActiveRecord::Base.connection.singleton_class.class_eval do remove_method :execute alias_method :execute, :execute_with_stub end end def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) end def column_present?(table_name, column_name, type) results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'") results.first && results.first['Type'] == type end end rails-4.2.6/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb000066400000000000000000000044161266740050600273200ustar00rootroot00000000000000require "cases/helper" require 'models/person' class MysqlCaseSensitivityTest < ActiveRecord::TestCase class CollationTest < ActiveRecord::Base end repair_validations(CollationTest) def test_columns_include_collation_different_from_table assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation end def test_case_sensitive assert !CollationTest.columns_hash['string_ci_column'].case_sensitive? assert CollationTest.columns_hash['string_cs_column'].case_sensitive? end def test_case_insensitive_comparison_for_ci_column CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false) CollationTest.create!(:string_ci_column => 'A') invalid = CollationTest.new(:string_ci_column => 'a') queries = assert_sql { invalid.save } ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_no_match(/lower/i, ci_uniqueness_query) end def test_case_insensitive_comparison_for_cs_column CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false) CollationTest.create!(:string_cs_column => 'A') invalid = CollationTest.new(:string_cs_column => 'a') queries = assert_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_match(/lower/i, cs_uniqueness_query) end def test_case_sensitive_comparison_for_ci_column CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true) CollationTest.create!(:string_ci_column => 'A') invalid = CollationTest.new(:string_ci_column => 'A') queries = assert_sql { invalid.save } ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_match(/binary/i, ci_uniqueness_query) end def test_case_sensitive_comparison_for_cs_column CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true) CollationTest.create!(:string_cs_column => 'A') invalid = CollationTest.new(:string_cs_column => 'A') queries = assert_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_no_match(/binary/i, cs_uniqueness_query) end end rails-4.2.6/activerecord/test/cases/adapters/mysql/connection_test.rb000066400000000000000000000142121266740050600260650ustar00rootroot00000000000000require "cases/helper" require 'support/connection_helper' require 'support/ddl_helper' class MysqlConnectionTest < ActiveRecord::TestCase include ConnectionHelper include DdlHelper class Klass < ActiveRecord::Base end def setup super @connection = ActiveRecord::Base.connection end def test_mysql_reconnect_attribute_after_connection_with_reconnect_true run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true})) assert ActiveRecord::Base.connection.raw_connection.reconnect end end unless ARTest.connection_config['arunit']['socket'] def test_connect_with_url run_without_connection do ar_config = ARTest.connection_config['arunit'] url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" Klass.establish_connection(url) assert_equal ar_config['database'], Klass.connection.current_database end end end def test_mysql_reconnect_attribute_after_connection_with_reconnect_false run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false})) assert !ActiveRecord::Base.connection.raw_connection.reconnect end end def test_no_automatic_reconnection_after_timeout assert @connection.active? @connection.update('set @@wait_timeout=1') sleep 2 assert !@connection.active? # Repair all fixture connections so other tests won't break. @fixture_connections.each do |c| c.verify! end end def test_successful_reconnection_after_timeout_with_manual_reconnect assert @connection.active? @connection.update('set @@wait_timeout=1') sleep 2 @connection.reconnect! assert @connection.active? end def test_successful_reconnection_after_timeout_with_verify assert @connection.active? @connection.update('set @@wait_timeout=1') sleep 2 @connection.verify! assert @connection.active? end def test_bind_value_substitute bind_param = @connection.substitute_at('foo') assert_equal Arel.sql('?'), bind_param.to_sql end def test_exec_no_binds with_example_table do result = @connection.exec_query('SELECT id, data FROM ex') assert_equal 0, result.rows.length assert_equal 2, result.columns.length assert_equal %w{ id data }, result.columns @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') # if there are no bind parameters, it will return a string (due to # the libmysql api) result = @connection.exec_query('SELECT id, data FROM ex') assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [['1', 'foo']], result.rows end end def test_exec_with_binds with_example_table do @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') result = @connection.exec_query( 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [[1, 'foo']], result.rows end end def test_exec_typecasts_bind_vals with_example_table do @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') column = @connection.columns('ex').find { |col| col.name == 'id' } result = @connection.exec_query( 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [[1, 'foo']], result.rows end end # Test that MySQL allows multiple results for stored procedures if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS) def test_multi_results rows = ActiveRecord::Base.connection.select_rows('CALL ten();') assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'" end end def test_mysql_connection_collation_is_configured assert_equal 'utf8_unicode_ci', @connection.show_variable('collation_connection') assert_equal 'utf8_general_ci', ARUnit2Model.connection.show_variable('collation_connection') end def test_mysql_default_in_strict_mode result = @connection.exec_query "SELECT @@SESSION.sql_mode" assert_equal [["STRICT_ALL_TABLES"]], result.rows end def test_mysql_strict_mode_disabled run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false})) result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" assert_equal [['']], result.rows end end def test_mysql_set_session_variable run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}})) session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" assert_equal 3, session_mode.rows.first.first.to_i end end def test_mysql_sql_mode_variable_overrides_strict_mode run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' assert_not_equal [['STRICT_ALL_TABLES']], result.rows end end def test_mysql_set_session_variable_to_default run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT" session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" assert_equal global_mode.rows, session_mode.rows end end private def with_example_table(&block) definition ||= <<-SQL `id` int(11) auto_increment PRIMARY KEY, `data` varchar(255) SQL super(@connection, 'ex', definition, &block) end end rails-4.2.6/activerecord/test/cases/adapters/mysql/consistency_test.rb000066400000000000000000000025761266740050600263010ustar00rootroot00000000000000require "cases/helper" class MysqlConsistencyTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class Consistency < ActiveRecord::Base self.table_name = "mysql_consistency" end setup do @old_emulate_booleans = ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false @connection = ActiveRecord::Base.connection @connection.clear_cache! @connection.create_table("mysql_consistency") do |t| t.boolean "a_bool" t.string "a_string" end Consistency.reset_column_information Consistency.create! end teardown do ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = @old_emulate_booleans @connection.drop_table "mysql_consistency" end test "boolean columns with random value type cast to 0 when emulate_booleans is false" do with_new = Consistency.new with_last = Consistency.last with_new.a_bool = 'wibble' with_last.a_bool = 'wibble' assert_equal 0, with_new.a_bool assert_equal 0, with_last.a_bool end test "string columns call #to_s" do with_new = Consistency.new with_last = Consistency.last thing = Object.new with_new.a_string = thing with_last.a_string = thing assert_equal thing.to_s, with_new.a_string assert_equal thing.to_s, with_last.a_string end end rails-4.2.6/activerecord/test/cases/adapters/mysql/datetime_test.rb000066400000000000000000000067371266740050600255370ustar00rootroot00000000000000require 'cases/helper' if mysql_56? class DateTimeTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class Foo < ActiveRecord::Base; end def test_default_datetime_precision ActiveRecord::Base.connection.create_table(:foos, force: true) ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime assert_nil activerecord_column_option('foos', 'created_at', 'precision') end def test_datetime_data_type_with_precision ActiveRecord::Base.connection.create_table(:foos, force: true) ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1 ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5 assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision') assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision') end def test_timestamps_helper_with_custom_precision ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps null: true, precision: 4 end assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision') assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision') end def test_passing_precision_to_datetime_does_not_set_limit ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps null: true, precision: 4 end assert_nil activerecord_column_option('foos', 'created_at', 'limit') assert_nil activerecord_column_option('foos', 'updated_at', 'limit') end def test_invalid_datetime_precision_raises_error assert_raises ActiveRecord::ActiveRecordError do ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps null: true, precision: 7 end end end def test_mysql_agrees_with_activerecord_about_precision ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps null: true, precision: 4 end assert_equal 4, mysql_datetime_precision('foos', 'created_at') assert_equal 4, mysql_datetime_precision('foos', 'updated_at') end def test_formatting_datetime_according_to_precision ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.datetime :created_at, precision: 0 t.datetime :updated_at, precision: 4 end date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999) Foo.create!(created_at: date, updated_at: date) assert foo = Foo.find_by(created_at: date) assert_equal date.to_s, foo.created_at.to_s assert_equal date.to_s, foo.updated_at.to_s assert_equal 000000, foo.created_at.usec assert_equal 999900, foo.updated_at.usec end private def mysql_datetime_precision(table_name, column_name) results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'") result = results.find do |result_hash| result_hash["column_name"] == column_name end result && result["datetime_precision"] end def activerecord_column_option(tablename, column_name, option) result = ActiveRecord::Base.connection.columns(tablename).find do |column| column.name == column_name end result && result.send(option) end end end rails-4.2.6/activerecord/test/cases/adapters/mysql/enum_test.rb000066400000000000000000000003031266740050600246660ustar00rootroot00000000000000require "cases/helper" class MysqlEnumTest < ActiveRecord::TestCase class EnumTest < ActiveRecord::Base end def test_enum_limit assert_equal 6, EnumTest.columns.first.limit end end rails-4.2.6/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb000066400000000000000000000111351266740050600265740ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters class MysqlAdapterTest < ActiveRecord::TestCase include DdlHelper def setup @conn = ActiveRecord::Base.connection end def test_bad_connection_mysql assert_raise ActiveRecord::NoDatabaseError do configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest') connection = ActiveRecord::Base.mysql_connection(configuration) connection.exec_query('drop table if exists ex') end end def test_valid_column with_example_table do column = @conn.columns('ex').find { |col| col.name == 'id' } assert @conn.valid_type?(column.type) end end def test_invalid_column assert_not @conn.valid_type?(:foobar) end def test_client_encoding assert_equal Encoding::UTF_8, @conn.client_encoding end def test_exec_insert_number with_example_table do insert(@conn, 'number' => 10) result = @conn.exec_query('SELECT number FROM ex WHERE number = 10') assert_equal 1, result.rows.length # if there are no bind parameters, it will return a string (due to # the libmysql api) assert_equal '10', result.rows.last.last end end def test_exec_insert_string with_example_table do str = 'ã„ãŸã ãã¾ã™ï¼' insert(@conn, 'number' => 10, 'data' => str) result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10') value = result.rows.last.last # FIXME: this should probably be inside the mysql AR adapter? value.force_encoding(@conn.client_encoding) # The strings in this file are utf-8, so transcode to utf-8 value.encode!(Encoding::UTF_8) assert_equal str, value end end def test_tables_quoting @conn.tables(nil, "foo-bar", nil) flunk rescue => e # assertion for *quoted* database properly assert_match(/database 'foo-bar'/, e.inspect) end def test_pk_and_sequence_for with_example_table do pk, seq = @conn.pk_and_sequence_for('ex') assert_equal 'id', pk assert_equal @conn.default_sequence_name('ex', 'id'), seq end end def test_pk_and_sequence_for_with_non_standard_primary_key with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do pk, seq = @conn.pk_and_sequence_for('ex') assert_equal 'code', pk assert_equal @conn.default_sequence_name('ex', 'code'), seq end end def test_pk_and_sequence_for_with_custom_index_type_pk with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do pk, seq = @conn.pk_and_sequence_for('ex') assert_equal 'id', pk assert_equal @conn.default_sequence_name('ex', 'id'), seq end end def test_composite_primary_key with_example_table '`id` INT(11), `number` INT(11), foo INT(11), PRIMARY KEY (`id`, `number`)' do assert_nil @conn.primary_key('ex') end end def test_tinyint_integer_typecasting with_example_table '`status` TINYINT(4)' do insert(@conn, { 'status' => 2 }, 'ex') result = @conn.exec_query('SELECT status FROM ex') assert_equal 2, result.column_types['status'].type_cast_from_database(result.last['status']) end end def test_supports_extensions assert_not @conn.supports_extensions?, 'does not support extensions' end def test_respond_to_enable_extension assert @conn.respond_to?(:enable_extension) end def test_respond_to_disable_extension assert @conn.respond_to?(:disable_extension) end private def insert(ctx, data, table='ex') binds = data.map { |name, value| [ctx.columns(table).find { |x| x.name == name }, value] } columns = binds.map(&:first).map(&:name) sql = "INSERT INTO #{table} (#{columns.join(", ")}) VALUES (#{(['?'] * columns.length).join(', ')})" ctx.exec_insert(sql, 'SQL', binds) end def with_example_table(definition = nil, &block) definition ||= <<-SQL `id` int(11) auto_increment PRIMARY KEY, `number` integer, `data` varchar(255) SQL super(@conn, 'ex', definition, &block) end end end end rails-4.2.6/activerecord/test/cases/adapters/mysql/quoting_test.rb000066400000000000000000000012171266740050600254150ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class MysqlAdapter class QuotingTest < ActiveRecord::TestCase def setup @conn = ActiveRecord::Base.connection end def test_type_cast_true c = Column.new(nil, 1, Type::Boolean.new) assert_equal 1, @conn.type_cast(true, nil) assert_equal 1, @conn.type_cast(true, c) end def test_type_cast_false c = Column.new(nil, 1, Type::Boolean.new) assert_equal 0, @conn.type_cast(false, nil) assert_equal 0, @conn.type_cast(false, c) end end end end end rails-4.2.6/activerecord/test/cases/adapters/mysql/reserved_word_test.rb000066400000000000000000000122041266740050600265770ustar00rootroot00000000000000require "cases/helper" class Group < ActiveRecord::Base Group.table_name = 'group' belongs_to :select has_one :values end class Select < ActiveRecord::Base Select.table_name = 'select' has_many :groups end class Values < ActiveRecord::Base Values.table_name = 'values' end class Distinct < ActiveRecord::Base Distinct.table_name = 'distinct' has_and_belongs_to_many :selects has_many :values, :through => :groups end # a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with # reserved word names (ie: group, order, values, etc...) class MysqlReservedWordTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table() # will fail with these table names if these test cases fail create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int', 'select'=>'id int auto_increment primary key', 'values'=>'id int auto_increment primary key, group_id int', 'distinct'=>'id int auto_increment primary key', 'distinct_select'=>'distinct_id int, select_id int' end teardown do drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order'] end # create tables with reserved-word names and columns def test_create_tables assert_nothing_raised { @connection.create_table :order do |t| t.column :group, :string end } end # rename tables with reserved-word names def test_rename_tables assert_nothing_raised { @connection.rename_table(:group, :order) } end # alter column with a reserved-word name in a table with a reserved-word name def test_change_columns assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') } #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) } assert_nothing_raised { @connection.rename_column(:group, :order, :values) } end # introspect table with reserved word name def test_introspect assert_nothing_raised { @connection.columns(:group) } assert_nothing_raised { @connection.indexes(:group) } end #fixtures self.use_instantiated_fixtures = true self.use_transactional_fixtures = false #activerecord model class with reserved-word table name def test_activerecord_model create_test_fixtures :select, :distinct, :group, :values, :distinct_select x = nil assert_nothing_raised { x = Group.new } x.order = 'x' assert_nothing_raised { x.save } x.order = 'y' assert_nothing_raised { x.save } assert_nothing_raised { Group.find_by_order('y') } assert_nothing_raised { Group.find(1) } Group.find(1) end # has_one association with reserved-word table name def test_has_one_associations create_test_fixtures :select, :distinct, :group, :values, :distinct_select v = nil assert_nothing_raised { v = Group.find(1).values } assert_equal 2, v.id end # belongs_to association with reserved-word table name def test_belongs_to_associations create_test_fixtures :select, :distinct, :group, :values, :distinct_select gs = nil assert_nothing_raised { gs = Select.find(2).groups } assert_equal gs.length, 2 assert(gs.collect{|x| x.id}.sort == [2, 3]) end # has_and_belongs_to_many with reserved-word table name def test_has_and_belongs_to_many create_test_fixtures :select, :distinct, :group, :values, :distinct_select s = nil assert_nothing_raised { s = Distinct.find(1).selects } assert_equal s.length, 2 assert(s.collect{|x|x.id}.sort == [1, 2]) end # activerecord model introspection with reserved-word table and column names def test_activerecord_introspection assert_nothing_raised { Group.table_exists? } assert_nothing_raised { Group.columns } end # Calculations def test_calculations_work_with_reserved_words assert_nothing_raised { Group.count } end def test_associations_work_with_reserved_words assert_nothing_raised { Select.all.merge!(:includes => [:groups]).to_a } end #the following functions were added to DRY test cases private # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path def create_test_fixtures(*fixture_names) ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) end # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name def drop_tables_directly(table_names, connection = @connection) table_names.each do |name| connection.execute("DROP TABLE IF EXISTS `#{name}`") end end # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns def create_tables_directly (tables, connection = @connection) tables.each do |table_name, column_properties| connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") end end end rails-4.2.6/activerecord/test/cases/adapters/mysql/schema_test.rb000066400000000000000000000064141266740050600251730ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/comment' module ActiveRecord module ConnectionAdapters class MysqlSchemaTest < ActiveRecord::TestCase fixtures :posts def setup @connection = ActiveRecord::Base.connection db = Post.connection_pool.spec.config[:database] table = Post.table_name @db_name = db @omgpost = Class.new(ActiveRecord::Base) do self.table_name = "#{db}.#{table}" def self.name; 'Post'; end end @connection.create_table "mysql_doubles" end teardown do @connection.execute "drop table if exists mysql_doubles" end class MysqlDouble < ActiveRecord::Base self.table_name = "mysql_doubles" end def test_float_limits @connection.add_column :mysql_doubles, :float_no_limit, :float @connection.add_column :mysql_doubles, :float_short, :float, limit: 5 @connection.add_column :mysql_doubles, :float_long, :float, limit: 53 @connection.add_column :mysql_doubles, :float_23, :float, limit: 23 @connection.add_column :mysql_doubles, :float_24, :float, limit: 24 @connection.add_column :mysql_doubles, :float_25, :float, limit: 25 MysqlDouble.reset_column_information column_no_limit = MysqlDouble.columns.find { |c| c.name == 'float_no_limit' } column_short = MysqlDouble.columns.find { |c| c.name == 'float_short' } column_long = MysqlDouble.columns.find { |c| c.name == 'float_long' } column_23 = MysqlDouble.columns.find { |c| c.name == 'float_23' } column_24 = MysqlDouble.columns.find { |c| c.name == 'float_24' } column_25 = MysqlDouble.columns.find { |c| c.name == 'float_25' } # Mysql floats are precision 0..24, Mysql doubles are precision 25..53 assert_equal 24, column_no_limit.limit assert_equal 24, column_short.limit assert_equal 53, column_long.limit assert_equal 24, column_23.limit assert_equal 24, column_24.limit assert_equal 53, column_25.limit end def test_schema assert @omgpost.first end def test_primary_key assert_equal 'id', @omgpost.primary_key end def test_table_exists? name = @omgpost.table_name assert @connection.table_exists?(name), "#{name} table should exist" end def test_table_exists_wrong_schema assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist") end def test_dump_indexes index_a_name = 'index_key_tests_on_snack' index_b_name = 'index_key_tests_on_pizza' index_c_name = 'index_key_tests_on_awesome' table = 'key_tests' indexes = @connection.indexes(table).sort_by {|i| i.name} assert_equal 3,indexes.size index_a = indexes.select{|i| i.name == index_a_name}[0] index_b = indexes.select{|i| i.name == index_b_name}[0] index_c = indexes.select{|i| i.name == index_c_name}[0] assert_equal :btree, index_a.using assert_nil index_a.type assert_equal :btree, index_b.using assert_nil index_b.type assert_nil index_c.using assert_equal :fulltext, index_c.type end end end end rails-4.2.6/activerecord/test/cases/adapters/mysql/sp_test.rb000066400000000000000000000007351266740050600243550ustar00rootroot00000000000000require "cases/helper" require 'models/topic' class StoredProcedureTest < ActiveRecord::TestCase fixtures :topics # Test that MySQL allows multiple results for stored procedures if Mysql.const_defined?(:CLIENT_MULTI_RESULTS) def test_multi_results_from_find_by_sql topics = Topic.find_by_sql 'CALL topics();' assert_equal 1, topics.size assert ActiveRecord::Base.connection.active?, "Bad connection use by 'MysqlAdapter.select'" end end end rails-4.2.6/activerecord/test/cases/adapters/mysql/sql_types_test.rb000066400000000000000000000006351266740050600257550ustar00rootroot00000000000000require "cases/helper" class SqlTypesTest < ActiveRecord::TestCase def test_binary_types assert_equal 'varbinary(64)', type_to_sql(:binary, 64) assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095) assert_equal 'blob(4096)', type_to_sql(:binary, 4096) assert_equal 'blob', type_to_sql(:binary) end def type_to_sql(*args) ActiveRecord::Base.connection.type_to_sql(*args) end end rails-4.2.6/activerecord/test/cases/adapters/mysql/statement_pool_test.rb000066400000000000000000000010601266740050600267600ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord::ConnectionAdapters class MysqlAdapter class StatementPoolTest < ActiveRecord::TestCase if Process.respond_to?(:fork) def test_cache_is_per_pid cache = StatementPool.new nil, 10 cache['foo'] = 'bar' assert_equal 'bar', cache['foo'] pid = fork { lookup = cache['foo']; exit!(!lookup) } Process.waitpid pid assert $?.success?, 'process should exit successfully' end end end end end rails-4.2.6/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb000066400000000000000000000013721266740050600266060ustar00rootroot00000000000000require "cases/helper" class UnsignedTypeTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class UnsignedType < ActiveRecord::Base end setup do @connection = ActiveRecord::Base.connection @connection.create_table("unsigned_types", force: true) do |t| t.column :unsigned_integer, "int unsigned" end end teardown do @connection.drop_table "unsigned_types" end test "unsigned int max value is in range" do assert expected = UnsignedType.create(unsigned_integer: 4294967295) assert_equal expected, UnsignedType.find_by(unsigned_integer: 4294967295) end test "minus value is out of range" do assert_raise(RangeError) do UnsignedType.create(unsigned_integer: -10) end end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/000077500000000000000000000000001266740050600224245ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/adapters/mysql2/active_schema_test.rb000066400000000000000000000145311266740050600266070ustar00rootroot00000000000000require "cases/helper" require 'support/connection_helper' class ActiveSchemaTest < ActiveRecord::TestCase include ConnectionHelper def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute def execute(sql, name = nil) return sql end end end teardown do reset_connection end def test_add_index # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed def (ActiveRecord::Base.connection).table_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " assert_equal expected, add_index(:people, :last_name, :length => nil) expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) " assert_equal expected, add_index(:people, :last_name, :length => 10) expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15) expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15}) expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10}) %w(SPATIAL FULLTEXT UNIQUE).each do |type| expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) " assert_equal expected, add_index(:people, :last_name, :type => type) end %w(btree hash).each do |using| expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) " assert_equal expected, add_index(:people, :last_name, :using => using) end expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) " assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree) expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY" assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy) assert_raise ArgumentError do add_index(:people, :last_name, algorithm: :coyp) end expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) end def test_drop_table assert_equal "DROP TABLE `people`", drop_table(:people) end if current_adapter?(:Mysql2Adapter) def test_create_mysql_database_with_encoding assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) end def test_recreate_mysql_database_with_encoding create_database(:luca, {:charset => 'latin1'}) assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) end end def test_add_column assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string) end def test_add_column_with_limit assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32) end def test_drop_table_with_specific_database assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people') end def test_add_timestamps with_real_execute do begin ActiveRecord::Base.connection.create_table :delete_me ActiveRecord::Base.connection.add_timestamps :delete_me, null: true assert column_present?('delete_me', 'updated_at', 'datetime') assert column_present?('delete_me', 'created_at', 'datetime') ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end end end def test_remove_timestamps with_real_execute do begin ActiveRecord::Base.connection.create_table :delete_me do |t| t.timestamps null: true end ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true } assert !column_present?('delete_me', 'updated_at', 'datetime') assert !column_present?('delete_me', 'created_at', 'datetime') ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end end end def test_indexes_in_create ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| t.index :zip end assert_equal expected, actual end private def with_real_execute ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_with_stub, :execute remove_method :execute alias_method :execute, :execute_without_stub end yield ensure ActiveRecord::Base.connection.singleton_class.class_eval do remove_method :execute alias_method :execute, :execute_with_stub end end def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) end def column_present?(table_name, column_name, type) results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'") results.first && results.first['Type'] == type end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb000066400000000000000000000023411266740050600267640ustar00rootroot00000000000000require "cases/helper" require 'models/topic' module ActiveRecord module ConnectionAdapters class Mysql2Adapter class BindParameterTest < ActiveRecord::TestCase fixtures :topics def test_update_question_marks str = "foo?bar" x = Topic.first x.title = str x.content = str x.save! x.reload assert_equal str, x.title assert_equal str, x.content end def test_create_question_marks str = "foo?bar" x = Topic.create!(:title => str, :content => str) x.reload assert_equal str, x.title assert_equal str, x.content end def test_update_null_bytes str = "foo\0bar" x = Topic.first x.title = str x.content = str x.save! x.reload assert_equal str, x.title assert_equal str, x.content end def test_create_null_bytes str = "foo\0bar" x = Topic.create!(:title => str, :content => str) x.reload assert_equal str, x.title assert_equal str, x.content end end end end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/boolean_test.rb000066400000000000000000000051401266740050600254270ustar00rootroot00000000000000require "cases/helper" class Mysql2BooleanTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class BooleanType < ActiveRecord::Base self.table_name = "mysql_booleans" end setup do @connection = ActiveRecord::Base.connection @connection.clear_cache! @connection.create_table("mysql_booleans") do |t| t.boolean "archived" t.string "published", limit: 1 end BooleanType.reset_column_information @emulate_booleans = ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans end teardown do emulate_booleans @emulate_booleans @connection.drop_table "mysql_booleans" end test "column type with emulated booleans" do emulate_booleans true assert_equal :boolean, boolean_column.type assert_equal :string, string_column.type end test "column type without emulated booleans" do emulate_booleans false assert_equal :integer, boolean_column.type assert_equal :string, string_column.type end test "test type casting with emulated booleans" do emulate_booleans true boolean = BooleanType.create!(archived: true, published: true) attributes = boolean.reload.attributes_before_type_cast assert_equal 1, attributes["archived"] assert_equal "1", attributes["published"] assert_equal 1, @connection.type_cast(true, boolean_column) assert_equal "1", @connection.type_cast(true, string_column) end test "test type casting without emulated booleans" do emulate_booleans false boolean = BooleanType.create!(archived: true, published: true) attributes = boolean.reload.attributes_before_type_cast assert_equal 1, attributes["archived"] assert_equal "1", attributes["published"] assert_equal 1, @connection.type_cast(true, boolean_column) assert_equal "1", @connection.type_cast(true, string_column) end test "with booleans stored as 1 and 0" do @connection.execute "INSERT INTO mysql_booleans(archived, published) VALUES(1, '1')" boolean = BooleanType.first assert_equal true, boolean.archived assert_equal "1", boolean.published end test "with booleans stored as t" do @connection.execute "INSERT INTO mysql_booleans(published) VALUES('t')" boolean = BooleanType.first assert_equal "t", boolean.published end def boolean_column BooleanType.columns.find { |c| c.name == 'archived' } end def string_column BooleanType.columns.find { |c| c.name == 'published' } end def emulate_booleans(value) ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = value BooleanType.reset_column_information end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb000066400000000000000000000044161266740050600274020ustar00rootroot00000000000000require "cases/helper" require 'models/person' class Mysql2CaseSensitivityTest < ActiveRecord::TestCase class CollationTest < ActiveRecord::Base end repair_validations(CollationTest) def test_columns_include_collation_different_from_table assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation end def test_case_sensitive assert !CollationTest.columns_hash['string_ci_column'].case_sensitive? assert CollationTest.columns_hash['string_cs_column'].case_sensitive? end def test_case_insensitive_comparison_for_ci_column CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false) CollationTest.create!(:string_ci_column => 'A') invalid = CollationTest.new(:string_ci_column => 'a') queries = assert_sql { invalid.save } ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_no_match(/lower/i, ci_uniqueness_query) end def test_case_insensitive_comparison_for_cs_column CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false) CollationTest.create!(:string_cs_column => 'A') invalid = CollationTest.new(:string_cs_column => 'a') queries = assert_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)} assert_match(/lower/i, cs_uniqueness_query) end def test_case_sensitive_comparison_for_ci_column CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true) CollationTest.create!(:string_ci_column => 'A') invalid = CollationTest.new(:string_ci_column => 'A') queries = assert_sql { invalid.save } ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_match(/binary/i, ci_uniqueness_query) end def test_case_sensitive_comparison_for_cs_column CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true) CollationTest.create!(:string_cs_column => 'A') invalid = CollationTest.new(:string_cs_column => 'A') queries = assert_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_no_match(/binary/i, cs_uniqueness_query) end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/connection_test.rb000066400000000000000000000112131266740050600261450ustar00rootroot00000000000000require "cases/helper" require 'support/connection_helper' class MysqlConnectionTest < ActiveRecord::TestCase include ConnectionHelper fixtures :comments def setup super @subscriber = SQLSubscriber.new @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) @connection = ActiveRecord::Base.connection end def teardown ActiveSupport::Notifications.unsubscribe(@subscription) super end def test_bad_connection assert_raise ActiveRecord::NoDatabaseError do configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest') connection = ActiveRecord::Base.mysql2_connection(configuration) connection.exec_query('drop table if exists ex') end end def test_truncate rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments") count = rows.first.values.first assert_operator count, :>, 0 ActiveRecord::Base.connection.truncate("comments") rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments") count = rows.first.values.first assert_equal 0, count end def test_no_automatic_reconnection_after_timeout assert @connection.active? @connection.update('set @@wait_timeout=1') sleep 2 assert !@connection.active? # Repair all fixture connections so other tests won't break. @fixture_connections.each do |c| c.verify! end end def test_successful_reconnection_after_timeout_with_manual_reconnect assert @connection.active? @connection.update('set @@wait_timeout=1') sleep 2 @connection.reconnect! assert @connection.active? end def test_successful_reconnection_after_timeout_with_verify assert @connection.active? @connection.update('set @@wait_timeout=1') sleep 2 @connection.verify! assert @connection.active? end def test_mysql_connection_collation_is_configured assert_equal 'utf8_unicode_ci', @connection.show_variable('collation_connection') assert_equal 'utf8_general_ci', ARUnit2Model.connection.show_variable('collation_connection') end # TODO: Below is a straight up copy/paste from mysql/connection_test.rb # I'm not sure what the correct way is to share these tests between # adapters in minitest. def test_mysql_default_in_strict_mode result = @connection.exec_query "SELECT @@SESSION.sql_mode" assert_equal [["STRICT_ALL_TABLES"]], result.rows end def test_mysql_strict_mode_disabled run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false})) result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" assert_equal [['']], result.rows end end def test_mysql_set_session_variable run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}})) session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" assert_equal 3, session_mode.rows.first.first.to_i end end def test_mysql_sql_mode_variable_overrides_strict_mode run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' assert_not_equal [['STRICT_ALL_TABLES']], result.rows end end def test_mysql_set_session_variable_to_default run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT" session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" assert_equal global_mode.rows, session_mode.rows end end def test_logs_name_show_variable @connection.show_variable 'foo' assert_equal "SCHEMA", @subscriber.logged[0][1] end def test_logs_name_rename_column_sql @connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))" @subscriber.logged.clear @connection.send(:rename_column_sql, 'bar_baz', 'foo', 'foo2') assert_equal "SCHEMA", @subscriber.logged[0][1] ensure @connection.execute "DROP TABLE `bar_baz`" end if mysql_56? def test_quote_time_usec assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0)) assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime) end end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/datetime_test.rb000066400000000000000000000067371266740050600256210ustar00rootroot00000000000000require 'cases/helper' if mysql_56? class DateTimeTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class Foo < ActiveRecord::Base; end def test_default_datetime_precision ActiveRecord::Base.connection.create_table(:foos, force: true) ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime assert_nil activerecord_column_option('foos', 'created_at', 'precision') end def test_datetime_data_type_with_precision ActiveRecord::Base.connection.create_table(:foos, force: true) ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1 ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5 assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision') assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision') end def test_timestamps_helper_with_custom_precision ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps null: true, precision: 4 end assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision') assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision') end def test_passing_precision_to_datetime_does_not_set_limit ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps null: true, precision: 4 end assert_nil activerecord_column_option('foos', 'created_at', 'limit') assert_nil activerecord_column_option('foos', 'updated_at', 'limit') end def test_invalid_datetime_precision_raises_error assert_raises ActiveRecord::ActiveRecordError do ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps null: true, precision: 7 end end end def test_mysql_agrees_with_activerecord_about_precision ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps null: true, precision: 4 end assert_equal 4, mysql_datetime_precision('foos', 'created_at') assert_equal 4, mysql_datetime_precision('foos', 'updated_at') end def test_formatting_datetime_according_to_precision ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.datetime :created_at, precision: 0 t.datetime :updated_at, precision: 4 end date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999) Foo.create!(created_at: date, updated_at: date) assert foo = Foo.find_by(created_at: date) assert_equal date.to_s, foo.created_at.to_s assert_equal date.to_s, foo.updated_at.to_s assert_equal 000000, foo.created_at.usec assert_equal 999900, foo.updated_at.usec end private def mysql_datetime_precision(table_name, column_name) results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'") result = results.find do |result_hash| result_hash["column_name"] == column_name end result && result["datetime_precision"] end def activerecord_column_option(tablename, column_name, option) result = ActiveRecord::Base.connection.columns(tablename).find do |column| column.name == column_name end result && result.send(option) end end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/enum_test.rb000066400000000000000000000003041266740050600247510ustar00rootroot00000000000000require "cases/helper" class Mysql2EnumTest < ActiveRecord::TestCase class EnumTest < ActiveRecord::Base end def test_enum_limit assert_equal 6, EnumTest.columns.first.limit end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/explain_test.rb000066400000000000000000000020001266740050600254400ustar00rootroot00000000000000require "cases/helper" require 'models/developer' require 'models/computer' module ActiveRecord module ConnectionAdapters class Mysql2Adapter class ExplainTest < ActiveRecord::TestCase fixtures :developers def test_explain_for_one_query explain = Developer.where(:id => 1).explain assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain assert_match %r(developers |.* const), explain end def test_explain_with_eager_loading explain = Developer.where(:id => 1).includes(:audit_logs).explain assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain assert_match %r(developers |.* const), explain assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain assert_match %r(audit_logs |.* ALL), explain end end end end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb000066400000000000000000000026451266740050600267460ustar00rootroot00000000000000require "cases/helper" class Mysql2AdapterTest < ActiveRecord::TestCase def setup @conn = ActiveRecord::Base.connection end def test_columns_for_distinct_zero_orders assert_equal "posts.id", @conn.columns_for_distinct("posts.id", []) end def test_columns_for_distinct_one_order assert_equal "posts.id, posts.created_at AS alias_0", @conn.columns_for_distinct("posts.id", ["posts.created_at desc"]) end def test_columns_for_distinct_few_orders assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1", @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"]) end def test_columns_for_distinct_with_case assert_equal( 'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0', @conn.columns_for_distinct('posts.id', ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"]) ) end def test_columns_for_distinct_blank_not_nil_orders assert_equal "posts.id, posts.created_at AS alias_0", @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "]) end def test_columns_for_distinct_with_arel_order order = Object.new def order.to_sql "posts.created_at desc" end assert_equal "posts.id, posts.created_at AS alias_0", @conn.columns_for_distinct("posts.id", [order]) end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb000066400000000000000000000121621266740050600266640ustar00rootroot00000000000000require "cases/helper" class Group < ActiveRecord::Base Group.table_name = 'group' belongs_to :select has_one :values end class Select < ActiveRecord::Base Select.table_name = 'select' has_many :groups end class Values < ActiveRecord::Base Values.table_name = 'values' end class Distinct < ActiveRecord::Base Distinct.table_name = 'distinct' has_and_belongs_to_many :selects has_many :values, :through => :groups end # a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with # reserved word names (ie: group, order, values, etc...) class MysqlReservedWordTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table() # will fail with these table names if these test cases fail create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int', 'select'=>'id int auto_increment primary key', 'values'=>'id int auto_increment primary key, group_id int', 'distinct'=>'id int auto_increment primary key', 'distinct_select'=>'distinct_id int, select_id int' end teardown do drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order'] end # create tables with reserved-word names and columns def test_create_tables assert_nothing_raised { @connection.create_table :order do |t| t.column :group, :string end } end # rename tables with reserved-word names def test_rename_tables assert_nothing_raised { @connection.rename_table(:group, :order) } end # alter column with a reserved-word name in a table with a reserved-word name def test_change_columns assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') } #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) } assert_nothing_raised { @connection.rename_column(:group, :order, :values) } end # introspect table with reserved word name def test_introspect assert_nothing_raised { @connection.columns(:group) } assert_nothing_raised { @connection.indexes(:group) } end #fixtures self.use_instantiated_fixtures = true self.use_transactional_fixtures = false #activerecord model class with reserved-word table name def test_activerecord_model create_test_fixtures :select, :distinct, :group, :values, :distinct_select x = nil assert_nothing_raised { x = Group.new } x.order = 'x' assert_nothing_raised { x.save } x.order = 'y' assert_nothing_raised { x.save } assert_nothing_raised { Group.find_by_order('y') } assert_nothing_raised { Group.find(1) } end # has_one association with reserved-word table name def test_has_one_associations create_test_fixtures :select, :distinct, :group, :values, :distinct_select v = nil assert_nothing_raised { v = Group.find(1).values } assert_equal 2, v.id end # belongs_to association with reserved-word table name def test_belongs_to_associations create_test_fixtures :select, :distinct, :group, :values, :distinct_select gs = nil assert_nothing_raised { gs = Select.find(2).groups } assert_equal gs.length, 2 assert(gs.collect{|x| x.id}.sort == [2, 3]) end # has_and_belongs_to_many with reserved-word table name def test_has_and_belongs_to_many create_test_fixtures :select, :distinct, :group, :values, :distinct_select s = nil assert_nothing_raised { s = Distinct.find(1).selects } assert_equal s.length, 2 assert(s.collect{|x|x.id}.sort == [1, 2]) end # activerecord model introspection with reserved-word table and column names def test_activerecord_introspection assert_nothing_raised { Group.table_exists? } assert_nothing_raised { Group.columns } end # Calculations def test_calculations_work_with_reserved_words assert_nothing_raised { Group.count } end def test_associations_work_with_reserved_words assert_nothing_raised { Select.all.merge!(:includes => [:groups]).to_a } end #the following functions were added to DRY test cases private # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path def create_test_fixtures(*fixture_names) ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) end # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name def drop_tables_directly(table_names, connection = @connection) table_names.each do |name| connection.execute("DROP TABLE IF EXISTS `#{name}`") end end # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns def create_tables_directly (tables, connection = @connection) tables.each do |table_name, column_properties| connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") end end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb000066400000000000000000000033451266740050600275110ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class Mysql2Adapter class SchemaMigrationsTest < ActiveRecord::TestCase def test_renaming_index_on_foreign_key connection.add_index "engines", "car_id" connection.add_foreign_key :engines, :cars, name: "fk_engines_cars" connection.rename_index("engines", "index_engines_on_car_id", "idx_renamed") assert_equal ["idx_renamed"], connection.indexes("engines").map(&:name) ensure connection.remove_foreign_key :engines, name: "fk_engines_cars" end def test_initializes_schema_migrations_for_encoding_utf8mb4 smtn = ActiveRecord::Migrator.schema_migrations_table_name connection.drop_table(smtn) if connection.table_exists?(smtn) database_name = connection.current_database database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'") original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"] original_collation = database_info["DEFAULT_COLLATION_NAME"] execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4") connection.initialize_schema_migrations_table assert connection.column_exists?(smtn, :version, :string, limit: Mysql2Adapter::MAX_INDEX_LENGTH_FOR_UTF8MB4) ensure execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}") end private def connection @connection ||= ActiveRecord::Base.connection end def execute(sql) connection.execute(sql) end end end end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/schema_test.rb000066400000000000000000000045561266740050600252620ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/comment' module ActiveRecord module ConnectionAdapters class Mysql2SchemaTest < ActiveRecord::TestCase fixtures :posts def setup @connection = ActiveRecord::Base.connection db = Post.connection_pool.spec.config[:database] table = Post.table_name @db_name = db @omgpost = Class.new(ActiveRecord::Base) do self.table_name = "#{db}.#{table}" def self.name; 'Post'; end end end def test_schema assert @omgpost.first end def test_primary_key assert_equal 'id', @omgpost.primary_key end def test_table_exists? name = @omgpost.table_name assert @connection.table_exists?(name), "#{name} table should exist" end def test_table_exists_wrong_schema assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist") end def test_tables_quoting @connection.tables(nil, "foo-bar", nil) flunk rescue => e # assertion for *quoted* database properly assert_match(/database 'foo-bar'/, e.inspect) end def test_dump_indexes index_a_name = 'index_key_tests_on_snack' index_b_name = 'index_key_tests_on_pizza' index_c_name = 'index_key_tests_on_awesome' table = 'key_tests' indexes = @connection.indexes(table).sort_by {|i| i.name} assert_equal 3,indexes.size index_a = indexes.select{|i| i.name == index_a_name}[0] index_b = indexes.select{|i| i.name == index_b_name}[0] index_c = indexes.select{|i| i.name == index_c_name}[0] assert_equal :btree, index_a.using assert_nil index_a.type assert_equal :btree, index_b.using assert_nil index_b.type assert_nil index_c.using assert_equal :fulltext, index_c.type end unless mysql_enforcing_gtid_consistency? def test_drop_temporary_table @connection.transaction do @connection.create_table(:temp_table, temporary: true) # if it doesn't properly say DROP TEMPORARY TABLE, the transaction commit # will complain that no transaction is active @connection.drop_table(:temp_table, temporary: true) end end end end end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/sql_types_test.rb000066400000000000000000000006351266740050600260370ustar00rootroot00000000000000require "cases/helper" class SqlTypesTest < ActiveRecord::TestCase def test_binary_types assert_equal 'varbinary(64)', type_to_sql(:binary, 64) assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095) assert_equal 'blob(4096)', type_to_sql(:binary, 4096) assert_equal 'blob', type_to_sql(:binary) end def type_to_sql(*args) ActiveRecord::Base.connection.type_to_sql(*args) end end rails-4.2.6/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb000066400000000000000000000013721266740050600266700ustar00rootroot00000000000000require "cases/helper" class UnsignedTypeTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class UnsignedType < ActiveRecord::Base end setup do @connection = ActiveRecord::Base.connection @connection.create_table("unsigned_types", force: true) do |t| t.column :unsigned_integer, "int unsigned" end end teardown do @connection.drop_table "unsigned_types" end test "unsigned int max value is in range" do assert expected = UnsignedType.create(unsigned_integer: 4294967295) assert_equal expected, UnsignedType.find_by(unsigned_integer: 4294967295) end test "minus value is out of range" do assert_raise(RangeError) do UnsignedType.create(unsigned_integer: -10) end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/000077500000000000000000000000001266740050600234005ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/adapters/postgresql/active_schema_test.rb000066400000000000000000000052721266740050600275650ustar00rootroot00000000000000require 'cases/helper' class PostgresqlActiveSchemaTest < ActiveRecord::TestCase def setup ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do def execute(sql, name = nil) sql end end end teardown do ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do remove_method :execute end end def test_create_database_with_encoding assert_equal %(CREATE DATABASE "matt" ENCODING = 'utf8'), create_database(:matt) assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1) assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, 'encoding' => :latin1) end def test_create_database_with_collation_and_ctype assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'UTF8' LC_COLLATE = 'ja_JP.UTF8' LC_CTYPE = 'ja_JP.UTF8'), create_database(:aimonetti, :encoding => :"UTF8", :collation => :"ja_JP.UTF8", :ctype => :"ja_JP.UTF8") end def test_add_index # add_index calls index_name_exists? which can't work since execute is stubbed ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.stubs(:index_name_exists?).returns(false) expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active') assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'") expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name")) assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently) %w(gin gist hash btree).each do |type| expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name")) assert_equal expected, add_index(:people, :last_name, using: type) expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name")) assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently) end assert_raise ArgumentError do add_index(:people, :last_name, algorithm: :copy) end expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name")) assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist) expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active') assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist) end private def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/array_test.rb000066400000000000000000000202711266740050600261040ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" class PostgresqlArrayTest < ActiveRecord::TestCase include InTimeZone OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID class PgArray < ActiveRecord::Base self.table_name = 'pg_arrays' end def setup @connection = ActiveRecord::Base.connection enable_extension!('hstore', @connection) @connection.transaction do @connection.create_table('pg_arrays') do |t| t.string 'tags', array: true t.integer 'ratings', array: true t.datetime :datetimes, array: true t.hstore :hstores, array: true end end @column = PgArray.columns_hash['tags'] end teardown do @connection.execute 'drop table if exists pg_arrays' disable_extension!('hstore', @connection) end def test_column assert_equal :string, @column.type assert_equal "character varying", @column.sql_type assert @column.array assert_not @column.number? assert_not @column.binary? ratings_column = PgArray.columns_hash['ratings'] assert_equal :integer, ratings_column.type assert ratings_column.array assert_not ratings_column.number? end def test_default @connection.add_column 'pg_arrays', 'score', :integer, array: true, default: [4, 4, 2] PgArray.reset_column_information assert_equal([4, 4, 2], PgArray.column_defaults['score']) assert_equal([4, 4, 2], PgArray.new.score) ensure PgArray.reset_column_information end def test_default_strings @connection.add_column 'pg_arrays', 'names', :string, array: true, default: ["foo", "bar"] PgArray.reset_column_information assert_equal(["foo", "bar"], PgArray.column_defaults['names']) assert_equal(["foo", "bar"], PgArray.new.names) ensure PgArray.reset_column_information end def test_change_column_with_array @connection.add_column :pg_arrays, :snippets, :string, array: true, default: [] @connection.change_column :pg_arrays, :snippets, :text, array: true, default: [] PgArray.reset_column_information column = PgArray.columns_hash['snippets'] assert_equal :text, column.type assert_equal [], PgArray.column_defaults['snippets'] assert column.array end def test_change_column_cant_make_non_array_column_to_array @connection.add_column :pg_arrays, :a_string, :string assert_raises ActiveRecord::StatementInvalid do @connection.transaction do @connection.change_column :pg_arrays, :a_string, :string, array: true end end end def test_change_column_default_with_array @connection.change_column_default :pg_arrays, :tags, [] PgArray.reset_column_information assert_equal [], PgArray.column_defaults['tags'] end def test_type_cast_array assert_equal(['1', '2', '3'], @column.type_cast_from_database('{1,2,3}')) assert_equal([], @column.type_cast_from_database('{}')) assert_equal([nil], @column.type_cast_from_database('{NULL}')) end def test_type_cast_integers x = PgArray.new(ratings: ['1', '2']) assert_equal([1, 2], x.ratings) x.save! x.reload assert_equal([1, 2], x.ratings) end def test_select_with_strings @connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')" x = PgArray.first assert_equal(['1','2','3'], x.tags) end def test_rewrite_with_strings @connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')" x = PgArray.first x.tags = ['1','2','3','4'] x.save! assert_equal ['1','2','3','4'], x.reload.tags end def test_select_with_integers @connection.execute "insert into pg_arrays (ratings) VALUES ('{1,2,3}')" x = PgArray.first assert_equal([1, 2, 3], x.ratings) end def test_rewrite_with_integers @connection.execute "insert into pg_arrays (ratings) VALUES ('{1,2,3}')" x = PgArray.first x.ratings = [2, '3', 4] x.save! assert_equal [2, 3, 4], x.reload.ratings end def test_multi_dimensional_with_strings assert_cycle(:tags, [[['1'], ['2']], [['2'], ['3']]]) end def test_with_empty_strings assert_cycle(:tags, [ '1', '2', '', '4', '', '5' ]) end def test_with_multi_dimensional_empty_strings assert_cycle(:tags, [[['1', '2'], ['', '4'], ['', '5']]]) end def test_with_arbitrary_whitespace assert_cycle(:tags, [[['1', '2'], [' ', '4'], [' ', '5']]]) end def test_multi_dimensional_with_integers assert_cycle(:ratings, [[[1], [7]], [[8], [10]]]) end def test_strings_with_quotes assert_cycle(:tags, ['this has','some "s that need to be escaped"']) end def test_strings_with_commas assert_cycle(:tags, ['this,has','many,values']) end def test_strings_with_array_delimiters assert_cycle(:tags, ['{','}']) end def test_strings_with_null_strings assert_cycle(:tags, ['NULL','NULL']) end def test_contains_nils assert_cycle(:tags, ['1',nil,nil]) end def test_insert_fixture tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"] @connection.insert_fixture({"tags" => tag_values}, "pg_arrays" ) assert_equal(PgArray.last.tags, tag_values) end def test_attribute_for_inspect_for_array_field record = PgArray.new { |a| a.ratings = (1..11).to_a } assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]", record.attribute_for_inspect(:ratings)) end def test_escaping unknown = 'foo\\",bar,baz,\\' tags = ["hello_#{unknown}"] ar = PgArray.create!(tags: tags) ar.reload assert_equal tags, ar.tags end def test_string_quoting_rules_match_pg_behavior tags = ["", "one{", "two}", %(three"), "four\\", "five ", "six\t", "seven\n", "eight,", "nine", "ten\r", "NULL"] x = PgArray.create!(tags: tags) x.reload assert_equal x.tags_before_type_cast, PgArray.columns_hash['tags'].type_cast_for_database(tags) end def test_quoting_non_standard_delimiters strings = ["hello,", "world;"] comma_delim = OID::Array.new(ActiveRecord::Type::String.new, ',') semicolon_delim = OID::Array.new(ActiveRecord::Type::String.new, ';') assert_equal %({"hello,",world;}), comma_delim.type_cast_for_database(strings) assert_equal %({hello,;"world;"}), semicolon_delim.type_cast_for_database(strings) end def test_mutate_array x = PgArray.create!(tags: %w(one two)) x.tags << "three" x.save! x.reload assert_equal %w(one two three), x.tags assert_not x.changed? end def test_mutate_value_in_array x = PgArray.create!(hstores: [{ a: 'a' }, { b: 'b' }]) x.hstores.first['a'] = 'c' x.save! x.reload assert_equal [{ 'a' => 'c' }, { 'b' => 'b' }], x.hstores assert_not x.changed? end def test_datetime_with_timezone_awareness tz = "Pacific Time (US & Canada)" in_time_zone tz do PgArray.reset_column_information time_string = Time.current.to_s time = Time.zone.parse(time_string) record = PgArray.new(datetimes: [time_string]) assert_equal [time], record.datetimes assert_equal ActiveSupport::TimeZone[tz], record.datetimes.first.time_zone record.save! record.reload assert_equal [time], record.datetimes assert_equal ActiveSupport::TimeZone[tz], record.datetimes.first.time_zone end end def test_assigning_non_array_value record = PgArray.new(tags: "not-an-array") assert_equal [], record.tags assert_equal "not-an-array", record.tags_before_type_cast assert record.save assert_equal record.tags, record.reload.tags end def test_assigning_empty_string record = PgArray.new(tags: "") assert_equal [], record.tags assert_equal "", record.tags_before_type_cast assert record.save assert_equal record.tags, record.reload.tags end def test_assigning_valid_pg_array_literal record = PgArray.new(tags: "{1,2,3}") assert_equal ["1", "2", "3"], record.tags assert_equal "{1,2,3}", record.tags_before_type_cast assert record.save assert_equal record.tags, record.reload.tags end private def assert_cycle field, array # test creation x = PgArray.create!(field => array) x.reload assert_equal(array, x.public_send(field)) # test updating x = PgArray.create!(field => []) x.public_send("#{field}=", array) x.save! x.reload assert_equal(array, x.public_send(field)) end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/bit_string_test.rb000066400000000000000000000047601266740050600271370ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "cases/helper" require 'support/connection_helper' require 'support/schema_dumping_helper' class PostgresqlBitStringTest < ActiveRecord::TestCase include ConnectionHelper include SchemaDumpingHelper class PostgresqlBitString < ActiveRecord::Base; end def setup @connection = ActiveRecord::Base.connection @connection.create_table('postgresql_bit_strings', :force => true) do |t| t.bit :a_bit, default: "00000011", limit: 8 t.bit_varying :a_bit_varying, default: "0011", limit: 4 t.bit :another_bit t.bit_varying :another_bit_varying end end def teardown return unless @connection @connection.execute 'DROP TABLE IF EXISTS postgresql_bit_strings' end def test_bit_string_column column = PostgresqlBitString.columns_hash["a_bit"] assert_equal :bit, column.type assert_equal "bit(8)", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_bit_string_varying_column column = PostgresqlBitString.columns_hash["a_bit_varying"] assert_equal :bit_varying, column.type assert_equal "bit varying(4)", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_default assert_equal "00000011", PostgresqlBitString.column_defaults['a_bit'] assert_equal "00000011", PostgresqlBitString.new.a_bit assert_equal "0011", PostgresqlBitString.column_defaults['a_bit_varying'] assert_equal "0011", PostgresqlBitString.new.a_bit_varying end def test_schema_dumping output = dump_table_schema("postgresql_bit_strings") assert_match %r{t\.bit\s+"a_bit",\s+limit: 8,\s+default: "00000011"$}, output assert_match %r{t\.bit_varying\s+"a_bit_varying",\s+limit: 4,\s+default: "0011"$}, output end def test_assigning_invalid_hex_string_raises_exception assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" } assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "FF" } end def test_roundtrip PostgresqlBitString.create! a_bit: "00001010", a_bit_varying: "0101" record = PostgresqlBitString.first assert_equal "00001010", record.a_bit assert_equal "0101", record.a_bit_varying record.a_bit = "11111111" record.a_bit_varying = "0xF" record.save! assert record.reload assert_equal "11111111", record.a_bit assert_equal "1111", record.a_bit_varying end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/bytea_test.rb000066400000000000000000000065111266740050600260730ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" class PostgresqlByteaTest < ActiveRecord::TestCase class ByteaDataType < ActiveRecord::Base self.table_name = 'bytea_data_type' end def setup @connection = ActiveRecord::Base.connection begin @connection.transaction do @connection.create_table('bytea_data_type') do |t| t.binary 'payload' t.binary 'serialized' end end end @column = ByteaDataType.columns_hash['payload'] end teardown do @connection.execute 'drop table if exists bytea_data_type' end def test_column assert @column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn) assert_equal :binary, @column.type end def test_binary_columns_are_limitless_the_upper_limit_is_one_GB assert_equal 'bytea', @connection.type_to_sql(:binary, 100_000) assert_raise ActiveRecord::ActiveRecordError do @connection.type_to_sql :binary, 4294967295 end end def test_type_cast_binary_converts_the_encoding assert @column data = "\u001F\x8B" assert_equal('UTF-8', data.encoding.name) assert_equal('ASCII-8BIT', @column.type_cast_from_database(data).encoding.name) end def test_type_cast_binary_value data = "\u001F\x8B".force_encoding("BINARY") assert_equal(data, @column.type_cast_from_database(data)) end def test_type_case_nil assert_equal(nil, @column.type_cast_from_database(nil)) end def test_read_value data = "\u001F" @connection.execute "insert into bytea_data_type (payload) VALUES ('#{data}')" record = ByteaDataType.first assert_equal(data, record.payload) record.delete end def test_read_nil_value @connection.execute "insert into bytea_data_type (payload) VALUES (null)" record = ByteaDataType.first assert_equal(nil, record.payload) record.delete end def test_write_value data = "\u001F" record = ByteaDataType.create(payload: data) assert_not record.new_record? assert_equal(data, record.payload) end def test_via_to_sql data = "'\u001F\\" ByteaDataType.create(payload: data) sql = ByteaDataType.where(payload: data).select(:payload).to_sql result = @connection.query(sql) assert_equal([[data]], result) end def test_via_to_sql_with_complicating_connection Thread.new do other_conn = ActiveRecord::Base.connection other_conn.execute('SET standard_conforming_strings = off') end.join test_via_to_sql end def test_write_binary data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) assert(data.size > 1) record = ByteaDataType.create(payload: data) assert_not record.new_record? assert_equal(data, record.payload) assert_equal(data, ByteaDataType.where(id: record.id).first.payload) end def test_write_nil record = ByteaDataType.create(payload: nil) assert_not record.new_record? assert_equal(nil, record.payload) assert_equal(nil, ByteaDataType.where(id: record.id).first.payload) end class Serializer def load(str); str; end def dump(str); str; end end def test_serialize klass = Class.new(ByteaDataType) { serialize :serialized, Serializer.new } obj = klass.new obj.serialized = "hello world" obj.save! obj.reload assert_equal "hello world", obj.serialized end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/change_schema_test.rb000066400000000000000000000016111266740050600275300ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class Migration class PGChangeSchemaTest < ActiveRecord::TestCase attr_reader :connection def setup super @connection = ActiveRecord::Base.connection connection.create_table(:strings) do |t| t.string :somedate end end def teardown connection.drop_table :strings end def test_change_string_to_date connection.change_column :strings, :somedate, :timestamp, using: 'CAST("somedate" AS timestamp)' assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type end def test_change_type_with_symbol connection.change_column :strings, :somedate, :timestamp, cast_as: :timestamp assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type end end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/citext_test.rb000066400000000000000000000034651266740050600262740ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' if ActiveRecord::Base.connection.supports_extensions? class PostgresqlCitextTest < ActiveRecord::TestCase class Citext < ActiveRecord::Base self.table_name = 'citexts' end def setup @connection = ActiveRecord::Base.connection enable_extension!('citext', @connection) @connection.create_table('citexts') do |t| t.citext 'cival' end end teardown do @connection.execute 'DROP TABLE IF EXISTS citexts;' disable_extension!('citext', @connection) end def test_citext_enabled assert @connection.extension_enabled?('citext') end def test_column column = Citext.columns_hash['cival'] assert_equal :citext, column.type assert_equal 'citext', column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_change_table_supports_json @connection.transaction do @connection.change_table('citexts') do |t| t.citext 'username' end Citext.reset_column_information column = Citext.columns_hash['username'] assert_equal :citext, column.type raise ActiveRecord::Rollback # reset the schema change end ensure Citext.reset_column_information end def test_write x = Citext.new(cival: 'Some CI Text') x.save! citext = Citext.first assert_equal "Some CI Text", citext.cival citext.cival = "Some NEW CI Text" citext.save! assert_equal "Some NEW CI Text", citext.reload.cival end def test_select_case_insensitive @connection.execute "insert into citexts (cival) values('Cased Text')" x = Citext.where(cival: 'cased text').first assert_equal 'Cased Text', x.cival end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/composite_test.rb000066400000000000000000000071101266740050600267650ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "cases/helper" require 'support/connection_helper' module PostgresqlCompositeBehavior include ConnectionHelper class PostgresqlComposite < ActiveRecord::Base self.table_name = "postgresql_composites" end def setup super @connection = ActiveRecord::Base.connection @connection.transaction do @connection.execute <<-SQL CREATE TYPE full_address AS ( city VARCHAR(90), street VARCHAR(90) ); SQL @connection.create_table('postgresql_composites') do |t| t.column :address, :full_address end end end def teardown super @connection.execute 'DROP TABLE IF EXISTS postgresql_composites' @connection.execute 'DROP TYPE IF EXISTS full_address' reset_connection PostgresqlComposite.reset_column_information end end # Composites are mapped to `OID::Identity` by default. The user is informed by a warning like: # "unknown OID 5653508: failed to recognize type of 'address'. It will be treated as String." # To take full advantage of composite types, we suggest you register your own +OID::Type+. # See PostgresqlCompositeWithCustomOIDTest class PostgresqlCompositeTest < ActiveRecord::TestCase include PostgresqlCompositeBehavior def test_column ensure_warning_is_issued column = PostgresqlComposite.columns_hash["address"] assert_nil column.type assert_equal "full_address", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_composite_mapping ensure_warning_is_issued @connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));" composite = PostgresqlComposite.first assert_equal "(Paris,Champs-Élysées)", composite.address composite.address = "(Paris,Rue Basse)" composite.save! assert_equal '(Paris,"Rue Basse")', composite.reload.address end private def ensure_warning_is_issued warning = capture(:stderr) do PostgresqlComposite.columns_hash end assert_match(/unknown OID \d+: failed to recognize type of 'address'\. It will be treated as String\./, warning) end end class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase include PostgresqlCompositeBehavior class FullAddressType < ActiveRecord::Type::Value def type; :full_address end def type_cast_from_database(value) if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/ FullAddress.new($1, $2) end end def type_cast_from_user(value) value end def type_cast_for_database(value) return if value.nil? "(#{value.city},#{value.street})" end end FullAddress = Struct.new(:city, :street) def setup super @connection.type_map.register_type "full_address", FullAddressType.new end def test_column column = PostgresqlComposite.columns_hash["address"] assert_equal :full_address, column.type assert_equal "full_address", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_composite_mapping @connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));" composite = PostgresqlComposite.first assert_equal "Paris", composite.address.city assert_equal "Champs-Élysées", composite.address.street composite.address = FullAddress.new("Paris", "Rue Basse") composite.save! assert_equal 'Paris', composite.reload.address.city assert_equal 'Rue Basse', composite.reload.address.street end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/connection_test.rb000066400000000000000000000162701266740050600271310ustar00rootroot00000000000000require "cases/helper" require 'support/connection_helper' module ActiveRecord class PostgresqlConnectionTest < ActiveRecord::TestCase include ConnectionHelper class NonExistentTable < ActiveRecord::Base end fixtures :comments def setup super @subscriber = SQLSubscriber.new @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) @connection = ActiveRecord::Base.connection end def teardown ActiveSupport::Notifications.unsubscribe(@subscription) super end def test_truncate count = ActiveRecord::Base.connection.execute("select count(*) from comments").first['count'].to_i assert_operator count, :>, 0 ActiveRecord::Base.connection.truncate("comments") count = ActiveRecord::Base.connection.execute("select count(*) from comments").first['count'].to_i assert_equal 0, count end def test_encoding assert_not_nil @connection.encoding end def test_collation assert_not_nil @connection.collation end def test_ctype assert_not_nil @connection.ctype end def test_default_client_min_messages assert_equal "warning", @connection.client_min_messages end # Ensure, we can set connection params using the example of Generic # Query Optimizer (geqo). It is 'on' per default. def test_connection_options params = ActiveRecord::Base.connection_config.dup params[:options] = "-c geqo=off" NonExistentTable.establish_connection(params) # Verify the connection param has been applied. expect = NonExistentTable.connection.query('show geqo').first.first assert_equal 'off', expect end def test_reset @connection.query('ROLLBACK') @connection.query('SET geqo TO off') # Verify the setting has been applied. expect = @connection.query('show geqo').first.first assert_equal 'off', expect @connection.reset! # Verify the setting has been cleared. expect = @connection.query('show geqo').first.first assert_equal 'on', expect end def test_reset_with_transaction @connection.query('ROLLBACK') @connection.query('SET geqo TO off') # Verify the setting has been applied. expect = @connection.query('show geqo').first.first assert_equal 'off', expect @connection.query('BEGIN') @connection.reset! # Verify the setting has been cleared. expect = @connection.query('show geqo').first.first assert_equal 'on', expect end def test_tables_logs_name @connection.tables('hello') assert_equal 'SCHEMA', @subscriber.logged[0][1] end def test_indexes_logs_name @connection.indexes('items', 'hello') assert_equal 'SCHEMA', @subscriber.logged[0][1] end def test_table_exists_logs_name @connection.table_exists?('items') assert_equal 'SCHEMA', @subscriber.logged[0][1] end def test_table_alias_length_logs_name @connection.instance_variable_set("@table_alias_length", nil) @connection.table_alias_length assert_equal 'SCHEMA', @subscriber.logged[0][1] end def test_current_database_logs_name @connection.current_database assert_equal 'SCHEMA', @subscriber.logged[0][1] end def test_encoding_logs_name @connection.encoding assert_equal 'SCHEMA', @subscriber.logged[0][1] end def test_schema_names_logs_name @connection.schema_names assert_equal 'SCHEMA', @subscriber.logged[0][1] end def test_statement_key_is_logged bindval = 1 @connection.exec_query('SELECT $1::integer', 'SQL', [[nil, bindval]]) name = @subscriber.payloads.last[:statement_name] assert name res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})") plan = res.column_types['QUERY PLAN'].type_cast_from_database res.rows.first.first assert_operator plan.length, :>, 0 end # Must have PostgreSQL >= 9.2, or with_manual_interventions set to # true for this test to run. # # When prompted, restart the PostgreSQL server with the # "-m fast" option or kill the individual connection assuming # you know the incantation to do that. # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ... # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast" def test_reconnection_after_actual_disconnection_with_verify original_connection_pid = @connection.query('select pg_backend_pid()') # Sanity check. assert @connection.active? if @connection.send(:postgresql_version) >= 90200 secondary_connection = ActiveRecord::Base.connection_pool.checkout secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})") ActiveRecord::Base.connection_pool.checkin(secondary_connection) elsif ARTest.config['with_manual_interventions'] puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' + 'server with the "-m fast" option) and then press enter.' $stdin.gets else # We're not capable of terminating the backend ourselves, and # we're not allowed to seek assistance; bail out without # actually testing anything. return end @connection.verify! assert @connection.active? # If we get no exception here, then either we re-connected successfully, or # we never actually got disconnected. new_connection_pid = @connection.query('select pg_backend_pid()') assert_not_equal original_connection_pid, new_connection_pid, "umm -- looks like you didn't break the connection, because we're still " + "successfully querying with the same connection pid." # Repair all fixture connections so other tests won't break. @fixture_connections.each do |c| c.verify! end end def test_set_session_variable_true run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => true}})) set_true = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN" assert_equal set_true.rows, [["on"]] end end def test_set_session_variable_false run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => false}})) set_false = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN" assert_equal set_false.rows, [["off"]] end end def test_set_session_variable_nil run_without_connection do |orig_connection| # This should be a no-op that does not raise an error ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => nil}})) end end def test_set_session_variable_default run_without_connection do |orig_connection| # This should execute a query that does not raise an error ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}})) end end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/datatype_test.rb000066400000000000000000000074331266740050600266060ustar00rootroot00000000000000require "cases/helper" require 'support/ddl_helper' class PostgresqlNumber < ActiveRecord::Base end class PostgresqlTime < ActiveRecord::Base end class PostgresqlOid < ActiveRecord::Base end class PostgresqlLtree < ActiveRecord::Base end class PostgresqlDataTypeTest < ActiveRecord::TestCase self.use_transactional_fixtures = false def setup @connection = ActiveRecord::Base.connection @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (1, 123.456, 123456.789)") @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (2, '-Infinity', 'Infinity')") @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (3, 123.456, 'NaN')") @first_number = PostgresqlNumber.find(1) @second_number = PostgresqlNumber.find(2) @third_number = PostgresqlNumber.find(3) @connection.execute("INSERT INTO postgresql_times (id, time_interval, scaled_time_interval) VALUES (1, '1 year 2 days ago', '3 weeks ago')") @first_time = PostgresqlTime.find(1) @connection.execute("INSERT INTO postgresql_oids (id, obj_id) VALUES (1, 1234)") @first_oid = PostgresqlOid.find(1) end teardown do [PostgresqlNumber, PostgresqlTime, PostgresqlOid].each(&:delete_all) end def test_data_type_of_number_types assert_equal :float, @first_number.column_for_attribute(:single).type assert_equal :float, @first_number.column_for_attribute(:double).type end def test_data_type_of_time_types assert_equal :string, @first_time.column_for_attribute(:time_interval).type assert_equal :string, @first_time.column_for_attribute(:scaled_time_interval).type end def test_data_type_of_oid_types assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type end def test_number_values assert_equal 123.456, @first_number.single assert_equal 123456.789, @first_number.double assert_equal(-::Float::INFINITY, @second_number.single) assert_equal ::Float::INFINITY, @second_number.double assert_same ::Float::NAN, @third_number.double end def test_time_values assert_equal '-1 years -2 days', @first_time.time_interval assert_equal '-21 days', @first_time.scaled_time_interval end def test_oid_values assert_equal 1234, @first_oid.obj_id end def test_update_number new_single = 789.012 new_double = 789012.345 @first_number.single = new_single @first_number.double = new_double assert @first_number.save assert @first_number.reload assert_equal new_single, @first_number.single assert_equal new_double, @first_number.double end def test_update_time @first_time.time_interval = '2 years 3 minutes' assert @first_time.save assert @first_time.reload assert_equal '2 years 00:03:00', @first_time.time_interval end def test_update_oid new_value = 567890 @first_oid.obj_id = new_value assert @first_oid.save assert @first_oid.reload assert_equal new_value, @first_oid.obj_id end def test_text_columns_are_limitless_the_upper_limit_is_one_GB assert_equal 'text', @connection.type_to_sql(:text, 100_000) assert_raise ActiveRecord::ActiveRecordError do @connection.type_to_sql :text, 4294967295 end end end class PostgresqlInternalDataTypeTest < ActiveRecord::TestCase include DdlHelper setup do @connection = ActiveRecord::Base.connection end def test_name_column_type with_example_table @connection, 'ex', 'data name' do column = @connection.columns('ex').find { |col| col.name == 'data' } assert_equal :string, column.type end end def test_char_column_type with_example_table @connection, 'ex', 'data "char"' do column = @connection.columns('ex').find { |col| col.name == 'data' } assert_equal :string, column.type end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/domain_test.rb000066400000000000000000000023051266740050600262330ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "cases/helper" require 'support/connection_helper' class PostgresqlDomainTest < ActiveRecord::TestCase include ConnectionHelper class PostgresqlDomain < ActiveRecord::Base self.table_name = "postgresql_domains" end def setup @connection = ActiveRecord::Base.connection @connection.transaction do @connection.execute "CREATE DOMAIN custom_money as numeric(8,2)" @connection.create_table('postgresql_domains') do |t| t.column :price, :custom_money end end end teardown do @connection.execute 'DROP TABLE IF EXISTS postgresql_domains' @connection.execute 'DROP DOMAIN IF EXISTS custom_money' reset_connection end def test_column column = PostgresqlDomain.columns_hash["price"] assert_equal :decimal, column.type assert_equal "custom_money", column.sql_type assert column.number? assert_not column.binary? assert_not column.array end def test_domain_acts_like_basetype PostgresqlDomain.create price: "" record = PostgresqlDomain.first assert_nil record.price record.price = "34.15" record.save! assert_equal BigDecimal.new("34.15"), record.reload.price end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/enum_test.rb000066400000000000000000000045041266740050600257330ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "cases/helper" require 'support/connection_helper' class PostgresqlEnumTest < ActiveRecord::TestCase include ConnectionHelper class PostgresqlEnum < ActiveRecord::Base self.table_name = "postgresql_enums" end def setup @connection = ActiveRecord::Base.connection @connection.transaction do @connection.execute <<-SQL CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); SQL @connection.create_table('postgresql_enums') do |t| t.column :current_mood, :mood end end end teardown do @connection.execute 'DROP TABLE IF EXISTS postgresql_enums' @connection.execute 'DROP TYPE IF EXISTS mood' reset_connection end def test_column column = PostgresqlEnum.columns_hash["current_mood"] assert_equal :enum, column.type assert_equal "mood", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_enum_defaults @connection.add_column 'postgresql_enums', 'good_mood', :mood, default: 'happy' PostgresqlEnum.reset_column_information assert_equal "happy", PostgresqlEnum.column_defaults['good_mood'] assert_equal "happy", PostgresqlEnum.new.good_mood ensure PostgresqlEnum.reset_column_information end def test_enum_mapping @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" enum = PostgresqlEnum.first assert_equal "sad", enum.current_mood enum.current_mood = "happy" enum.save! assert_equal "happy", enum.reload.current_mood end def test_invalid_enum_update @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" enum = PostgresqlEnum.first enum.current_mood = "angry" assert_raise ActiveRecord::StatementInvalid do enum.save end end def test_no_oid_warning @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" stderr_output = capture(:stderr) { PostgresqlEnum.first } assert stderr_output.blank? end def test_enum_type_cast enum = PostgresqlEnum.new enum.current_mood = :happy assert_equal "happy", enum.current_mood end def test_assigning_enum_to_nil model = PostgresqlEnum.new(current_mood: nil) assert_nil model.current_mood assert model.save assert_nil model.reload.current_mood end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/explain_test.rb000066400000000000000000000016711266740050600264310ustar00rootroot00000000000000require "cases/helper" require 'models/developer' require 'models/computer' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapter class ExplainTest < ActiveRecord::TestCase fixtures :developers def test_explain_for_one_query explain = Developer.where(:id => 1).explain assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain assert_match %(QUERY PLAN), explain end def test_explain_with_eager_loading explain = Developer.where(:id => 1).includes(:audit_logs).explain assert_match %(QUERY PLAN), explain assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain end end end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb000066400000000000000000000036651266740050600310630ustar00rootroot00000000000000require "cases/helper" class PostgresqlExtensionMigrationTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class EnableHstore < ActiveRecord::Migration def change enable_extension "hstore" end end class DisableHstore < ActiveRecord::Migration def change disable_extension "hstore" end end def setup super @connection = ActiveRecord::Base.connection unless @connection.supports_extensions? return skip("no extension support") end @old_schema_migration_tabel_name = ActiveRecord::SchemaMigration.table_name @old_tabel_name_prefix = ActiveRecord::Base.table_name_prefix @old_tabel_name_suffix = ActiveRecord::Base.table_name_suffix ActiveRecord::Base.table_name_prefix = "p_" ActiveRecord::Base.table_name_suffix = "_s" ActiveRecord::SchemaMigration.delete_all rescue nil ActiveRecord::SchemaMigration.table_name = "p_schema_migrations_s" ActiveRecord::Migration.verbose = false end def teardown ActiveRecord::Base.table_name_prefix = @old_tabel_name_prefix ActiveRecord::Base.table_name_suffix = @old_tabel_name_suffix ActiveRecord::SchemaMigration.delete_all rescue nil ActiveRecord::Migration.verbose = true ActiveRecord::SchemaMigration.table_name = @old_schema_migration_tabel_name super end def test_enable_extension_migration_ignores_prefix_and_suffix @connection.disable_extension("hstore") migrations = [EnableHstore.new(nil, 1)] ActiveRecord::Migrator.new(:up, migrations).migrate assert @connection.extension_enabled?("hstore"), "extension hstore should be enabled" end def test_disable_extension_migration_ignores_prefix_and_suffix @connection.enable_extension("hstore") migrations = [DisableHstore.new(nil, 1)] ActiveRecord::Migrator.new(:up, migrations).migrate assert_not @connection.extension_enabled?("hstore"), "extension hstore should not be enabled" end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/full_text_test.rb000066400000000000000000000014051266740050600267720ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" class PostgresqlFullTextTest < ActiveRecord::TestCase class PostgresqlTsvector < ActiveRecord::Base; end def test_tsvector_column column = PostgresqlTsvector.columns_hash["text_vector"] assert_equal :tsvector, column.type assert_equal "tsvector", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_update_tsvector PostgresqlTsvector.create text_vector: "'text' 'vector'" tsvector = PostgresqlTsvector.first assert_equal "'text' 'vector'", tsvector.text_vector tsvector.text_vector = "'new' 'text' 'vector'" tsvector.save! assert tsvector.reload assert_equal "'new' 'text' 'vector'", tsvector.text_vector end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/geometric_test.rb000066400000000000000000000035071266740050600267470ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "cases/helper" require 'support/connection_helper' require 'support/schema_dumping_helper' class PostgresqlPointTest < ActiveRecord::TestCase include ConnectionHelper include SchemaDumpingHelper class PostgresqlPoint < ActiveRecord::Base; end def setup @connection = ActiveRecord::Base.connection @connection.transaction do @connection.create_table('postgresql_points') do |t| t.point :x t.point :y, default: [12.2, 13.3] t.point :z, default: "(14.4,15.5)" end end end teardown do @connection.execute 'DROP TABLE IF EXISTS postgresql_points' end def test_column column = PostgresqlPoint.columns_hash["x"] assert_equal :point, column.type assert_equal "point", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_default assert_equal [12.2, 13.3], PostgresqlPoint.column_defaults['y'] assert_equal [12.2, 13.3], PostgresqlPoint.new.y assert_equal [14.4, 15.5], PostgresqlPoint.column_defaults['z'] assert_equal [14.4, 15.5], PostgresqlPoint.new.z end def test_schema_dumping output = dump_table_schema("postgresql_points") assert_match %r{t\.point\s+"x"$}, output assert_match %r{t\.point\s+"y",\s+default: \[12\.2, 13\.3\]$}, output assert_match %r{t\.point\s+"z",\s+default: \[14\.4, 15\.5\]$}, output end def test_roundtrip PostgresqlPoint.create! x: [10, 25.2] record = PostgresqlPoint.first assert_equal [10, 25.2], record.x record.x = [1.1, 2.2] record.save! assert record.reload assert_equal [1.1, 2.2], record.x end def test_mutation p = PostgresqlPoint.create! x: [10, 20] p.x[1] = 25 p.save! p.reload assert_equal [10.0, 25.0], p.x assert_not p.changed? end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/hstore_test.rb000066400000000000000000000230551266740050600262750ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" if ActiveRecord::Base.connection.supports_extensions? class PostgresqlHstoreTest < ActiveRecord::TestCase class Hstore < ActiveRecord::Base self.table_name = 'hstores' store_accessor :settings, :language, :timezone end def setup @connection = ActiveRecord::Base.connection unless @connection.extension_enabled?('hstore') @connection.enable_extension 'hstore' @connection.commit_db_transaction end @connection.reconnect! @connection.transaction do @connection.create_table('hstores') do |t| t.hstore 'tags', :default => '' t.hstore 'payload', array: true t.hstore 'settings' end end @column = Hstore.columns_hash['tags'] end teardown do @connection.execute 'drop table if exists hstores' end def test_hstore_included_in_extensions assert @connection.respond_to?(:extensions), "connection should have a list of extensions" assert @connection.extensions.include?('hstore'), "extension list should include hstore" end def test_disable_enable_hstore assert @connection.extension_enabled?('hstore') @connection.disable_extension 'hstore' assert_not @connection.extension_enabled?('hstore') @connection.enable_extension 'hstore' assert @connection.extension_enabled?('hstore') ensure # Restore column(s) dropped by `drop extension hstore cascade;` load_schema end def test_column assert_equal :hstore, @column.type assert_equal "hstore", @column.sql_type assert_not @column.number? assert_not @column.binary? assert_not @column.array end def test_default @connection.add_column 'hstores', 'permissions', :hstore, default: '"users"=>"read", "articles"=>"write"' Hstore.reset_column_information assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.column_defaults['permissions']) assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.new.permissions) ensure Hstore.reset_column_information end def test_change_table_supports_hstore @connection.transaction do @connection.change_table('hstores') do |t| t.hstore 'users', default: '' end Hstore.reset_column_information column = Hstore.columns_hash['users'] assert_equal :hstore, column.type raise ActiveRecord::Rollback # reset the schema change end ensure Hstore.reset_column_information end def test_hstore_migration hstore_migration = Class.new(ActiveRecord::Migration) do def change change_table("hstores") do |t| t.hstore :keys end end end hstore_migration.new.suppress_messages do hstore_migration.migrate(:up) assert_includes @connection.columns(:hstores).map(&:name), "keys" hstore_migration.migrate(:down) assert_not_includes @connection.columns(:hstores).map(&:name), "keys" end end def test_cast_value_on_write x = Hstore.new tags: {"bool" => true, "number" => 5} assert_equal({"bool" => true, "number" => 5}, x.tags_before_type_cast) assert_equal({"bool" => "true", "number" => "5"}, x.tags) x.save assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags) end def test_type_cast_hstore assert_equal({'1' => '2'}, @column.type_cast_from_database("\"1\"=>\"2\"")) assert_equal({}, @column.type_cast_from_database("")) assert_equal({'key'=>nil}, @column.type_cast_from_database('key => NULL')) assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b"))) end def test_with_store_accessors x = Hstore.new(language: "fr", timezone: "GMT") assert_equal "fr", x.language assert_equal "GMT", x.timezone x.save! x = Hstore.first assert_equal "fr", x.language assert_equal "GMT", x.timezone x.language = "de" x.save! x = Hstore.first assert_equal "de", x.language assert_equal "GMT", x.timezone end def test_duplication_with_store_accessors x = Hstore.new(language: "fr", timezone: "GMT") assert_equal "fr", x.language assert_equal "GMT", x.timezone y = x.dup assert_equal "fr", y.language assert_equal "GMT", y.timezone end def test_yaml_round_trip_with_store_accessors x = Hstore.new(language: "fr", timezone: "GMT") assert_equal "fr", x.language assert_equal "GMT", x.timezone y = YAML.load(YAML.dump(x)) assert_equal "fr", y.language assert_equal "GMT", y.timezone end def test_changes_in_place hstore = Hstore.create!(settings: { 'one' => 'two' }) hstore.settings['three'] = 'four' hstore.save! hstore.reload assert_equal 'four', hstore.settings['three'] assert_not hstore.changed? end def test_gen1 assert_equal(%q(" "=>""), @column.cast_type.type_cast_for_database({' '=>''})) end def test_gen2 assert_equal(%q(","=>""), @column.cast_type.type_cast_for_database({','=>''})) end def test_gen3 assert_equal(%q("="=>""), @column.cast_type.type_cast_for_database({'='=>''})) end def test_gen4 assert_equal(%q(">"=>""), @column.cast_type.type_cast_for_database({'>'=>''})) end def test_parse1 assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c')) end def test_parse2 assert_equal({" " => " "}, @column.type_cast_from_database("\\ =>\\ ")) end def test_parse3 assert_equal({"=" => ">"}, @column.type_cast_from_database("==>>")) end def test_parse4 assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('\=a=>q=w')) end def test_parse5 assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('"=a"=>q\=w')) end def test_parse6 assert_equal({"\"a"=>"q>w"}, @column.type_cast_from_database('"\"a"=>q>w')) end def test_parse7 assert_equal({"\"a"=>"q\"w"}, @column.type_cast_from_database('\"a=>q"w')) end def test_rewrite @connection.execute "insert into hstores (tags) VALUES ('1=>2')" x = Hstore.first x.tags = { '"a\'' => 'b' } assert x.save! end def test_select @connection.execute "insert into hstores (tags) VALUES ('1=>2')" x = Hstore.first assert_equal({'1' => '2'}, x.tags) end def test_array_cycle assert_array_cycle([{"AA" => "BB", "CC" => "DD"}, {"AA" => nil}]) end def test_array_strings_with_quotes assert_array_cycle([{'this has' => 'some "s that need to be escaped"'}]) end def test_array_strings_with_commas assert_array_cycle([{'this,has' => 'many,values'}]) end def test_array_strings_with_array_delimiters assert_array_cycle(['{' => '}']) end def test_array_strings_with_null_strings assert_array_cycle([{'NULL' => 'NULL'}]) end def test_contains_nils assert_array_cycle([{'NULL' => nil}]) end def test_select_multikey @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" x = Hstore.first assert_equal({'1' => '2', '2' => '3'}, x.tags) end def test_create assert_cycle('a' => 'b', '1' => '2') end def test_nil assert_cycle('a' => nil) end def test_quotes assert_cycle('a' => 'b"ar', '1"foo' => '2') end def test_whitespace assert_cycle('a b' => 'b ar', '1"foo' => '2') end def test_backslash assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2') end def test_comma assert_cycle('a, b' => 'bar', '1"foo' => '2') end def test_arrow assert_cycle('a=>b' => 'bar', '1"foo' => '2') end def test_quoting_special_characters assert_cycle('ca' => 'cà', 'ac' => 'àc') end def test_multiline assert_cycle("a\nb" => "c\nd") end class TagCollection def initialize(hash); @hash = hash end def to_hash; @hash end def self.load(hash); new(hash) end def self.dump(object); object.to_hash end end class HstoreWithSerialize < Hstore serialize :tags, TagCollection end def test_hstore_with_serialized_attributes HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"}) record = HstoreWithSerialize.first assert_instance_of TagCollection, record.tags assert_equal({"one" => "two"}, record.tags.to_hash) record.tags = TagCollection.new("three" => "four") record.save! assert_equal({"three" => "four"}, HstoreWithSerialize.first.tags.to_hash) end def test_clone_hstore_with_serialized_attributes HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"}) record = HstoreWithSerialize.first dupe = record.dup assert_equal({"one" => "two"}, dupe.tags.to_hash) end private def assert_array_cycle(array) # test creation x = Hstore.create!(payload: array) x.reload assert_equal(array, x.payload) # test updating x = Hstore.create!(payload: []) x.payload = array x.save! x.reload assert_equal(array, x.payload) end def assert_cycle(hash) # test creation x = Hstore.create!(:tags => hash) x.reload assert_equal(hash, x.tags) # test updating x = Hstore.create!(:tags => {}) x.tags = hash x.save! x.reload assert_equal(hash, x.tags) end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/infinity_test.rb000066400000000000000000000034561266740050600266250ustar00rootroot00000000000000require "cases/helper" class PostgresqlInfinityTest < ActiveRecord::TestCase include InTimeZone class PostgresqlInfinity < ActiveRecord::Base end setup do @connection = ActiveRecord::Base.connection @connection.create_table(:postgresql_infinities) do |t| t.float :float t.datetime :datetime end end teardown do @connection.execute("DROP TABLE IF EXISTS postgresql_infinities") end test "type casting infinity on a float column" do record = PostgresqlInfinity.create!(float: Float::INFINITY) record.reload assert_equal Float::INFINITY, record.float end test "update_all with infinity on a float column" do record = PostgresqlInfinity.create! PostgresqlInfinity.update_all(float: Float::INFINITY) record.reload assert_equal Float::INFINITY, record.float end test "type casting infinity on a datetime column" do record = PostgresqlInfinity.create!(datetime: Float::INFINITY) record.reload assert_equal Float::INFINITY, record.datetime end test "update_all with infinity on a datetime column" do record = PostgresqlInfinity.create! PostgresqlInfinity.update_all(datetime: Float::INFINITY) record.reload assert_equal Float::INFINITY, record.datetime end test "assigning 'infinity' on a datetime column with TZ aware attributes" do begin in_time_zone "Pacific Time (US & Canada)" do record = PostgresqlInfinity.create!(datetime: "infinity") assert_equal Float::INFINITY, record.datetime assert_equal record.datetime, record.reload.datetime end ensure # setting time_zone_aware_attributes causes the types to change. # There is no way to do this automatically since it can be set on a superclass PostgresqlInfinity.reset_column_information end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/integer_test.rb000066400000000000000000000011311266740050600264150ustar00rootroot00000000000000require "cases/helper" require "active_support/core_ext/numeric/bytes" class PostgresqlIntegerTest < ActiveRecord::TestCase class PgInteger < ActiveRecord::Base end def setup @connection = ActiveRecord::Base.connection @connection.transaction do @connection.create_table "pg_integers", force: true do |t| t.integer :quota, limit: 8, default: 2.gigabytes end end end teardown do @connection.execute "drop table if exists pg_integers" end test "schema properly respects bigint ranges" do assert_equal 2.gigabytes, PgInteger.new.quota end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/json_test.rb000066400000000000000000000134521266740050600257420ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'support/schema_dumping_helper' module PostgresqlJSONSharedTestCases include SchemaDumpingHelper class JsonDataType < ActiveRecord::Base self.table_name = 'json_data_type' store_accessor :settings, :resolution end def setup @connection = ActiveRecord::Base.connection begin @connection.transaction do @connection.create_table('json_data_type') do |t| t.public_send column_type, 'payload', default: {} # t.json 'payload', default: {} t.public_send column_type, 'settings' # t.json 'settings' end end rescue ActiveRecord::StatementInvalid skip "do not test on PG without json" end @column = JsonDataType.columns_hash['payload'] end def teardown @connection.execute 'drop table if exists json_data_type' end def test_column column = JsonDataType.columns_hash["payload"] assert_equal column_type, column.type assert_equal column_type.to_s, column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_default @connection.add_column 'json_data_type', 'permissions', column_type, default: '{"users": "read", "posts": ["read", "write"]}' JsonDataType.reset_column_information assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.column_defaults['permissions']) assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.new.permissions) ensure JsonDataType.reset_column_information end def test_change_table_supports_json @connection.transaction do @connection.change_table('json_data_type') do |t| t.public_send column_type, 'users', default: '{}' # t.json 'users', default: '{}' end JsonDataType.reset_column_information column = JsonDataType.columns_hash['users'] assert_equal column_type, column.type raise ActiveRecord::Rollback # reset the schema change end ensure JsonDataType.reset_column_information end def test_schema_dumping output = dump_table_schema("json_data_type") assert_match(/t.#{column_type.to_s}\s+"payload",\s+default: {}/, output) end def test_cast_value_on_write x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar} assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast) assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload) x.save assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload) end def test_type_cast_json column = JsonDataType.columns_hash["payload"] data = "{\"a_key\":\"a_value\"}" hash = column.type_cast_from_database(data) assert_equal({'a_key' => 'a_value'}, hash) assert_equal({'a_key' => 'a_value'}, column.type_cast_from_database(data)) assert_equal({}, column.type_cast_from_database("{}")) assert_equal({'key'=>nil}, column.type_cast_from_database('{"key": null}')) assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"}))) end def test_rewrite @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" x = JsonDataType.first x.payload = { '"a\'' => 'b' } assert x.save! end def test_select @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" x = JsonDataType.first assert_equal({'k' => 'v'}, x.payload) end def test_select_multikey @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| x = JsonDataType.first assert_equal({'k1' => 'v1', 'k2' => 'v2', 'k3' => [1,2,3]}, x.payload) end def test_null_json @connection.execute %q|insert into json_data_type (payload) VALUES(null)| x = JsonDataType.first assert_equal(nil, x.payload) end def test_select_array_json_value @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| x = JsonDataType.first assert_equal(['v0', {'k1' => 'v1'}], x.payload) end def test_rewrite_array_json_value @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| x = JsonDataType.first x.payload = ['v1', {'k2' => 'v2'}, 'v3'] assert x.save! end def test_with_store_accessors x = JsonDataType.new(resolution: "320×480") assert_equal "320×480", x.resolution x.save! x = JsonDataType.first assert_equal "320×480", x.resolution x.resolution = "640×1136" x.save! x = JsonDataType.first assert_equal "640×1136", x.resolution end def test_duplication_with_store_accessors x = JsonDataType.new(resolution: "320×480") assert_equal "320×480", x.resolution y = x.dup assert_equal "320×480", y.resolution end def test_yaml_round_trip_with_store_accessors x = JsonDataType.new(resolution: "320×480") assert_equal "320×480", x.resolution y = YAML.load(YAML.dump(x)) assert_equal "320×480", y.resolution end def test_changes_in_place json = JsonDataType.new assert_not json.changed? json.payload = { 'one' => 'two' } assert json.changed? assert json.payload_changed? json.save! assert_not json.changed? json.payload['three'] = 'four' assert json.payload_changed? json.save! json.reload assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload) assert_not json.changed? end def test_assigning_invalid_json json = JsonDataType.new json.payload = 'foo' assert_nil json.payload end end class PostgresqlJSONTest < ActiveRecord::TestCase include PostgresqlJSONSharedTestCases def column_type :json end end class PostgresqlJSONBTest < ActiveRecord::TestCase include PostgresqlJSONSharedTestCases def column_type :jsonb end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/ltree_test.rb000066400000000000000000000020111266740050600260710ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" class PostgresqlLtreeTest < ActiveRecord::TestCase class Ltree < ActiveRecord::Base self.table_name = 'ltrees' end def setup @connection = ActiveRecord::Base.connection enable_extension!('ltree', @connection) @connection.transaction do @connection.create_table('ltrees') do |t| t.ltree 'path' end end rescue ActiveRecord::StatementInvalid skip "do not test on PG without ltree" end teardown do @connection.execute 'drop table if exists ltrees' end def test_column column = Ltree.columns_hash['path'] assert_equal :ltree, column.type assert_equal "ltree", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_write ltree = Ltree.new(path: '1.2.3.4') assert ltree.save! end def test_select @connection.execute "insert into ltrees (path) VALUES ('1.2.3')" ltree = Ltree.first assert_equal '1.2.3', ltree.path end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/money_test.rb000066400000000000000000000054531266740050600261220ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'support/schema_dumping_helper' class PostgresqlMoneyTest < ActiveRecord::TestCase include SchemaDumpingHelper class PostgresqlMoney < ActiveRecord::Base; end setup do @connection = ActiveRecord::Base.connection @connection.execute("set lc_monetary = 'C'") @connection.create_table('postgresql_moneys', force: true) do |t| t.money "wealth" t.money "depth", default: "150.55" end end teardown do @connection.execute 'DROP TABLE IF EXISTS postgresql_moneys' end def test_column column = PostgresqlMoney.columns_hash["wealth"] assert_equal :money, column.type assert_equal "money", column.sql_type assert_equal 2, column.scale assert column.number? assert_not column.binary? assert_not column.array end def test_default assert_equal BigDecimal.new("150.55"), PostgresqlMoney.column_defaults['depth'] assert_equal BigDecimal.new("150.55"), PostgresqlMoney.new.depth end def test_money_values @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (1, '567.89'::money)") @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (2, '-567.89'::money)") first_money = PostgresqlMoney.find(1) second_money = PostgresqlMoney.find(2) assert_equal 567.89, first_money.wealth assert_equal(-567.89, second_money.wealth) end def test_money_type_cast column = PostgresqlMoney.columns_hash['wealth'] assert_equal(12345678.12, column.type_cast_from_user("$12,345,678.12")) assert_equal(12345678.12, column.type_cast_from_user("$12.345.678,12")) assert_equal(-1.15, column.type_cast_from_user("-$1.15")) assert_equal(-2.25, column.type_cast_from_user("($2.25)")) end def test_schema_dumping output = dump_table_schema("postgresql_moneys") assert_match %r{t\.money\s+"wealth",\s+scale: 2$}, output assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150.55$}, output end def test_create_and_update_money money = PostgresqlMoney.create(wealth: "987.65") assert_equal 987.65, money.wealth new_value = BigDecimal.new('123.45') money.wealth = new_value money.save! money.reload assert_equal new_value, money.wealth end def test_update_all_with_money_string money = PostgresqlMoney.create! PostgresqlMoney.update_all(wealth: "987.65") money.reload assert_equal 987.65, money.wealth end def test_update_all_with_money_big_decimal money = PostgresqlMoney.create! PostgresqlMoney.update_all(wealth: '123.45'.to_d) money.reload assert_equal 123.45, money.wealth end def test_update_all_with_money_numeric money = PostgresqlMoney.create! PostgresqlMoney.update_all(wealth: 123.45) money.reload assert_equal 123.45, money.wealth end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/network_test.rb000066400000000000000000000047161266740050600264650ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" class PostgresqlNetworkTest < ActiveRecord::TestCase class PostgresqlNetworkAddress < ActiveRecord::Base end def test_cidr_column column = PostgresqlNetworkAddress.columns_hash["cidr_address"] assert_equal :cidr, column.type assert_equal "cidr", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_inet_column column = PostgresqlNetworkAddress.columns_hash["inet_address"] assert_equal :inet, column.type assert_equal "inet", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_macaddr_column column = PostgresqlNetworkAddress.columns_hash["mac_address"] assert_equal :macaddr, column.type assert_equal "macaddr", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_network_types PostgresqlNetworkAddress.create(cidr_address: '192.168.0.0/24', inet_address: '172.16.1.254/32', mac_address: '01:23:45:67:89:0a') address = PostgresqlNetworkAddress.first assert_equal IPAddr.new('192.168.0.0/24'), address.cidr_address assert_equal IPAddr.new('172.16.1.254'), address.inet_address assert_equal '01:23:45:67:89:0a', address.mac_address address.cidr_address = '10.1.2.3/32' address.inet_address = '10.0.0.0/8' address.mac_address = 'bc:de:f0:12:34:56' address.save! assert address.reload assert_equal IPAddr.new('10.1.2.3/32'), address.cidr_address assert_equal IPAddr.new('10.0.0.0/8'), address.inet_address assert_equal 'bc:de:f0:12:34:56', address.mac_address end def test_invalid_network_address invalid_address = PostgresqlNetworkAddress.new(cidr_address: 'invalid addr', inet_address: 'invalid addr') assert_nil invalid_address.cidr_address assert_nil invalid_address.inet_address assert_equal 'invalid addr', invalid_address.cidr_address_before_type_cast assert_equal 'invalid addr', invalid_address.inet_address_before_type_cast assert invalid_address.save invalid_address.reload assert_nil invalid_address.cidr_address assert_nil invalid_address.inet_address assert_nil invalid_address.cidr_address_before_type_cast assert_nil invalid_address.inet_address_before_type_cast end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb000066400000000000000000000400651266740050600306740ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'support/ddl_helper' require 'support/connection_helper' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapterTest < ActiveRecord::TestCase include DdlHelper include ConnectionHelper def setup @connection = ActiveRecord::Base.connection end def test_bad_connection assert_raise ActiveRecord::NoDatabaseError do configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'should_not_exist-cinco-dog-db') connection = ActiveRecord::Base.postgresql_connection(configuration) connection.exec_query('SELECT 1') end end def test_valid_column with_example_table do column = @connection.columns('ex').find { |col| col.name == 'id' } assert @connection.valid_type?(column.type) end end def test_invalid_column assert_not @connection.valid_type?(:foobar) end def test_primary_key with_example_table do assert_equal 'id', @connection.primary_key('ex') end end def test_primary_key_works_tables_containing_capital_letters assert_equal 'id', @connection.primary_key('CamelCase') end def test_non_standard_primary_key with_example_table 'data character varying(255) primary key' do assert_equal 'data', @connection.primary_key('ex') end end def test_primary_key_returns_nil_for_no_pk with_example_table 'id integer' do assert_nil @connection.primary_key('ex') end end def test_composite_primary_key with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do assert_nil @connection.primary_key('ex') end end def test_primary_key_raises_error_if_table_not_found assert_raises(ActiveRecord::StatementInvalid) do @connection.primary_key('unobtainium') end end def test_insert_sql_with_proprietary_returning_clause with_example_table do id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") assert_equal "5150", id end end def test_insert_sql_with_quoted_schema_and_table_name with_example_table do id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') expect = @connection.query('select max(id) from ex').first.first assert_equal expect, id end end def test_insert_sql_with_no_space_after_table_name with_example_table do id = @connection.insert_sql("insert into ex(number) values(5150)") expect = @connection.query('select max(id) from ex').first.first assert_equal expect, id end end def test_multiline_insert_sql with_example_table do id = @connection.insert_sql(<<-SQL) insert into ex( number) values( 5152 ) SQL expect = @connection.query('select max(id) from ex').first.first assert_equal expect, id end end def test_insert_sql_with_returning_disabled connection = connection_without_insert_returning id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)") expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first assert_equal expect, id end def test_exec_insert_with_returning_disabled connection = connection_without_insert_returning result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq') expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first assert_equal expect, result.rows.first.first end def test_exec_insert_with_returning_disabled_and_no_sequence_name_given connection = connection_without_insert_returning result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id') expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first assert_equal expect, result.rows.first.first end def test_sql_for_insert_with_returning_disabled connection = connection_without_insert_returning result = connection.sql_for_insert('sql', nil, nil, nil, 'binds') assert_equal ['sql', 'binds'], result end def test_serial_sequence assert_equal 'public.accounts_id_seq', @connection.serial_sequence('accounts', 'id') assert_raises(ActiveRecord::StatementInvalid) do @connection.serial_sequence('zomg', 'id') end end def test_default_sequence_name assert_equal 'public.accounts_id_seq', @connection.default_sequence_name('accounts', 'id') assert_equal 'public.accounts_id_seq', @connection.default_sequence_name('accounts') end def test_default_sequence_name_bad_table assert_equal 'zomg_id_seq', @connection.default_sequence_name('zomg', 'id') assert_equal 'zomg_id_seq', @connection.default_sequence_name('zomg') end def test_pk_and_sequence_for with_example_table do pk, seq = @connection.pk_and_sequence_for('ex') assert_equal 'id', pk assert_equal @connection.default_sequence_name('ex', 'id'), seq.to_s end end def test_pk_and_sequence_for_with_non_standard_primary_key with_example_table 'code serial primary key' do pk, seq = @connection.pk_and_sequence_for('ex') assert_equal 'code', pk assert_equal @connection.default_sequence_name('ex', 'code'), seq.to_s end end def test_pk_and_sequence_for_returns_nil_if_no_seq with_example_table 'id integer primary key' do assert_nil @connection.pk_and_sequence_for('ex') end end def test_pk_and_sequence_for_returns_nil_if_no_pk with_example_table 'id integer' do assert_nil @connection.pk_and_sequence_for('ex') end end def test_pk_and_sequence_for_returns_nil_if_table_not_found assert_nil @connection.pk_and_sequence_for('unobtainium') end def test_pk_and_sequence_for_with_collision_pg_class_oid @connection.exec_query('create table ex(id serial primary key)') @connection.exec_query('create table ex2(id serial primary key)') correct_depend_record = [ "'pg_class'::regclass", "'ex_id_seq'::regclass", '0', "'pg_class'::regclass", "'ex'::regclass", '1', "'a'" ] collision_depend_record = [ "'pg_attrdef'::regclass", "'ex2_id_seq'::regclass", '0', "'pg_class'::regclass", "'ex'::regclass", '1', "'a'" ] @connection.exec_query( "DELETE FROM pg_depend WHERE objid = 'ex_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'" ) @connection.exec_query( "INSERT INTO pg_depend VALUES(#{collision_depend_record.join(',')})" ) @connection.exec_query( "INSERT INTO pg_depend VALUES(#{correct_depend_record.join(',')})" ) seq = @connection.pk_and_sequence_for('ex').last assert_equal PostgreSQL::Name.new("public", "ex_id_seq"), seq @connection.exec_query( "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'" ) ensure @connection.exec_query('DROP TABLE IF EXISTS ex') @connection.exec_query('DROP TABLE IF EXISTS ex2') end def test_exec_insert_number with_example_table do insert(@connection, 'number' => 10) result = @connection.exec_query('SELECT number FROM ex WHERE number = 10') assert_equal 1, result.rows.length assert_equal "10", result.rows.last.last end end def test_exec_insert_string with_example_table do str = 'ã„ãŸã ãã¾ã™ï¼' insert(@connection, 'number' => 10, 'data' => str) result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10') value = result.rows.last.last assert_equal str, value end end def test_table_alias_length assert_nothing_raised do @connection.table_alias_length end end def test_exec_no_binds with_example_table do result = @connection.exec_query('SELECT id, data FROM ex') assert_equal 0, result.rows.length assert_equal 2, result.columns.length assert_equal %w{ id data }, result.columns string = @connection.quote('foo') @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") result = @connection.exec_query('SELECT id, data FROM ex') assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [['1', 'foo']], result.rows end end def test_exec_with_binds with_example_table do string = @connection.quote('foo') @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") result = @connection.exec_query( 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [['1', 'foo']], result.rows end end def test_exec_typecasts_bind_vals with_example_table do string = @connection.quote('foo') @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") column = @connection.columns('ex').find { |col| col.name == 'id' } result = @connection.exec_query( 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [['1', 'foo']], result.rows end end def test_substitute_at bind = @connection.substitute_at(nil) assert_equal Arel.sql('$1'), bind.to_sql end def test_partial_index with_example_table do @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100" index = @connection.indexes('ex').find { |idx| idx.name == 'partial' } assert_equal "(number > 100)", index.where end end def test_columns_for_distinct_zero_orders assert_equal "posts.id", @connection.columns_for_distinct("posts.id", []) end def test_columns_for_distinct_one_order assert_equal "posts.id, posts.created_at AS alias_0", @connection.columns_for_distinct("posts.id", ["posts.created_at desc"]) end def test_columns_for_distinct_few_orders assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1", @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"]) end def test_columns_for_distinct_with_case assert_equal( 'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0', @connection.columns_for_distinct('posts.id', ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"]) ) end def test_columns_for_distinct_blank_not_nil_orders assert_equal "posts.id, posts.created_at AS alias_0", @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "]) end def test_columns_for_distinct_with_arel_order order = Object.new def order.to_sql "posts.created_at desc" end assert_equal "posts.id, posts.created_at AS alias_0", @connection.columns_for_distinct("posts.id", [order]) end def test_columns_for_distinct_with_nulls assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"]) assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"]) end def test_columns_for_distinct_without_order_specifiers assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id"]) assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls last"]) assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls first"]) end def test_raise_error_when_cannot_translate_exception assert_raise TypeError do @connection.send(:log, nil) { @connection.execute(nil) } end end def test_reload_type_map_for_newly_defined_types @connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')" result = @connection.select_all "SELECT 'good'::feeling" assert_instance_of(PostgreSQLAdapter::OID::Enum, result.column_types["feeling"]) ensure @connection.execute "DROP TYPE IF EXISTS feeling" reset_connection end def test_only_reload_type_map_once_for_every_unknown_type silence_warnings do assert_queries 2, ignore_none: true do @connection.select_all "SELECT NULL::anyelement" end assert_queries 1, ignore_none: true do @connection.select_all "SELECT NULL::anyelement" end assert_queries 2, ignore_none: true do @connection.select_all "SELECT NULL::anyarray" end end ensure reset_connection end def test_only_warn_on_first_encounter_of_unknown_oid warning = capture(:stderr) { @connection.select_all "SELECT NULL::anyelement" @connection.select_all "SELECT NULL::anyelement" @connection.select_all "SELECT NULL::anyelement" } assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning) ensure reset_connection end def test_unparsed_defaults_are_at_least_set_when_saving with_example_table "id SERIAL PRIMARY KEY, number INTEGER NOT NULL DEFAULT (4 + 4) * 2 / 4" do number_klass = Class.new(ActiveRecord::Base) do self.table_name = 'ex' end column = number_klass.columns_hash["number"] assert_nil column.default assert_nil column.default_function first_number = number_klass.new assert_nil first_number.number first_number.save! assert_equal 4, first_number.reload.number end end private def insert(ctx, data) binds = data.map { |name, value| [ctx.columns('ex').find { |x| x.name == name }, value] } columns = binds.map(&:first).map(&:name) bind_subs = columns.length.times.map { |x| "$#{x + 1}" } sql = "INSERT INTO ex (#{columns.join(", ")}) VALUES (#{bind_subs.join(', ')})" ctx.exec_insert(sql, 'SQL', binds) end def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block) super(@connection, 'ex', definition, &block) end def connection_without_insert_returning ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) end end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/quoting_test.rb000066400000000000000000000047101266740050600264540ustar00rootroot00000000000000require "cases/helper" require 'ipaddr' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapter class QuotingTest < ActiveRecord::TestCase def setup @conn = ActiveRecord::Base.connection end def test_type_cast_true c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean') assert_equal 't', @conn.type_cast(true, nil) assert_equal 't', @conn.type_cast(true, c) end def test_type_cast_false c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean') assert_equal 'f', @conn.type_cast(false, nil) assert_equal 'f', @conn.type_cast(false, c) end def test_type_cast_cidr ip = IPAddr.new('255.0.0.0/8') c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'cidr') assert_equal ip, @conn.type_cast(ip, c) end def test_type_cast_inet ip = IPAddr.new('255.1.0.0/8') c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'inet') assert_equal ip, @conn.type_cast(ip, c) end def test_quote_float_nan nan = 0.0/0 c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float') assert_equal "'NaN'", @conn.quote(nan, c) end def test_quote_float_infinity infinity = 1.0/0 c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float') assert_equal "'Infinity'", @conn.quote(infinity, c) end def test_quote_cast_numeric fixnum = 666 c = PostgreSQLColumn.new(nil, nil, Type::String.new, 'varchar') assert_equal "'666'", @conn.quote(fixnum, c) c = PostgreSQLColumn.new(nil, nil, Type::Text.new, 'text') assert_equal "'666'", @conn.quote(fixnum, c) end def test_quote_time_usec assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0)) assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0).to_datetime) end def test_quote_range range = "1,2]'; SELECT * FROM users; --".."a" c = PostgreSQLColumn.new(nil, nil, OID::Range.new(Type::Integer.new, :int8range)) assert_equal "'[1,0]'", @conn.quote(range, c) end def test_quote_bit_string c = PostgreSQLColumn.new(nil, 1, OID::Bit.new) assert_equal nil, @conn.quote("'); SELECT * FROM users; /*\n01\n*/--", c) end end end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/range_test.rb000066400000000000000000000310161266740050600260610ustar00rootroot00000000000000require "cases/helper" require 'support/connection_helper' if ActiveRecord::Base.connection.supports_ranges? class PostgresqlRange < ActiveRecord::Base self.table_name = "postgresql_ranges" end class PostgresqlRangeTest < ActiveRecord::TestCase self.use_transactional_fixtures = false include ConnectionHelper def setup @connection = PostgresqlRange.connection begin @connection.transaction do @connection.execute <<_SQL CREATE TYPE floatrange AS RANGE ( subtype = float8, subtype_diff = float8mi ); _SQL @connection.create_table('postgresql_ranges') do |t| t.daterange :date_range t.numrange :num_range t.tsrange :ts_range t.tstzrange :tstz_range t.int4range :int4_range t.int8range :int8_range end @connection.add_column 'postgresql_ranges', 'float_range', 'floatrange' end PostgresqlRange.reset_column_information rescue ActiveRecord::StatementInvalid skip "do not test on PG without range" end insert_range(id: 101, date_range: "[''2012-01-02'', ''2012-01-04'']", num_range: "[0.1, 0.2]", ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'']", tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']", int4_range: "[1, 10]", int8_range: "[10, 100]", float_range: "[0.5, 0.7]") insert_range(id: 102, date_range: "[''2012-01-02'', ''2012-01-04'')", num_range: "[0.1, 0.2)", ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'')", tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')", int4_range: "[1, 10)", int8_range: "[10, 100)", float_range: "[0.5, 0.7)") insert_range(id: 103, date_range: "[''2012-01-02'',]", num_range: "[0.1,]", ts_range: "[''2010-01-01 14:30'',]", tstz_range: "[''2010-01-01 14:30:00+05'',]", int4_range: "[1,]", int8_range: "[10,]", float_range: "[0.5,]") insert_range(id: 104, date_range: "[,]", num_range: "[,]", ts_range: "[,]", tstz_range: "[,]", int4_range: "[,]", int8_range: "[,]", float_range: "[,]") insert_range(id: 105, date_range: "[''2012-01-02'', ''2012-01-02'')", num_range: "[0.1, 0.1)", ts_range: "[''2010-01-01 14:30'', ''2010-01-01 14:30'')", tstz_range: "[''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')", int4_range: "[1, 1)", int8_range: "[10, 10)", float_range: "[0.5, 0.5)") @new_range = PostgresqlRange.new @first_range = PostgresqlRange.find(101) @second_range = PostgresqlRange.find(102) @third_range = PostgresqlRange.find(103) @fourth_range = PostgresqlRange.find(104) @empty_range = PostgresqlRange.find(105) end teardown do @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' @connection.execute 'DROP TYPE IF EXISTS floatrange' reset_connection end def test_data_type_of_range_types assert_equal :daterange, @first_range.column_for_attribute(:date_range).type assert_equal :numrange, @first_range.column_for_attribute(:num_range).type assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type end def test_int4range_values assert_equal 1...11, @first_range.int4_range assert_equal 1...10, @second_range.int4_range assert_equal 1...Float::INFINITY, @third_range.int4_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range) assert_nil @empty_range.int4_range end def test_int8range_values assert_equal 10...101, @first_range.int8_range assert_equal 10...100, @second_range.int8_range assert_equal 10...Float::INFINITY, @third_range.int8_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range) assert_nil @empty_range.int8_range end def test_daterange_values assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 4), @second_range.date_range assert_equal Date.new(2012, 1, 2)...Float::INFINITY, @third_range.date_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range) assert_nil @empty_range.date_range end def test_numrange_values assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range assert_nil @empty_range.num_range end def test_tsrange_values tz = ::ActiveRecord::Base.default_timezone assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) assert_nil @empty_range.ts_range end def test_tstzrange_values assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) assert_nil @empty_range.tstz_range end def test_custom_range_values assert_equal 0.5..0.7, @first_range.float_range assert_equal 0.5...0.7, @second_range.float_range assert_equal 0.5...Float::INFINITY, @third_range.float_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.float_range) assert_nil @empty_range.float_range end def test_create_tstzrange tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') round_trip(@new_range, :tstz_range, tstzrange) assert_equal @new_range.tstz_range, tstzrange assert_equal @new_range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC') end def test_update_tstzrange assert_equal_round_trip(@first_range, :tstz_range, Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET')) assert_nil_round_trip(@first_range, :tstz_range, Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000')) end def test_create_tsrange tz = ::ActiveRecord::Base.default_timezone assert_equal_round_trip(@new_range, :ts_range, Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0)) end def test_update_tsrange tz = ::ActiveRecord::Base.default_timezone assert_equal_round_trip(@first_range, :ts_range, Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0)) assert_nil_round_trip(@first_range, :ts_range, Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0)) end def test_create_numrange assert_equal_round_trip(@new_range, :num_range, BigDecimal.new('0.5')...BigDecimal.new('1')) end def test_update_numrange assert_equal_round_trip(@first_range, :num_range, BigDecimal.new('0.5')...BigDecimal.new('1')) assert_nil_round_trip(@first_range, :num_range, BigDecimal.new('0.5')...BigDecimal.new('0.5')) end def test_create_daterange assert_equal_round_trip(@new_range, :date_range, Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true)) end def test_update_daterange assert_equal_round_trip(@first_range, :date_range, Date.new(2012, 2, 3)...Date.new(2012, 2, 10)) assert_nil_round_trip(@first_range, :date_range, Date.new(2012, 2, 3)...Date.new(2012, 2, 3)) end def test_create_int4range assert_equal_round_trip(@new_range, :int4_range, Range.new(3, 50, true)) end def test_update_int4range assert_equal_round_trip(@first_range, :int4_range, 6...10) assert_nil_round_trip(@first_range, :int4_range, 3...3) end def test_create_int8range assert_equal_round_trip(@new_range, :int8_range, Range.new(30, 50, true)) end def test_update_int8range assert_equal_round_trip(@first_range, :int8_range, 60000...10000000) assert_nil_round_trip(@first_range, :int8_range, 39999...39999) end def test_exclude_beginning_for_subtypes_with_succ_method_is_deprecated tz = ::ActiveRecord::Base.default_timezone silence_warnings { assert_deprecated { range = PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range } assert_deprecated { range = PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 1)..Time.send(tz, 2011, 1, 1, 14, 30, 0), range.ts_range } assert_deprecated { range = PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") assert_equal Time.parse('2010-01-01 09:30:01 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), range.tstz_range } assert_deprecated { range = PostgresqlRange.create!(int4_range: "(1, 10]") assert_equal 2..10, range.int4_range } assert_deprecated { range = PostgresqlRange.create!(int8_range: "(10, 100]") assert_equal 11..100, range.int8_range } } end def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") } assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") } end def test_update_all_with_ranges PostgresqlRange.create! PostgresqlRange.update_all(int8_range: 1..100) assert_equal 1...101, PostgresqlRange.first.int8_range end def test_ranges_correctly_escape_input range = "-1,2]'; DROP TABLE postgresql_ranges; --".."a" PostgresqlRange.update_all(int8_range: range) assert_nothing_raised do PostgresqlRange.first end end private def assert_equal_round_trip(range, attribute, value) round_trip(range, attribute, value) assert_equal value, range.public_send(attribute) end def assert_nil_round_trip(range, attribute, value) round_trip(range, attribute, value) assert_nil range.public_send(attribute) end def round_trip(range, attribute, value) range.public_send "#{attribute}=", value assert range.save assert range.reload end def insert_range(values) @connection.execute <<-SQL INSERT INTO postgresql_ranges ( id, date_range, num_range, ts_range, tstz_range, int4_range, int8_range, float_range ) VALUES ( #{values[:id]}, '#{values[:date_range]}', '#{values[:num_range]}', '#{values[:ts_range]}', '#{values[:tstz_range]}', '#{values[:int4_range]}', '#{values[:int8_range]}', '#{values[:float_range]}' ) SQL end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/rename_table_test.rb000066400000000000000000000017421266740050600274060ustar00rootroot00000000000000require "cases/helper" class PostgresqlRenameTableTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection @connection.create_table :before_rename, force: true end def teardown @connection.execute 'DROP TABLE IF EXISTS "before_rename"' @connection.execute 'DROP TABLE IF EXISTS "after_rename"' end test "renaming a table also renames the primary key index" do # sanity check assert_equal 1, num_indices_named("before_rename_pkey") assert_equal 0, num_indices_named("after_rename_pkey") @connection.rename_table :before_rename, :after_rename assert_equal 0, num_indices_named("before_rename_pkey") assert_equal 1, num_indices_named("after_rename_pkey") end private def num_indices_named(name) @connection.execute(<<-SQL).values.length SELECT 1 FROM "pg_index" JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid" WHERE "pg_class"."relname" = '#{name}' SQL end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb000066400000000000000000000056761266740050600312220ustar00rootroot00000000000000require "cases/helper" class SchemaThing < ActiveRecord::Base end class SchemaAuthorizationTest < ActiveRecord::TestCase self.use_transactional_fixtures = false TABLE_NAME = 'schema_things' COLUMNS = [ 'id serial primary key', 'name character varying(50)' ] USERS = ['rails_pg_schema_user1', 'rails_pg_schema_user2'] def setup @connection = ActiveRecord::Base.connection @connection.execute "SET search_path TO '$user',public" set_session_auth USERS.each do |u| @connection.execute "CREATE USER #{u}" rescue nil @connection.execute "CREATE SCHEMA AUTHORIZATION #{u}" rescue nil set_session_auth u @connection.execute "CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" @connection.execute "INSERT INTO #{TABLE_NAME} (name) VALUES ('#{u}')" set_session_auth end end teardown do set_session_auth @connection.execute "RESET search_path" USERS.each do |u| @connection.execute "DROP SCHEMA #{u} CASCADE" @connection.execute "DROP USER #{u}" end end def test_schema_invisible assert_raise(ActiveRecord::StatementInvalid) do set_session_auth @connection.execute "SELECT * FROM #{TABLE_NAME}" end end def test_session_auth= assert_raise(ActiveRecord::StatementInvalid) do @connection.session_auth = 'DEFAULT' @connection.execute "SELECT * FROM #{TABLE_NAME}" end end def test_setting_auth_clears_stmt_cache assert_nothing_raised do set_session_auth USERS.each do |u| set_session_auth u assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] set_session_auth end end end def test_auth_with_bind assert_nothing_raised do set_session_auth USERS.each do |u| @connection.clear_cache! set_session_auth u assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] set_session_auth end end end def test_schema_uniqueness assert_nothing_raised do set_session_auth USERS.each do |u| set_session_auth u assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1") set_session_auth end end end def test_sequence_schema_caching assert_nothing_raised do USERS.each do |u| set_session_auth u st = SchemaThing.new :name => 'TEST1' st.save! st = SchemaThing.new :id => 5, :name => 'TEST2' st.save! set_session_auth end end end def test_tables_in_current_schemas assert !@connection.tables.include?(TABLE_NAME) USERS.each do |u| set_session_auth u assert @connection.tables.include?(TABLE_NAME) set_session_auth end end private def set_session_auth auth = nil @connection.session_auth = auth || 'default' end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/schema_test.rb000066400000000000000000000431611266740050600262310ustar00rootroot00000000000000require "cases/helper" require 'support/schema_dumping_helper' class SchemaTest < ActiveRecord::TestCase self.use_transactional_fixtures = false SCHEMA_NAME = 'test_schema' SCHEMA2_NAME = 'test_schema2' TABLE_NAME = 'things' CAPITALIZED_TABLE_NAME = 'Things' INDEX_A_NAME = 'a_index_things_on_name' INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema' INDEX_C_NAME = 'c_index_full_text_search' INDEX_D_NAME = 'd_index_things_on_description_desc' INDEX_E_NAME = 'e_index_things_on_name_vector' INDEX_A_COLUMN = 'name' INDEX_B_COLUMN_S1 = 'email' INDEX_B_COLUMN_S2 = 'moment' INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))} INDEX_D_COLUMN = 'description' INDEX_E_COLUMN = 'name_vector' COLUMNS = [ 'id integer', 'name character varying(50)', 'email character varying(50)', 'description character varying(100)', 'name_vector tsvector', 'moment timestamp without time zone default now()' ] PK_TABLE_NAME = 'table_with_pk' UNMATCHED_SEQUENCE_NAME = 'unmatched_primary_key_default_value_seq' UNMATCHED_PK_TABLE_NAME = 'table_with_unmatched_sequence_for_pk' class Thing1 < ActiveRecord::Base self.table_name = "test_schema.things" end class Thing2 < ActiveRecord::Base self.table_name = "test_schema2.things" end class Thing3 < ActiveRecord::Base self.table_name = 'test_schema."things.table"' end class Thing4 < ActiveRecord::Base self.table_name = 'test_schema."Things"' end class Thing5 < ActiveRecord::Base self.table_name = 'things' end class Song < ActiveRecord::Base self.table_name = "music.songs" has_and_belongs_to_many :albums end class Album < ActiveRecord::Base self.table_name = "music.albums" has_and_belongs_to_many :songs end def setup @connection = ActiveRecord::Base.connection @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{CAPITALIZED_TABLE_NAME}\" (#{COLUMNS.join(',')})" @connection.execute "CREATE SCHEMA #{SCHEMA2_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" @connection.execute "CREATE INDEX #{INDEX_A_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_A_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_A_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_A_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S1});" @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);" @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);" @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)" @connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))" end teardown do @connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE" @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end def test_schema_names assert_equal ["public", "schema_1", "test_schema", "test_schema2"], @connection.schema_names end def test_create_schema begin @connection.create_schema "test_schema3" assert @connection.schema_names.include? "test_schema3" ensure @connection.drop_schema "test_schema3" end end def test_raise_create_schema_with_existing_schema begin @connection.create_schema "test_schema3" assert_raises(ActiveRecord::StatementInvalid) do @connection.create_schema "test_schema3" end ensure @connection.drop_schema "test_schema3" end end def test_drop_schema begin @connection.create_schema "test_schema3" ensure @connection.drop_schema "test_schema3" end assert !@connection.schema_names.include?("test_schema3") end def test_habtm_table_name_with_schema ActiveRecord::Base.connection.execute <<-SQL DROP SCHEMA IF EXISTS music CASCADE; CREATE SCHEMA music; CREATE TABLE music.albums (id serial primary key); CREATE TABLE music.songs (id serial primary key); CREATE TABLE music.albums_songs (album_id integer, song_id integer); SQL song = Song.create Album.create assert_equal song, Song.includes(:albums).references(:albums).first ensure ActiveRecord::Base.connection.execute "DROP SCHEMA music CASCADE;" end def test_raise_drop_schema_with_nonexisting_schema assert_raises(ActiveRecord::StatementInvalid) do @connection.drop_schema "test_schema3" end end def test_raise_wraped_exception_on_bad_prepare assert_raises(ActiveRecord::StatementInvalid) do @connection.exec_query "select * from developers where id = ?", 'sql', [[nil, 1]] end end def test_schema_change_with_prepared_stmt altered = false @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] @connection.exec_query "alter table developers add column zomg int", 'sql', [] altered = true @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] ensure # We are not using DROP COLUMN IF EXISTS because that syntax is only # supported by pg 9.X @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered end def test_table_exists? [Thing1, Thing2, Thing3, Thing4].each do |klass| name = klass.table_name assert @connection.table_exists?(name), "'#{name}' table should exist" end end def test_table_exists_when_on_schema_search_path with_schema_search_path(SCHEMA_NAME) do assert(@connection.table_exists?(TABLE_NAME), "table should exist and be found") end end def test_table_exists_when_not_on_schema_search_path with_schema_search_path('PUBLIC') do assert(!@connection.table_exists?(TABLE_NAME), "table exists but should not be found") end end def test_table_exists_wrong_schema assert(!@connection.table_exists?("foo.things"), "table should not exist") end def test_table_exists_quoted_names [ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given| assert(@connection.table_exists?(given), "table should exist when specified as #{given}") end with_schema_search_path(SCHEMA_NAME) do given = %("#{TABLE_NAME}") assert(@connection.table_exists?(given), "table should exist when specified as #{given}") end end def test_table_exists_quoted_table with_schema_search_path(SCHEMA_NAME) do assert(@connection.table_exists?('"things.table"'), "table should exist") end end def test_with_schema_prefixed_table_name assert_nothing_raised do assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}") end end def test_with_schema_prefixed_capitalized_table_name assert_nothing_raised do assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{CAPITALIZED_TABLE_NAME}") end end def test_with_schema_search_path assert_nothing_raised do with_schema_search_path(SCHEMA_NAME) do assert_equal COLUMNS, columns(TABLE_NAME) end end end def test_proper_encoding_of_table_name assert_equal '"table_name"', @connection.quote_table_name('table_name') assert_equal '"table.name"', @connection.quote_table_name('"table.name"') assert_equal '"schema_name"."table_name"', @connection.quote_table_name('schema_name.table_name') assert_equal '"schema_name"."table.name"', @connection.quote_table_name('schema_name."table.name"') assert_equal '"schema.name"."table_name"', @connection.quote_table_name('"schema.name".table_name') assert_equal '"schema.name"."table.name"', @connection.quote_table_name('"schema.name"."table.name"') end def test_classes_with_qualified_schema_name assert_equal 0, Thing1.count assert_equal 0, Thing2.count assert_equal 0, Thing3.count assert_equal 0, Thing4.count Thing1.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now) assert_equal 1, Thing1.count assert_equal 0, Thing2.count assert_equal 0, Thing3.count assert_equal 0, Thing4.count Thing2.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now) assert_equal 1, Thing1.count assert_equal 1, Thing2.count assert_equal 0, Thing3.count assert_equal 0, Thing4.count Thing3.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now) assert_equal 1, Thing1.count assert_equal 1, Thing2.count assert_equal 1, Thing3.count assert_equal 0, Thing4.count Thing4.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now) assert_equal 1, Thing1.count assert_equal 1, Thing2.count assert_equal 1, Thing3.count assert_equal 1, Thing4.count end def test_raise_on_unquoted_schema_name assert_raises(ActiveRecord::StatementInvalid) do with_schema_search_path '$user,public' end end def test_without_schema_search_path assert_raises(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) } end def test_ignore_nil_schema_search_path assert_nothing_raised { with_schema_search_path nil } end def test_index_name_exists with_schema_search_path(SCHEMA_NAME) do assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true) assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true) assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true) assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true) assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) assert_not @connection.index_name_exists?(TABLE_NAME, 'missing_index', true) end end def test_dump_indexes_for_schema_one do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN) end def test_dump_indexes_for_schema_two do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN, INDEX_E_COLUMN) end def test_dump_indexes_for_schema_multiple_schemas_in_search_path do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN) end def test_with_uppercase_index_name @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" assert_nothing_raised { @connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"} @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" with_schema_search_path SCHEMA_NAME do assert_nothing_raised { @connection.remove_index! "things", "things_Index"} end end def test_primary_key_with_schema_specified [ %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) ].each do |given| assert_equal 'id', @connection.primary_key(given), "primary key should be found when table referenced as #{given}" end end def test_primary_key_assuming_schema_search_path with_schema_search_path(SCHEMA_NAME) do assert_equal 'id', @connection.primary_key(PK_TABLE_NAME), "primary key should be found" end end def test_primary_key_raises_error_if_table_not_found_on_schema_search_path with_schema_search_path(SCHEMA2_NAME) do assert_raises(ActiveRecord::StatementInvalid) do @connection.primary_key(PK_TABLE_NAME) end end end def test_pk_and_sequence_for_with_schema_specified pg_name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name [ %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}") ].each do |given| pk, seq = @connection.pk_and_sequence_for(given) assert_equal 'id', pk, "primary key should be found when table referenced as #{given}" assert_equal pg_name.new(SCHEMA_NAME, "#{PK_TABLE_NAME}_id_seq"), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}") assert_equal pg_name.new(SCHEMA_NAME, UNMATCHED_SEQUENCE_NAME), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}") end end def test_current_schema { %('$user',public) => 'public', SCHEMA_NAME => SCHEMA_NAME, %(#{SCHEMA2_NAME},#{SCHEMA_NAME},public) => SCHEMA2_NAME, %(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => 'public' }.each do |given,expect| with_schema_search_path(given) { assert_equal expect, @connection.current_schema } end end def test_prepared_statements_with_multiple_schemas [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name| with_schema_search_path schema_name do Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now) end end [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name| with_schema_search_path schema_name do assert_equal 1, Thing5.count end end end def test_schema_exists? { 'public' => true, SCHEMA_NAME => true, SCHEMA2_NAME => true, 'darkside' => false }.each do |given,expect| assert_equal expect, @connection.schema_exists?(given) end end def test_reset_pk_sequence sequence_name = "#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}" @connection.execute "SELECT setval('#{sequence_name}', 123)" assert_equal "124", @connection.select_value("SELECT nextval('#{sequence_name}')") @connection.reset_pk_sequence!("#{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME}") assert_equal "1", @connection.select_value("SELECT nextval('#{sequence_name}')") end def test_set_pk_sequence table_name = "#{SCHEMA_NAME}.#{PK_TABLE_NAME}" _, sequence_name = @connection.pk_and_sequence_for table_name @connection.set_pk_sequence! table_name, 123 assert_equal "124", @connection.select_value("SELECT nextval('#{sequence_name}')") @connection.reset_pk_sequence! table_name end private def columns(table_name) @connection.send(:column_definitions, table_name).map do |name, type, default| "#{name} #{type}" + (default ? " default #{default}" : '') end end def with_schema_search_path(schema_search_path) @connection.schema_search_path = schema_search_path yield if block_given? ensure @connection.schema_search_path = "'$user', public" end def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name) with_schema_search_path(this_schema_name) do indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name} assert_equal 4,indexes.size do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name) do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name) do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name) do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name) indexes.select{|i| i.name != INDEX_E_NAME}.each do |index| assert_equal :btree, index.using end assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN] end end def do_dump_index_assertions_for_one_index(this_index, this_index_name, this_index_column) assert_equal TABLE_NAME, this_index.table assert_equal 1, this_index.columns.size assert_equal this_index_column, this_index.columns[0] assert_equal this_index_name, this_index.name end end class SchemaForeignKeyTest < ActiveRecord::TestCase include SchemaDumpingHelper setup do @connection = ActiveRecord::Base.connection end def test_dump_foreign_key_targeting_different_schema @connection.create_schema "my_schema" @connection.create_table "my_schema.trains" do |t| t.string :name end @connection.create_table "wagons" do |t| t.integer :train_id end @connection.add_foreign_key "wagons", "my_schema.trains", column: "train_id" output = dump_table_schema "wagons" assert_match %r{\s+add_foreign_key "wagons", "my_schema.trains", column: "train_id"$}, output ensure @connection.execute "DROP TABLE IF EXISTS wagons" @connection.execute "DROP TABLE IF EXISTS my_schema.trains" @connection.execute "DROP SCHEMA IF EXISTS my_schema" end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb000066400000000000000000000017611266740050600300260ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapter < AbstractAdapter class InactivePGconn def query(*args) raise PGError end def status PGconn::CONNECTION_BAD end end class StatementPoolTest < ActiveRecord::TestCase if Process.respond_to?(:fork) def test_cache_is_per_pid cache = StatementPool.new nil, 10 cache['foo'] = 'bar' assert_equal 'bar', cache['foo'] pid = fork { lookup = cache['foo']; exit!(!lookup) } Process.waitpid pid assert $?.success?, 'process should exit successfully' end end def test_dealloc_does_not_raise_on_inactive_connection cache = StatementPool.new InactivePGconn.new, 10 cache['foo'] = 'bar' assert_nothing_raised { cache.clear } end end end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/timestamp_test.rb000066400000000000000000000137111266740050600267720ustar00rootroot00000000000000require 'cases/helper' require 'models/developer' require 'models/topic' class PostgresqlTimestampTest < ActiveRecord::TestCase class PostgresqlTimestampWithZone < ActiveRecord::Base; end self.use_transactional_fixtures = false setup do @connection = ActiveRecord::Base.connection @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')") end teardown do PostgresqlTimestampWithZone.delete_all end def test_timestamp_with_zone_values_with_rails_time_zone_support with_timezone_config default: :utc, aware_attributes: true do @connection.reconnect! timestamp = PostgresqlTimestampWithZone.find(1) assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time assert_instance_of Time, timestamp.time end ensure @connection.reconnect! end def test_timestamp_with_zone_values_without_rails_time_zone_support with_timezone_config default: :local, aware_attributes: false do @connection.reconnect! # make sure to use a non-UTC time zone @connection.execute("SET time zone 'America/Jamaica'", 'SCHEMA') timestamp = PostgresqlTimestampWithZone.find(1) assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time assert_instance_of Time, timestamp.time end ensure @connection.reconnect! end end class TimestampTest < ActiveRecord::TestCase fixtures :topics class Foo < ActiveRecord::Base; end def test_group_by_date keys = Topic.group("date_trunc('month', created_at)").count.keys assert_operator keys.length, :>, 0 keys.each { |k| assert_kind_of Time, k } end def test_load_infinity_and_beyond d = Developer.find_by_sql("select 'infinity'::timestamp as updated_at") assert d.first.updated_at.infinite?, 'timestamp should be infinite' d = Developer.find_by_sql("select '-infinity'::timestamp as updated_at") time = d.first.updated_at assert time.infinite?, 'timestamp should be infinite' assert_operator time, :<, 0 end def test_save_infinity_and_beyond d = Developer.create!(:name => 'aaron', :updated_at => 1.0 / 0.0) assert_equal(1.0 / 0.0, d.updated_at) d = Developer.create!(:name => 'aaron', :updated_at => -1.0 / 0.0) assert_equal(-1.0 / 0.0, d.updated_at) end def test_default_datetime_precision ActiveRecord::Base.connection.create_table(:foos) ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime assert_nil activerecord_column_option('foos', 'created_at', 'precision') end def test_timestamp_data_type_with_precision ActiveRecord::Base.connection.create_table(:foos) ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, :precision => 0 ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, :precision => 5 assert_equal 0, activerecord_column_option('foos', 'created_at', 'precision') assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision') end def test_timestamps_helper_with_custom_precision ActiveRecord::Base.connection.create_table(:foos) do |t| t.timestamps :null => true, :precision => 4 end assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision') assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision') end def test_passing_precision_to_timestamp_does_not_set_limit ActiveRecord::Base.connection.create_table(:foos) do |t| t.timestamps :null => true, :precision => 4 end assert_nil activerecord_column_option("foos", "created_at", "limit") assert_nil activerecord_column_option("foos", "updated_at", "limit") end def test_invalid_timestamp_precision_raises_error assert_raises ActiveRecord::ActiveRecordError do ActiveRecord::Base.connection.create_table(:foos) do |t| t.timestamps :null => true, :precision => 7 end end end def test_postgres_agrees_with_activerecord_about_precision ActiveRecord::Base.connection.create_table(:foos) do |t| t.timestamps :null => true, :precision => 4 end assert_equal '4', pg_datetime_precision('foos', 'created_at') assert_equal '4', pg_datetime_precision('foos', 'updated_at') end def test_bc_timestamp date = Date.new(0) - 1.week Developer.create!(:name => "aaron", :updated_at => date) assert_equal date, Developer.find_by_name("aaron").updated_at end def test_bc_timestamp_leap_year date = Time.utc(-4, 2, 29) Developer.create!(:name => "taihou", :updated_at => date) assert_equal date, Developer.find_by_name("taihou").updated_at end def test_bc_timestamp_year_zero date = Time.utc(0, 4, 7) Developer.create!(:name => "yahagi", :updated_at => date) assert_equal date, Developer.find_by_name("yahagi").updated_at end def test_formatting_timestamp_according_to_precision ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.datetime :created_at, precision: 0 t.datetime :updated_at, precision: 4 end date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999) Foo.create!(created_at: date, updated_at: date) assert foo = Foo.find_by(created_at: date) assert_equal date.to_s, foo.created_at.to_s assert_equal date.to_s, foo.updated_at.to_s assert_equal 000000, foo.created_at.usec assert_equal 999900, foo.updated_at.usec end private def pg_datetime_precision(table_name, column_name) results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'") result = results.find do |result_hash| result_hash["column_name"] == column_name end result && result["datetime_precision"] end def activerecord_column_option(tablename, column_name, option) result = ActiveRecord::Base.connection.columns(tablename).find do |column| column.name == column_name end result && result.send(option) end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb000066400000000000000000000023121266740050600273340ustar00rootroot00000000000000require 'cases/helper' class PostgresqlTypeLookupTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection end test "array delimiters are looked up correctly" do box_array = @connection.type_map.lookup(1020) int_array = @connection.type_map.lookup(1007) assert_equal ';', box_array.delimiter assert_equal ',', int_array.delimiter end test "array types correctly respect registration of subtypes" do int_array = @connection.type_map.lookup(1007, -1, "integer[]") bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]") big_array = [123456789123456789] assert_raises(RangeError) { int_array.type_cast_for_database(big_array) } assert_equal "{123456789123456789}", bigint_array.type_cast_for_database(big_array) end test "range types correctly respect registration of subtypes" do int_range = @connection.type_map.lookup(3904, -1, "int4range") bigint_range = @connection.type_map.lookup(3926, -1, "int8range") big_range = 0..123456789123456789 assert_raises(RangeError) { int_range.type_cast_for_database(big_range) } assert_equal "[0,123456789123456789]", bigint_range.type_cast_for_database(big_range) end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/utils_test.rb000066400000000000000000000044731266740050600261340ustar00rootroot00000000000000require 'cases/helper' class PostgreSQLUtilsTest < ActiveSupport::TestCase Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils def test_extract_schema_qualified_name { %(table_name) => [nil,'table_name'], %("table.name") => [nil,'table.name'], %(schema.table_name) => %w{schema table_name}, %("schema".table_name) => %w{schema table_name}, %(schema."table_name") => %w{schema table_name}, %("schema"."table_name") => %w{schema table_name}, %("even spaces".table) => ['even spaces','table'], %(schema."table.name") => ['schema', 'table.name'] }.each do |given, expect| assert_equal Name.new(*expect), extract_schema_qualified_name(given) end end end class PostgreSQLNameTest < ActiveSupport::TestCase Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name test "represents itself as schema.name" do obj = Name.new("public", "articles") assert_equal "public.articles", obj.to_s end test "without schema, represents itself as name only" do obj = Name.new(nil, "articles") assert_equal "articles", obj.to_s end test "quoted returns a string representation usable in a query" do assert_equal %("articles"), Name.new(nil, "articles").quoted assert_equal %("public"."articles"), Name.new("public", "articles").quoted end test "prevents double quoting" do name = Name.new('"quoted_schema"', '"quoted_table"') assert_equal "quoted_schema.quoted_table", name.to_s assert_equal %("quoted_schema"."quoted_table"), name.quoted end test "equality based on state" do assert_equal Name.new("access", "users"), Name.new("access", "users") assert_equal Name.new(nil, "users"), Name.new(nil, "users") assert_not_equal Name.new(nil, "users"), Name.new("access", "users") assert_not_equal Name.new("access", "users"), Name.new("public", "users") assert_not_equal Name.new("public", "users"), Name.new("public", "articles") end test "can be used as hash key" do hash = {Name.new("schema", "article_seq") => "success"} assert_equal "success", hash[Name.new("schema", "article_seq")] assert_equal nil, hash[Name.new("schema", "articles")] assert_equal nil, hash[Name.new("public", "article_seq")] end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/uuid_test.rb000066400000000000000000000215471266740050600257430ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'support/schema_dumping_helper' module PostgresqlUUIDHelper def connection @connection ||= ActiveRecord::Base.connection end def drop_table(name) connection.execute "drop table if exists #{name}" end end class PostgresqlUUIDTest < ActiveRecord::TestCase include PostgresqlUUIDHelper class UUIDType < ActiveRecord::Base self.table_name = "uuid_data_type" end setup do connection.create_table "uuid_data_type" do |t| t.uuid 'guid' end end teardown do drop_table "uuid_data_type" end def test_change_column_default @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()" UUIDType.reset_column_information column = UUIDType.columns_hash['thingy'] assert_equal "uuid_generate_v1()", column.default_function @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()" UUIDType.reset_column_information column = UUIDType.columns_hash['thingy'] assert_equal "uuid_generate_v4()", column.default_function ensure UUIDType.reset_column_information end def test_data_type_of_uuid_types column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type assert_equal "uuid", column.sql_type assert_not column.number? assert_not column.binary? assert_not column.array end def test_treat_blank_uuid_as_nil UUIDType.create! guid: '' assert_equal(nil, UUIDType.last.guid) end def test_treat_invalid_uuid_as_nil uuid = UUIDType.create! guid: 'foobar' assert_equal(nil, uuid.guid) end def test_invalid_uuid_dont_modify_before_type_cast uuid = UUIDType.new guid: 'foobar' assert_equal 'foobar', uuid.guid_before_type_cast end def test_acceptable_uuid_regex # Valid uuids ['A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11', '{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}', 'a0eebc999c0b4ef8bb6d6bb9bd380a11', 'a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11', '{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}', # The following is not a valid RFC 4122 UUID, but PG doesn't seem to care, # so we shouldn't block it either. (Pay attention to "fb6d" – the "f" here # is invalid – it must be one of 8, 9, A, B, a, b according to the spec.) '{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}', ].each do |valid_uuid| uuid = UUIDType.new guid: valid_uuid assert_not_nil uuid.guid end # Invalid uuids [['A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'], Hash.new, 0, 0.0, true, 'Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11', 'a0eebc999r0b4ef8ab6d6bb9bd380a11', 'a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11', '{a0eebc99-bb6d6bb9-bd380a11}'].each do |invalid_uuid| uuid = UUIDType.new guid: invalid_uuid assert_nil uuid.guid end end def test_uuid_formats ["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11", "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}", "a0eebc999c0b4ef8bb6d6bb9bd380a11", "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11", "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}"].each do |valid_uuid| UUIDType.create(guid: valid_uuid) uuid = UUIDType.last assert_equal "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", uuid.guid end end def test_uniqueness_validation_ignores_uuid klass = Class.new(ActiveRecord::Base) do self.table_name = "uuid_data_type" validates :guid, uniqueness: { case_sensitive: false } def self.name "UUIDType" end end record = klass.create!(guid: "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11") duplicate = klass.new(guid: record.guid) assert record.guid.present? # Ensure we actually are testing a UUID assert_not duplicate.valid? end end class PostgresqlLargeKeysTest < ActiveRecord::TestCase include PostgresqlUUIDHelper include SchemaDumpingHelper def setup connection.create_table('big_serials', id: :bigserial) do |t| t.string 'name' end end def test_omg schema = dump_table_schema "big_serials" assert_match "create_table \"big_serials\", id: :bigserial", schema end def teardown drop_table "big_serials" end end class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase include PostgresqlUUIDHelper include SchemaDumpingHelper class UUID < ActiveRecord::Base self.table_name = 'pg_uuids' end setup do enable_extension!('uuid-ossp', connection) connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t| t.string 'name' t.uuid 'other_uuid', default: 'uuid_generate_v4()' end # Create custom PostgreSQL function to generate UUIDs # to test dumping tables which columns have defaults with custom functions connection.execute <<-SQL CREATE OR REPLACE FUNCTION my_uuid_generator() RETURNS uuid AS $$ SELECT * FROM uuid_generate_v4() $$ LANGUAGE SQL VOLATILE; SQL # Create such a table with custom function as default value generator connection.create_table('pg_uuids_2', id: :uuid, default: 'my_uuid_generator()') do |t| t.string 'name' t.uuid 'other_uuid_2', default: 'my_uuid_generator()' end end teardown do drop_table "pg_uuids" drop_table 'pg_uuids_2' connection.execute 'DROP FUNCTION IF EXISTS my_uuid_generator();' disable_extension!('uuid-ossp', connection) end if ActiveRecord::Base.connection.supports_extensions? def test_id_is_uuid assert_equal :uuid, UUID.columns_hash['id'].type assert UUID.primary_key end def test_id_has_a_default u = UUID.create assert_not_nil u.id end def test_auto_create_uuid u = UUID.create u.reload assert_not_nil u.other_uuid end def test_pk_and_sequence_for_uuid_primary_key pk, seq = connection.pk_and_sequence_for('pg_uuids') assert_equal 'id', pk assert_equal nil, seq end def test_schema_dumper_for_uuid_primary_key schema = dump_table_schema "pg_uuids" assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema) assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema) end def test_schema_dumper_for_uuid_primary_key_with_custom_default schema = dump_table_schema "pg_uuids_2" assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema) assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema) end end end class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase include PostgresqlUUIDHelper include SchemaDumpingHelper setup do enable_extension!('uuid-ossp', connection) connection.create_table('pg_uuids', id: false) do |t| t.primary_key :id, :uuid, default: nil t.string 'name' end end teardown do drop_table "pg_uuids" disable_extension!('uuid-ossp', connection) end if ActiveRecord::Base.connection.supports_extensions? def test_id_allows_default_override_via_nil col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first assert_nil col_desc["default"] end def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil schema = dump_table_schema "pg_uuids" assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema) end end end class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase include PostgresqlUUIDHelper class UuidPost < ActiveRecord::Base self.table_name = 'pg_uuid_posts' has_many :uuid_comments, inverse_of: :uuid_post end class UuidComment < ActiveRecord::Base self.table_name = 'pg_uuid_comments' belongs_to :uuid_post end setup do enable_extension!('uuid-ossp', connection) connection.transaction do connection.create_table('pg_uuid_posts', id: :uuid) do |t| t.string 'title' end connection.create_table('pg_uuid_comments', id: :uuid) do |t| t.references :uuid_post, type: :uuid t.string 'content' end end end teardown do drop_table "pg_uuid_comments" drop_table "pg_uuid_posts" disable_extension!('uuid-ossp', connection) end if ActiveRecord::Base.connection.supports_extensions? def test_collection_association_with_uuid post = UuidPost.create! comment = post.uuid_comments.create! assert post.uuid_comments.find(comment.id) end def test_find_with_uuid UuidPost.create! assert_raise ActiveRecord::RecordNotFound do UuidPost.find(123456) end end def test_find_by_with_uuid UuidPost.create! assert_nil UuidPost.find_by(id: 789) end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/view_test.rb000066400000000000000000000026741266740050600257470ustar00rootroot00000000000000require "cases/helper" require "cases/view_test" class UpdateableViewTest < ActiveRecord::TestCase fixtures :books class PrintedBook < ActiveRecord::Base self.primary_key = "id" end setup do @connection = ActiveRecord::Base.connection @connection.execute <<-SQL CREATE VIEW printed_books AS SELECT id, name, status, format FROM books WHERE format = 'paperback' SQL end teardown do @connection.execute "DROP VIEW printed_books" if @connection.table_exists? "printed_books" end def test_update_record book = PrintedBook.first book.name = "AWDwR" book.save! book.reload assert_equal "AWDwR", book.name end def test_insert_record PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback" new_book = PrintedBook.last assert_equal "Rails in Action", new_book.name end def test_update_record_to_fail_view_conditions book = PrintedBook.first book.format = "ebook" book.save! assert_raises ActiveRecord::RecordNotFound do book.reload end end end if ActiveRecord::Base.connection.supports_materialized_views? class MaterializedViewTest < ActiveRecord::TestCase include ViewBehavior private def create_view(name, query) @connection.execute "CREATE MATERIALIZED VIEW #{name} AS #{query}" end def drop_view(name) @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.table_exists? name end end end rails-4.2.6/activerecord/test/cases/adapters/postgresql/xml_test.rb000066400000000000000000000022541266740050600255670ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' class PostgresqlXMLTest < ActiveRecord::TestCase class XmlDataType < ActiveRecord::Base self.table_name = 'xml_data_type' end def setup @connection = ActiveRecord::Base.connection begin @connection.transaction do @connection.create_table('xml_data_type') do |t| t.xml 'payload' end end rescue ActiveRecord::StatementInvalid skip "do not test on PG without xml" end @column = XmlDataType.columns_hash['payload'] end teardown do @connection.execute 'drop table if exists xml_data_type' end def test_column assert_equal :xml, @column.type end def test_null_xml @connection.execute %q|insert into xml_data_type (payload) VALUES(null)| assert_nil XmlDataType.first.payload end def test_round_trip data = XmlDataType.new(payload: "bar") assert_equal "bar", data.payload data.save! assert_equal "bar", data.reload.payload end def test_update_all data = XmlDataType.create! XmlDataType.update_all(payload: "baz") assert_equal "baz", data.reload.payload end end rails-4.2.6/activerecord/test/cases/adapters/sqlite3/000077500000000000000000000000001266740050600225615ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb000066400000000000000000000061271266740050600262740ustar00rootroot00000000000000require "cases/helper" class CopyTableTest < ActiveRecord::TestCase fixtures :customers def setup @connection = ActiveRecord::Base.connection class << @connection public :copy_table, :table_structure, :indexes end end def test_copy_table(from = 'customers', to = 'customers2', options = {}) assert_nothing_raised {copy_table(from, to, options)} assert_equal row_count(from), row_count(to) if block_given? yield from, to, options else assert_equal column_names(from), column_names(to) end @connection.drop_table(to) rescue nil end def test_copy_table_renaming_column test_copy_table('customers', 'customers2', :rename => {'name' => 'person_name'}) do |from, to, options| expected = column_values(from, 'name') assert_equal expected, column_values(to, 'person_name') assert expected.any?, "No values in table: #{expected.inspect}" end end def test_copy_table_allows_to_pass_options_to_create_table @connection.create_table('blocker_table') test_copy_table('customers', 'blocker_table', force: true) end def test_copy_table_with_index test_copy_table('comments', 'comments_with_index') do @connection.add_index('comments_with_index', ['post_id', 'type']) test_copy_table('comments_with_index', 'comments_with_index2') do assert_equal table_indexes_without_name('comments_with_index'), table_indexes_without_name('comments_with_index2') end end end def test_copy_table_without_primary_key test_copy_table('developers_projects', 'programmers_projects') do assert_nil @connection.primary_key('programmers_projects') end end def test_copy_table_with_id_col_that_is_not_primary_key test_copy_table('goofy_string_id', 'goofy_string_id2') do original_id = @connection.columns('goofy_string_id').detect{|col| col.name == 'id' } copied_id = @connection.columns('goofy_string_id2').detect{|col| col.name == 'id' } assert_equal original_id.type, copied_id.type assert_equal original_id.sql_type, copied_id.sql_type assert_equal original_id.limit, copied_id.limit end end def test_copy_table_with_unconventional_primary_key test_copy_table('owners', 'owners_unconventional') do original_pk = @connection.primary_key('owners') copied_pk = @connection.primary_key('owners_unconventional') assert_equal original_pk, copied_pk end end def test_copy_table_with_binary_column test_copy_table 'binaries', 'binaries2' end protected def copy_table(from, to, options = {}) @connection.copy_table(from, to, {:temporary => true}.merge(options)) end def column_names(table) @connection.table_structure(table).map {|column| column['name']} end def column_values(table, column) @connection.select_all("SELECT #{column} FROM #{table} ORDER BY id").map {|row| row[column]} end def table_indexes_without_name(table) @connection.indexes(table).delete(:name) end def row_count(table) @connection.select_one("SELECT COUNT(*) AS count FROM #{table}")['count'] end end rails-4.2.6/activerecord/test/cases/adapters/sqlite3/explain_test.rb000066400000000000000000000021121266740050600256010ustar00rootroot00000000000000require "cases/helper" require 'models/developer' require 'models/computer' module ActiveRecord module ConnectionAdapters class SQLite3Adapter class ExplainTest < ActiveRecord::TestCase fixtures :developers def test_explain_for_one_query explain = Developer.where(:id => 1).explain assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) end def test_explain_with_eager_loading explain = Developer.where(:id => 1).includes(:audit_logs).explain assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain assert_match(/(SCAN )?TABLE audit_logs/, explain) end end end end end rails-4.2.6/activerecord/test/cases/adapters/sqlite3/quoting_test.rb000066400000000000000000000065271266740050600256450ustar00rootroot00000000000000require "cases/helper" require 'bigdecimal' require 'yaml' require 'securerandom' module ActiveRecord module ConnectionAdapters class SQLite3Adapter class QuotingTest < ActiveRecord::TestCase def setup @conn = Base.sqlite3_connection :database => ':memory:', :adapter => 'sqlite3', :timeout => 100 end def test_type_cast_binary_encoding_without_logger @conn.extend(Module.new { def logger; end }) column = Column.new(nil, nil, Type::String.new) binary = SecureRandom.hex expected = binary.dup.encode!(Encoding::UTF_8) assert_equal expected, @conn.type_cast(binary, column) end def test_type_cast_symbol assert_equal 'foo', @conn.type_cast(:foo, nil) end def test_type_cast_date date = Date.today expected = @conn.quoted_date(date) assert_equal expected, @conn.type_cast(date, nil) end def test_type_cast_time time = Time.now expected = @conn.quoted_date(time) assert_equal expected, @conn.type_cast(time, nil) end def test_type_cast_numeric assert_equal 10, @conn.type_cast(10, nil) assert_equal 2.2, @conn.type_cast(2.2, nil) end def test_type_cast_nil assert_equal nil, @conn.type_cast(nil, nil) end def test_type_cast_true c = Column.new(nil, 1, Type::Integer.new) assert_equal 't', @conn.type_cast(true, nil) assert_equal 1, @conn.type_cast(true, c) end def test_type_cast_false c = Column.new(nil, 1, Type::Integer.new) assert_equal 'f', @conn.type_cast(false, nil) assert_equal 0, @conn.type_cast(false, c) end def test_type_cast_string assert_equal '10', @conn.type_cast('10', nil) c = Column.new(nil, 1, Type::Integer.new) assert_equal 10, @conn.type_cast('10', c) c = Column.new(nil, 1, Type::Float.new) assert_equal 10.1, @conn.type_cast('10.1', c) c = Column.new(nil, 1, Type::Binary.new) assert_equal '10.1', @conn.type_cast('10.1', c) c = Column.new(nil, 1, Type::Date.new) assert_equal '10.1', @conn.type_cast('10.1', c) end def test_type_cast_bigdecimal bd = BigDecimal.new '10.0' assert_equal bd.to_f, @conn.type_cast(bd, nil) end def test_type_cast_unknown_should_raise_error obj = Class.new.new assert_raise(TypeError) { @conn.type_cast(obj, nil) } end def test_type_cast_object_which_responds_to_quoted_id quoted_id_obj = Class.new { def quoted_id "'zomg'" end def id 10 end }.new assert_equal 10, @conn.type_cast(quoted_id_obj, nil) quoted_id_obj = Class.new { def quoted_id "'zomg'" end }.new assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) } end def test_quoting_binary_strings value = "hello".encode('ascii-8bit') column = Column.new(nil, 1, Type::String.new) assert_equal "'hello'", @conn.quote(value, column) end end end end end rails-4.2.6/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb000066400000000000000000000343201266740050600272330ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'models/owner' require 'tempfile' require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters class SQLite3AdapterTest < ActiveRecord::TestCase include DdlHelper self.use_transactional_fixtures = false class DualEncoding < ActiveRecord::Base end def setup @conn = Base.sqlite3_connection database: ':memory:', adapter: 'sqlite3', timeout: 100 end def test_bad_connection assert_raise ActiveRecord::NoDatabaseError do connection = ActiveRecord::Base.sqlite3_connection(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db") connection.exec_query('drop table if exists ex') end end unless in_memory_db? def test_connect_with_url original_connection = ActiveRecord::Base.remove_connection tf = Tempfile.open 'whatever' url = "sqlite3:#{tf.path}" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection ensure tf.close tf.unlink ActiveRecord::Base.establish_connection(original_connection) end def test_connect_memory_with_url original_connection = ActiveRecord::Base.remove_connection url = "sqlite3::memory:" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection ensure ActiveRecord::Base.establish_connection(original_connection) end end def test_valid_column with_example_table do column = @conn.columns('ex').find { |col| col.name == 'id' } assert @conn.valid_type?(column.type) end end # sqlite3 databases should be able to support any type and not just the # ones mentioned in the native_database_types. # # Therefore test_invalid column should always return true even if the # type is not valid. def test_invalid_column assert @conn.valid_type?(:foobar) end def test_column_types owner = Owner.create!(name: "hello".encode('ascii-8bit')) owner.reload select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', ' result = Owner.connection.exec_query <<-esql SELECT #{select} FROM #{Owner.table_name} WHERE #{Owner.primary_key} = #{owner.id} esql assert(!result.rows.first.include?("blob"), "should not store blobs") ensure owner.delete end def test_exec_insert with_example_table do column = @conn.columns('ex').find { |col| col.name == 'number' } vals = [[column, 10]] @conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals) result = @conn.exec_query( 'select number from ex where number = ?', 'SQL', vals) assert_equal 1, result.rows.length assert_equal 10, result.rows.first.first end end def test_primary_key_returns_nil_for_no_pk with_example_table 'id int, data string' do assert_nil @conn.primary_key('ex') end end def test_connection_no_db assert_raises(ArgumentError) do Base.sqlite3_connection {} end end def test_bad_timeout assert_raises(TypeError) do Base.sqlite3_connection database: ':memory:', adapter: 'sqlite3', timeout: 'usa' end end # connection is OK with a nil timeout def test_nil_timeout conn = Base.sqlite3_connection database: ':memory:', adapter: 'sqlite3', timeout: nil assert conn, 'made a connection' end def test_connect assert @conn, 'should have connection' end # sqlite3 defaults to UTF-8 encoding def test_encoding assert_equal 'UTF-8', @conn.encoding end def test_bind_value_substitute bind_param = @conn.substitute_at('foo') assert_equal Arel.sql('?'), bind_param.to_sql end def test_exec_no_binds with_example_table 'id int, data string' do result = @conn.exec_query('SELECT id, data FROM ex') assert_equal 0, result.rows.length assert_equal 2, result.columns.length assert_equal %w{ id data }, result.columns @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') result = @conn.exec_query('SELECT id, data FROM ex') assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [[1, 'foo']], result.rows end end def test_exec_query_with_binds with_example_table 'id int, data string' do @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') result = @conn.exec_query( 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [[1, 'foo']], result.rows end end def test_exec_query_typecasts_bind_vals with_example_table 'id int, data string' do @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') column = @conn.columns('ex').find { |col| col.name == 'id' } result = @conn.exec_query( 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [[1, 'foo']], result.rows end end def test_quote_binary_column_escapes_it DualEncoding.connection.execute(<<-eosql) CREATE TABLE IF NOT EXISTS dual_encodings ( id integer PRIMARY KEY AUTOINCREMENT, name varchar(255), data binary ) eosql str = "\x80".force_encoding("ASCII-8BIT") binary = DualEncoding.new name: 'ã„ãŸã ãã¾ã™ï¼', data: str binary.save! assert_equal str, binary.data ensure DualEncoding.connection.execute('DROP TABLE IF EXISTS dual_encodings') end def test_type_cast_should_not_mutate_encoding name = 'hello'.force_encoding(Encoding::ASCII_8BIT) Owner.create(name: name) assert_equal Encoding::ASCII_8BIT, name.encoding ensure Owner.delete_all end def test_execute with_example_table do @conn.execute "INSERT INTO ex (number) VALUES (10)" records = @conn.execute "SELECT * FROM ex" assert_equal 1, records.length record = records.first assert_equal 10, record['number'] assert_equal 1, record['id'] end end def test_quote_string assert_equal "''", @conn.quote_string("'") end def test_insert_sql with_example_table do 2.times do |i| rv = @conn.insert_sql "INSERT INTO ex (number) VALUES (#{i})" assert_equal(i + 1, rv) end records = @conn.execute "SELECT * FROM ex" assert_equal 2, records.length end end def test_insert_sql_logged with_example_table do sql = "INSERT INTO ex (number) VALUES (10)" name = "foo" assert_logged [[sql, name, []]] do @conn.insert_sql sql, name end end end def test_insert_id_value_returned with_example_table do sql = "INSERT INTO ex (number) VALUES (10)" idval = 'vuvuzela' id = @conn.insert_sql sql, nil, nil, idval assert_equal idval, id end end def test_select_rows with_example_table do 2.times do |i| @conn.create "INSERT INTO ex (number) VALUES (#{i})" end rows = @conn.select_rows 'select number, id from ex' assert_equal [[0, 1], [1, 2]], rows end end def test_select_rows_logged with_example_table do sql = "select * from ex" name = "foo" assert_logged [[sql, name, []]] do @conn.select_rows sql, name end end end def test_transaction with_example_table do count_sql = 'select count(*) from ex' @conn.begin_db_transaction @conn.create "INSERT INTO ex (number) VALUES (10)" assert_equal 1, @conn.select_rows(count_sql).first.first @conn.rollback_db_transaction assert_equal 0, @conn.select_rows(count_sql).first.first end end def test_tables with_example_table do assert_equal %w{ ex }, @conn.tables with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer', 'people' do assert_equal %w{ ex people }.sort, @conn.tables.sort end end end def test_tables_logs_name sql = <<-SQL SELECT name FROM sqlite_master WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence' SQL assert_logged [[sql.squish, 'SCHEMA', []]] do @conn.tables('hello') end end def test_indexes_logs_name with_example_table do assert_logged [["PRAGMA index_list(\"ex\")", 'SCHEMA', []]] do @conn.indexes('ex', 'hello') end end end def test_table_exists_logs_name with_example_table do sql = <<-SQL SELECT name FROM sqlite_master WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence' AND name = \"ex\" SQL assert_logged [[sql.squish, 'SCHEMA', []]] do assert @conn.table_exists?('ex') end end end def test_columns with_example_table do columns = @conn.columns('ex').sort_by { |x| x.name } assert_equal 2, columns.length assert_equal %w{ id number }.sort, columns.map { |x| x.name } assert_equal [nil, nil], columns.map { |x| x.default } assert_equal [true, true], columns.map { |x| x.null } end end def test_columns_with_default with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer default 10' do column = @conn.columns('ex').find { |x| x.name == 'number' } assert_equal '10', column.default end end def test_columns_with_not_null with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer not null' do column = @conn.columns('ex').find { |x| x.name == 'number' } assert_not column.null, "column should not be null" end end def test_indexes_logs with_example_table do assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do @conn.indexes('ex') end end end def test_no_indexes assert_equal [], @conn.indexes('items') end def test_index with_example_table do @conn.add_index 'ex', 'id', unique: true, name: 'fun' index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } assert_equal 'ex', index.table assert index.unique, 'index is unique' assert_equal ['id'], index.columns end end def test_non_unique_index with_example_table do @conn.add_index 'ex', 'id', name: 'fun' index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } assert_not index.unique, 'index is not unique' end end def test_compound_index with_example_table do @conn.add_index 'ex', %w{ id number }, name: 'fun' index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } assert_equal %w{ id number }.sort, index.columns.sort end end def test_primary_key with_example_table do assert_equal 'id', @conn.primary_key('ex') with_example_table 'internet integer PRIMARY KEY AUTOINCREMENT, number integer not null', 'foos' do assert_equal 'internet', @conn.primary_key('foos') end end end def test_no_primary_key with_example_table 'number integer not null' do assert_nil @conn.primary_key('ex') end end def test_composite_primary_key with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do assert_nil @conn.primary_key('ex') end end def test_supports_extensions assert_not @conn.supports_extensions?, 'does not support extensions' end def test_respond_to_enable_extension assert @conn.respond_to?(:enable_extension) end def test_respond_to_disable_extension assert @conn.respond_to?(:disable_extension) end def test_statement_closed db = SQLite3::Database.new(ActiveRecord::Base. configurations['arunit']['database']) statement = SQLite3::Statement.new(db, 'CREATE TABLE statement_test (number integer not null)') statement.stubs(:step).raises(SQLite3::BusyException, 'busy') statement.stubs(:columns).once.returns([]) statement.expects(:close).once SQLite3::Statement.stubs(:new).returns(statement) assert_raises ActiveRecord::StatementInvalid do @conn.exec_query 'select * from statement_test' end end private def assert_logged logs subscriber = SQLSubscriber.new subscription = ActiveSupport::Notifications.subscribe('sql.active_record', subscriber) yield assert_equal logs, subscriber.logged ensure ActiveSupport::Notifications.unsubscribe(subscription) end def with_example_table(definition = nil, table_name = 'ex', &block) definition ||= <<-SQL id integer PRIMARY KEY AUTOINCREMENT, number integer SQL super(@conn, table_name, definition, &block) end end end end rails-4.2.6/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb000066400000000000000000000011241266740050600304050ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'models/owner' module ActiveRecord module ConnectionAdapters class SQLite3CreateFolder < ActiveRecord::TestCase def test_sqlite_creates_directory Dir.mktmpdir do |dir| dir = Pathname.new(dir) @conn = Base.sqlite3_connection :database => dir.join("db/foo.sqlite3"), :adapter => 'sqlite3', :timeout => 100 assert Dir.exist? dir.join('db') assert File.exist? dir.join('db/foo.sqlite3') end end end end end rails-4.2.6/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb000066400000000000000000000010641266740050600272030ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord::ConnectionAdapters class SQLite3Adapter class StatementPoolTest < ActiveRecord::TestCase if Process.respond_to?(:fork) def test_cache_is_per_pid cache = StatementPool.new nil, 10 cache['foo'] = 'bar' assert_equal 'bar', cache['foo'] pid = fork { lookup = cache['foo']; exit!(!lookup) } Process.waitpid pid assert $?.success?, 'process should exit successfully' end end end end end rails-4.2.6/activerecord/test/cases/aggregations_test.rb000066400000000000000000000121421266740050600234300ustar00rootroot00000000000000require "cases/helper" require 'models/customer' class AggregationsTest < ActiveRecord::TestCase fixtures :customers def test_find_single_value_object assert_equal 50, customers(:david).balance.amount assert_kind_of Money, customers(:david).balance assert_equal 300, customers(:david).balance.exchange_to("DKK").amount end def test_find_multiple_value_object assert_equal customers(:david).address_street, customers(:david).address.street assert( customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country)) ) end def test_change_single_value_object customers(:david).balance = Money.new(100) customers(:david).save assert_equal 100, customers(:david).reload.balance.amount end def test_immutable_value_objects customers(:david).balance = Money.new(100) assert_raise(RuntimeError) { customers(:david).balance.instance_eval { @amount = 20 } } end def test_inferred_mapping assert_equal "35.544623640962634", customers(:david).gps_location.latitude assert_equal "-105.9309951055148", customers(:david).gps_location.longitude customers(:david).gps_location = GpsLocation.new("39x-110") assert_equal "39", customers(:david).gps_location.latitude assert_equal "-110", customers(:david).gps_location.longitude customers(:david).save customers(:david).reload assert_equal "39", customers(:david).gps_location.latitude assert_equal "-110", customers(:david).gps_location.longitude end def test_reloaded_instance_refreshes_aggregations assert_equal "35.544623640962634", customers(:david).gps_location.latitude assert_equal "-105.9309951055148", customers(:david).gps_location.longitude Customer.update_all("gps_location = '24x113'") customers(:david).reload assert_equal '24x113', customers(:david)['gps_location'] assert_equal GpsLocation.new('24x113'), customers(:david).gps_location end def test_gps_equality assert_equal GpsLocation.new('39x110'), GpsLocation.new('39x110') end def test_gps_inequality assert_not_equal GpsLocation.new('39x110'), GpsLocation.new('39x111') end def test_allow_nil_gps_is_nil assert_nil customers(:zaphod).gps_location end def test_allow_nil_gps_set_to_nil customers(:david).gps_location = nil customers(:david).save customers(:david).reload assert_nil customers(:david).gps_location end def test_allow_nil_set_address_attributes_to_nil customers(:zaphod).address = nil assert_nil customers(:zaphod).attributes[:address_street] assert_nil customers(:zaphod).attributes[:address_city] assert_nil customers(:zaphod).attributes[:address_country] end def test_allow_nil_address_set_to_nil customers(:zaphod).address = nil customers(:zaphod).save customers(:zaphod).reload assert_nil customers(:zaphod).address end def test_nil_raises_error_when_allow_nil_is_false assert_raise(NoMethodError) { customers(:david).balance = nil } end def test_allow_nil_address_loaded_when_only_some_attributes_are_nil customers(:zaphod).address_street = nil customers(:zaphod).save customers(:zaphod).reload assert_kind_of Address, customers(:zaphod).address assert_nil customers(:zaphod).address.street end def test_nil_assignment_results_in_nil customers(:david).gps_location = GpsLocation.new('39x111') assert_not_nil customers(:david).gps_location customers(:david).gps_location = nil assert_nil customers(:david).gps_location end def test_nil_return_from_converter_is_respected_when_allow_nil_is_true customers(:david).non_blank_gps_location = "" customers(:david).save customers(:david).reload assert_nil customers(:david).non_blank_gps_location ensure Customer.gps_conversion_was_run = nil end def test_nil_return_from_converter_results_in_failure_when_allow_nil_is_false assert_raises(NoMethodError) do customers(:barney).gps_location = "" end end def test_do_not_run_the_converter_when_nil_was_set customers(:david).non_blank_gps_location = nil assert_nil Customer.gps_conversion_was_run end def test_custom_constructor assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s assert_kind_of Fullname, customers(:barney).fullname end def test_custom_converter customers(:barney).fullname = 'Barnoit Gumbleau' assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s assert_kind_of Fullname, customers(:barney).fullname end end class OverridingAggregationsTest < ActiveRecord::TestCase class DifferentName; end class Person < ActiveRecord::Base composed_of :composed_of, :mapping => %w(person_first_name first_name) end class DifferentPerson < Person composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name) end def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited assert_not_equal Person.reflect_on_aggregation(:composed_of), DifferentPerson.reflect_on_aggregation(:composed_of) end end rails-4.2.6/activerecord/test/cases/ar_schema_test.rb000066400000000000000000000122741266740050600227060ustar00rootroot00000000000000require "cases/helper" if ActiveRecord::Base.connection.supports_migrations? class ActiveRecordSchemaTest < ActiveRecord::TestCase self.use_transactional_fixtures = false setup do @original_verbose = ActiveRecord::Migration.verbose ActiveRecord::Migration.verbose = false @connection = ActiveRecord::Base.connection ActiveRecord::SchemaMigration.drop_table end teardown do @connection.drop_table :fruits rescue nil @connection.drop_table :nep_fruits rescue nil @connection.drop_table :nep_schema_migrations rescue nil @connection.drop_table :has_timestamps rescue nil ActiveRecord::SchemaMigration.delete_all rescue nil ActiveRecord::Migration.verbose = @original_verbose end def test_has_no_primary_key old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore assert_nil ActiveRecord::SchemaMigration.primary_key ActiveRecord::SchemaMigration.create_table assert_difference "ActiveRecord::SchemaMigration.count", 1 do ActiveRecord::SchemaMigration.create version: 12 end ensure ActiveRecord::SchemaMigration.drop_table ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type end def test_schema_define ActiveRecord::Schema.define(:version => 7) do create_table :fruits do |t| t.column :color, :string t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle t.column :texture, :string t.column :flavor, :string end end assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } assert_equal 7, ActiveRecord::Migrator::current_version end def test_schema_define_w_table_name_prefix table_name = ActiveRecord::SchemaMigration.table_name old_table_name_prefix = ActiveRecord::Base.table_name_prefix ActiveRecord::Base.table_name_prefix = "nep_" ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}" ActiveRecord::Schema.define(:version => 7) do create_table :fruits do |t| t.column :color, :string t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle t.column :texture, :string t.column :flavor, :string end end assert_equal 7, ActiveRecord::Migrator::current_version ensure ActiveRecord::Base.table_name_prefix = old_table_name_prefix ActiveRecord::SchemaMigration.table_name = table_name end def test_schema_raises_an_error_for_invalid_column_type assert_raise NoMethodError do ActiveRecord::Schema.define(:version => 8) do create_table :vegetables do |t| t.unknown :color end end end end def test_schema_subclass Class.new(ActiveRecord::Schema).define(:version => 9) do create_table :fruits end assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } end def test_normalize_version assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118") assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2") assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017") assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") end def test_timestamps_without_null_is_deprecated_on_create_table assert_deprecated do ActiveRecord::Schema.define do create_table :has_timestamps do |t| t.timestamps end end end end def test_timestamps_without_null_is_deprecated_on_change_table assert_deprecated do ActiveRecord::Schema.define do create_table :has_timestamps change_table :has_timestamps do |t| t.timestamps end end end end def test_timestamps_without_null_is_deprecated_on_add_timestamps assert_deprecated do ActiveRecord::Schema.define do create_table :has_timestamps add_timestamps :has_timestamps end end end def test_no_deprecation_warning_from_timestamps_on_create_table assert_not_deprecated do ActiveRecord::Schema.define do create_table :has_timestamps do |t| t.timestamps null: true end drop_table :has_timestamps create_table :has_timestamps do |t| t.timestamps null: false end end end end def test_no_deprecation_warning_from_timestamps_on_change_table assert_not_deprecated do ActiveRecord::Schema.define do create_table :has_timestamps change_table :has_timestamps do |t| t.timestamps null: true end drop_table :has_timestamps create_table :has_timestamps change_table :has_timestamps do |t| t.timestamps null: false, default: Time.now end end end end end end rails-4.2.6/activerecord/test/cases/associations/000077500000000000000000000000001266740050600220715ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/associations/association_scope_test.rb000066400000000000000000000012401266740050600271570ustar00rootroot00000000000000require 'cases/helper' require 'models/post' require 'models/author' module ActiveRecord module Associations class AssociationScopeTest < ActiveRecord::TestCase test 'does not duplicate conditions' do scope = AssociationScope.scope(Author.new.association(:welcome_posts), Author.connection) wheres = scope.where_values.map(&:right) binds = scope.bind_values.map(&:last) wheres = scope.where_values.map(&:right).reject { |node| Arel::Nodes::BindParam === node } assert_equal wheres.uniq, wheres assert_equal binds.uniq, binds end end end end rails-4.2.6/activerecord/test/cases/associations/belongs_to_associations_test.rb000066400000000000000000000767041266740050600304050ustar00rootroot00000000000000require 'cases/helper' require 'models/developer' require 'models/computer' require 'models/project' require 'models/company' require 'models/topic' require 'models/reply' require 'models/computer' require 'models/post' require 'models/author' require 'models/tag' require 'models/tagging' require 'models/comment' require 'models/sponsor' require 'models/member' require 'models/essay' require 'models/toy' require 'models/invoice' require 'models/line_item' require 'models/column' require 'models/record' require 'models/admin' require 'models/admin/user' require 'models/ship' require 'models/treasure' require 'models/parrot' class BelongsToAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, :developers_projects, :computers, :authors, :author_addresses, :posts, :tags, :taggings, :comments, :sponsors, :members def test_belongs_to firm = Client.find(3).firm assert_not_nil firm assert_equal companies(:first_firm).name, firm.name end def test_belongs_to_does_not_use_order_by ActiveRecord::SQLCounter.clear_log Client.find(3).firm ensure assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query' end def test_belongs_to_with_primary_key client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name) assert_equal companies(:first_firm).name, client.firm_with_primary_key.name end def test_belongs_to_with_primary_key_joins_on_correct_column sql = Client.joins(:firm_with_primary_key).to_sql if current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql) assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql) elsif current_adapter?(:OracleAdapter) # on Oracle aliases are truncated to 30 characters and are quoted in uppercase assert_no_match(/"firm_with_primary_keys_compani"\."id"/i, sql) assert_match(/"firm_with_primary_keys_compani"\."name"/i, sql) else assert_no_match(/"firm_with_primary_keys_companies"\."id"/, sql) assert_match(/"firm_with_primary_keys_companies"\."name"/, sql) end end def test_default_scope_on_relations_is_not_cached counter = 0 comments = Class.new(ActiveRecord::Base) { self.table_name = 'comments' self.inheritance_column = 'not_there' posts = Class.new(ActiveRecord::Base) { self.table_name = 'posts' self.inheritance_column = 'not_there' default_scope -> { counter += 1 where("id = :inc", :inc => counter) } has_many :comments, :anonymous_class => comments } belongs_to :post, :anonymous_class => posts, :inverse_of => false } assert_equal 0, counter comment = comments.first assert_equal 0, counter sql = capture_sql { comment.post } comment.reload assert_not_equal sql, capture_sql { comment.post } end def test_proxy_assignment account = Account.find(1) assert_nothing_raised { account.firm = account.firm } end def test_type_mismatch assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 } assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) } end def test_raises_type_mismatch_with_namespaced_class assert_nil defined?(Region), "This test requires that there is no top-level Region class" ActiveRecord::Base.connection.instance_eval do create_table(:admin_regions) { |t| t.string :name } add_column :admin_users, :region_id, :integer end Admin.const_set "RegionalUser", Class.new(Admin::User) { belongs_to(:region) } Admin.const_set "Region", Class.new(ActiveRecord::Base) e = assert_raise(ActiveRecord::AssociationTypeMismatch) { Admin::RegionalUser.new(region: 'wrong value') } assert_match(/^Region\([^)]+\) expected, got String\([^)]+\)$/, e.message) ensure Admin.send :remove_const, "Region" if Admin.const_defined?("Region") Admin.send :remove_const, "RegionalUser" if Admin.const_defined?("RegionalUser") ActiveRecord::Base.connection.instance_eval do remove_column :admin_users, :region_id if column_exists?(:admin_users, :region_id) drop_table :admin_regions, if_exists: true end end def test_natural_assignment apple = Firm.create("name" => "Apple") citibank = Account.create("credit_limit" => 10) citibank.firm = apple assert_equal apple.id, citibank.firm_id end def test_id_assignment apple = Firm.create("name" => "Apple") citibank = Account.create("credit_limit" => 10) citibank.firm_id = apple assert_nil citibank.firm_id end def test_natural_assignment_with_primary_key apple = Firm.create("name" => "Apple") citibank = Client.create("name" => "Primary key client") citibank.firm_with_primary_key = apple assert_equal apple.name, citibank.firm_name end def test_eager_loading_with_primary_key Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first assert citibank_result.association(:firm_with_primary_key).loaded? end def test_eager_loading_with_primary_key_as_symbol Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first assert citibank_result.association(:firm_with_primary_key_symbols).loaded? end def test_creating_the_belonging_object citibank = Account.create("credit_limit" => 10) apple = citibank.create_firm("name" => "Apple") assert_equal apple, citibank.firm citibank.save citibank.reload assert_equal apple, citibank.firm end def test_creating_the_belonging_object_with_primary_key client = Client.create(:name => "Primary key client") apple = client.create_firm_with_primary_key("name" => "Apple") assert_equal apple, client.firm_with_primary_key client.save client.reload assert_equal apple, client.firm_with_primary_key end def test_building_the_belonging_object citibank = Account.create("credit_limit" => 10) apple = citibank.build_firm("name" => "Apple") citibank.save assert_equal apple.id, citibank.firm_id end def test_building_the_belonging_object_with_implicit_sti_base_class account = Account.new company = account.build_firm assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_belonging_object_with_explicit_sti_base_class account = Account.new company = account.build_firm(:type => "Company") assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_belonging_object_with_sti_subclass account = Account.new company = account.build_firm(:type => "Firm") assert_kind_of Firm, company, "Expected #{company.class} to be a Firm" end def test_building_the_belonging_object_with_an_invalid_type account = Account.new assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(:type => "InvalidType") } end def test_building_the_belonging_object_with_an_unrelated_type account = Account.new assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(:type => "Account") } end def test_building_the_belonging_object_with_primary_key client = Client.create(:name => "Primary key client") apple = client.build_firm_with_primary_key("name" => "Apple") client.save assert_equal apple.name, client.firm_name end def test_create! client = Client.create!(:name => "Jimmy") account = client.create_account!(:credit_limit => 10) assert_equal account, client.account assert account.persisted? client.save client.reload assert_equal account, client.account end def test_failing_create! client = Client.create!(:name => "Jimmy") assert_raise(ActiveRecord::RecordInvalid) { client.create_account! } assert_not_nil client.account assert client.account.new_record? end def test_natural_assignment_to_nil client = Client.find(3) client.firm = nil client.save assert_nil client.firm(true) assert_nil client.client_of end def test_natural_assignment_to_nil_with_primary_key client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name) client.firm_with_primary_key = nil client.save assert_nil client.firm_with_primary_key(true) assert_nil client.client_of end def test_with_different_class_name assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name assert_not_nil Company.find(3).firm_with_other_name, "Microsoft should have a firm" end def test_with_condition assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm" end def test_polymorphic_association_class sponsor = Sponsor.new assert_nil sponsor.association(:sponsorable).send(:klass) assert_nil sponsor.sponsorable(force_reload: true) sponsor.sponsorable_type = '' # the column doesn't have to be declared NOT NULL assert_nil sponsor.association(:sponsorable).send(:klass) assert_nil sponsor.sponsorable(force_reload: true) sponsor.sponsorable = Member.new :name => "Bert" assert_equal Member, sponsor.association(:sponsorable).send(:klass) assert_equal "members", sponsor.association(:sponsorable).aliased_table_name end def test_with_polymorphic_and_condition sponsor = Sponsor.create member = Member.create :name => "Bert" sponsor.sponsorable = member assert_equal member, sponsor.sponsorable assert_nil sponsor.sponsorable_with_conditions end def test_with_select assert_equal 1, Company.find(2).firm_with_select.attributes.size assert_equal 1, Company.all.merge!(:includes => :firm_with_select ).find(2).firm_with_select.attributes.size end def test_belongs_to_without_counter_cache_option # Ship has a conventionally named `treasures_count` column, but the counter_cache # option is not given on the association. ship = Ship.create(name: 'Countless') assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do treasure = Treasure.new(name: 'Gold', ship: ship) treasure.save end assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do treasure = ship.treasures.first treasure.destroy end end def test_belongs_to_counter debate = Topic.create("title" => "debate") assert_equal 0, debate.read_attribute("replies_count"), "No replies yet" trash = debate.replies.create("title" => "blah!", "content" => "world around!") assert_equal 1, Topic.find(debate.id).read_attribute("replies_count"), "First reply created" trash.destroy assert_equal 0, Topic.find(debate.id).read_attribute("replies_count"), "First reply deleted" end def test_belongs_to_counter_with_assigning_nil post = Post.find(1) comment = Comment.find(1) assert_equal post.id, comment.post_id assert_equal 2, Post.find(post.id).comments.size comment.post = nil assert_equal 1, Post.find(post.id).comments.size end def test_belongs_to_with_primary_key_counter debate = Topic.create("title" => "debate") debate2 = Topic.create("title" => "debate2") reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate") assert_equal 1, debate.reload.replies_count assert_equal 0, debate2.reload.replies_count reply.topic_with_primary_key = debate2 assert_equal 0, debate.reload.replies_count assert_equal 1, debate2.reload.replies_count reply.topic_with_primary_key = nil assert_equal 0, debate.reload.replies_count assert_equal 0, debate2.reload.replies_count end def test_belongs_to_counter_with_reassigning topic1 = Topic.create("title" => "t1") topic2 = Topic.create("title" => "t2") reply1 = Reply.new("title" => "r1", "content" => "r1") reply1.topic = topic1 assert reply1.save assert_equal 1, Topic.find(topic1.id).replies.size assert_equal 0, Topic.find(topic2.id).replies.size reply1.topic = Topic.find(topic2.id) assert_no_queries do reply1.topic = topic2 end assert reply1.save assert_equal 0, Topic.find(topic1.id).replies.size assert_equal 1, Topic.find(topic2.id).replies.size reply1.topic = nil assert_equal 0, Topic.find(topic1.id).replies.size assert_equal 0, Topic.find(topic2.id).replies.size reply1.topic = topic1 assert_equal 1, Topic.find(topic1.id).replies.size assert_equal 0, Topic.find(topic2.id).replies.size reply1.destroy assert_equal 0, Topic.find(topic1.id).replies.size assert_equal 0, Topic.find(topic2.id).replies.size end def test_belongs_to_reassign_with_namespaced_models_and_counters topic1 = Web::Topic.create("title" => "t1") topic2 = Web::Topic.create("title" => "t2") reply1 = Web::Reply.new("title" => "r1", "content" => "r1") reply1.topic = topic1 assert reply1.save assert_equal 1, Web::Topic.find(topic1.id).replies.size assert_equal 0, Web::Topic.find(topic2.id).replies.size reply1.topic = Web::Topic.find(topic2.id) assert reply1.save assert_equal 0, Web::Topic.find(topic1.id).replies.size assert_equal 1, Web::Topic.find(topic2.id).replies.size end def test_belongs_to_counter_after_save topic = Topic.create!(:title => "monday night") topic.replies.create!(:title => "re: monday night", :content => "football") assert_equal 1, Topic.find(topic.id)[:replies_count] topic.save! assert_equal 1, Topic.find(topic.id)[:replies_count] end def test_belongs_to_with_touch_option_on_touch line_item = LineItem.create! Invoice.create!(line_items: [line_item]) assert_queries(1) { line_item.touch } end def test_belongs_to_with_touch_option_on_touch_without_updated_at_attributes assert_not LineItem.column_names.include?("updated_at") line_item = LineItem.create! invoice = Invoice.create!(line_items: [line_item]) initial = invoice.updated_at line_item.touch assert_not_equal initial, invoice.reload.updated_at end def test_belongs_to_with_touch_option_on_touch_and_removed_parent line_item = LineItem.create! Invoice.create!(line_items: [line_item]) line_item.invoice = nil assert_queries(2) { line_item.touch } end def test_belongs_to_with_touch_option_on_update line_item = LineItem.create! Invoice.create!(line_items: [line_item]) assert_queries(2) { line_item.update amount: 10 } end def test_belongs_to_with_touch_option_on_empty_update line_item = LineItem.create! Invoice.create!(line_items: [line_item]) assert_queries(0) { line_item.save } end def test_belongs_to_with_touch_option_on_destroy line_item = LineItem.create! Invoice.create!(line_items: [line_item]) assert_queries(2) { line_item.destroy } end def test_belongs_to_with_touch_option_on_destroy_with_destroyed_parent line_item = LineItem.create! invoice = Invoice.create!(line_items: [line_item]) invoice.destroy assert_queries(1) { line_item.destroy } end def test_belongs_to_with_touch_option_on_touch_and_reassigned_parent line_item = LineItem.create! Invoice.create!(line_items: [line_item]) line_item.invoice = Invoice.create! assert_queries(3) { line_item.touch } end def test_belongs_to_counter_after_update topic = Topic.create!(title: "37s") topic.replies.create!(title: "re: 37s", content: "rails") assert_equal 1, Topic.find(topic.id)[:replies_count] topic.update(title: "37signals") assert_equal 1, Topic.find(topic.id)[:replies_count] end def test_belongs_to_counter_when_update_columns topic = Topic.create!(:title => "37s") topic.replies.create!(:title => "re: 37s", :content => "rails") assert_equal 1, Topic.find(topic.id)[:replies_count] topic.update_columns(content: "rails is wonderful") assert_equal 1, Topic.find(topic.id)[:replies_count] end def test_assignment_before_child_saved final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) final_cut.firm = firm assert !final_cut.persisted? assert final_cut.save assert final_cut.persisted? assert firm.persisted? assert_equal firm, final_cut.firm assert_equal firm, final_cut.firm(true) end def test_assignment_before_child_saved_with_primary_key final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) final_cut.firm_with_primary_key = firm assert !final_cut.persisted? assert final_cut.save assert final_cut.persisted? assert firm.persisted? assert_equal firm, final_cut.firm_with_primary_key assert_equal firm, final_cut.firm_with_primary_key(true) end def test_new_record_with_foreign_key_but_no_object client = Client.new("firm_id" => 1) # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first assert_equal Firm.all.merge!(:order => "id").first, client.firm_with_basic_id end def test_setting_foreign_key_after_nil_target_loaded client = Client.new client.firm_with_basic_id client.firm_id = 1 assert_equal companies(:first_firm), client.firm_with_basic_id end def test_polymorphic_setting_foreign_key_after_nil_target_loaded sponsor = Sponsor.new sponsor.sponsorable sponsor.sponsorable_id = 1 sponsor.sponsorable_type = "Member" assert_equal members(:groucho), sponsor.sponsorable end def test_dont_find_target_when_foreign_key_is_null tagging = taggings(:thinking_general) assert_queries(0) { tagging.super_tag } end def test_field_name_same_as_foreign_key computer = Computer.find(1) assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # ' end def test_counter_cache topic = Topic.create :title => "Zoom-zoom-zoom" assert_equal 0, topic[:replies_count] reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") reply.topic = topic assert_equal 1, topic.reload[:replies_count] assert_equal 1, topic.replies.size topic[:replies_count] = 15 assert_equal 15, topic.replies.size end def test_counter_cache_double_destroy topic = Topic.create :title => "Zoom-zoom-zoom" 5.times do topic.replies.create(:title => "re: zoom", :content => "speedy quick!") end assert_equal 5, topic.reload[:replies_count] assert_equal 5, topic.replies.size reply = topic.replies.first reply.destroy assert_equal 4, topic.reload[:replies_count] reply.destroy assert_equal 4, topic.reload[:replies_count] assert_equal 4, topic.replies.size end def test_concurrent_counter_cache_double_destroy topic = Topic.create :title => "Zoom-zoom-zoom" 5.times do topic.replies.create(:title => "re: zoom", :content => "speedy quick!") end assert_equal 5, topic.reload[:replies_count] assert_equal 5, topic.replies.size reply = topic.replies.first reply_clone = Reply.find(reply.id) reply.destroy assert_equal 4, topic.reload[:replies_count] reply_clone.destroy assert_equal 4, topic.reload[:replies_count] assert_equal 4, topic.replies.size end def test_custom_counter_cache reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") assert_equal 0, reply[:replies_count] silly = SillyReply.create(:title => "gaga", :content => "boo-boo") silly.reply = reply assert_equal 1, reply.reload[:replies_count] assert_equal 1, reply.replies.size reply[:replies_count] = 17 assert_equal 17, reply.replies.size end def test_association_assignment_sticks post = Post.first author1, author2 = Author.all.merge!(:limit => 2).to_a assert_not_nil author1 assert_not_nil author2 # make sure the association is loaded post.author # set the association by id, directly post.author_id = author2.id # save and reload post.save! post.reload # the author id of the post should be the id we set assert_equal post.author_id, author2.id end def test_cant_save_readonly_association assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! } assert companies(:first_client).readonly_firm.readonly? end def test_test_polymorphic_assignment_foreign_key_type_string comment = Comment.first comment.author = Author.first comment.resource = Member.first comment.save assert_equal Comment.all.to_a, Comment.includes(:author).to_a assert_equal Comment.all.to_a, Comment.includes(:resource).to_a end def test_polymorphic_assignment_foreign_type_field_updating # should update when assigning a saved record sponsor = Sponsor.new member = Member.create sponsor.sponsorable = member assert_equal "Member", sponsor.sponsorable_type # should update when assigning a new record sponsor = Sponsor.new member = Member.new sponsor.sponsorable = member assert_equal "Member", sponsor.sponsorable_type end def test_polymorphic_assignment_with_primary_key_foreign_type_field_updating # should update when assigning a saved record essay = Essay.new writer = Author.create(:name => "David") essay.writer = writer assert_equal "Author", essay.writer_type # should update when assigning a new record essay = Essay.new writer = Author.new essay.writer = writer assert_equal "Author", essay.writer_type end def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_records sponsor = Sponsor.new saved_member = Member.create new_member = Member.new sponsor.sponsorable = saved_member assert_equal saved_member.id, sponsor.sponsorable_id sponsor.sponsorable = new_member assert_nil sponsor.sponsorable_id end def test_assignment_updates_foreign_id_field_for_new_and_saved_records client = Client.new saved_firm = Firm.create :name => "Saved" new_firm = Firm.new client.firm = saved_firm assert_equal saved_firm.id, client.client_of client.firm = new_firm assert_nil client.client_of end def test_polymorphic_assignment_with_primary_key_updates_foreign_id_field_for_new_and_saved_records essay = Essay.new saved_writer = Author.create(:name => "David") new_writer = Author.new essay.writer = saved_writer assert_equal saved_writer.name, essay.writer_id essay.writer = new_writer assert_nil essay.writer_id end def test_polymorphic_assignment_with_nil essay = Essay.new assert_nil essay.writer_id assert_nil essay.writer_type essay.writer_id = 1 essay.writer_type = 'Author' essay.writer = nil assert_nil essay.writer_id assert_nil essay.writer_type end def test_belongs_to_proxy_should_not_respond_to_private_methods assert_raise(NoMethodError) { companies(:first_firm).private_method } assert_raise(NoMethodError) { companies(:second_client).firm.private_method } end def test_belongs_to_proxy_should_respond_to_private_methods_via_send companies(:first_firm).send(:private_method) companies(:second_client).firm.send(:private_method) end def test_save_of_record_with_loaded_belongs_to @account = companies(:first_firm).account assert_nothing_raised do Account.find(@account.id).save! Account.all.merge!(:includes => :firm).find(@account.id).save! end @account.firm.delete assert_nothing_raised do Account.find(@account.id).save! Account.all.merge!(:includes => :firm).find(@account.id).save! end end def test_dependent_delete_and_destroy_with_belongs_to AuthorAddress.destroyed_author_address_ids.clear author_address = author_addresses(:david_address) author_address_extra = author_addresses(:david_address_extra) assert_equal [], AuthorAddress.destroyed_author_address_ids assert_difference "AuthorAddress.count", -2 do authors(:david).destroy end assert_equal [], AuthorAddress.where(id: [author_address.id, author_address_extra.id]) assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids end def test_belongs_to_invalid_dependent_option_raises_exception error = assert_raise ArgumentError do Class.new(Author).belongs_to :special_author_address, :dependent => :nullify end assert_equal error.message, 'The :dependent option must be one of [:destroy, :delete], but is :nullify' end def test_attributes_are_being_set_when_initialized_from_belongs_to_association_with_where_clause new_firm = accounts(:signals37).build_firm(:name => 'Apple') assert_equal new_firm.name, "Apple" end def test_attributes_are_set_without_error_when_initialized_from_belongs_to_association_with_array_in_where_clause new_account = Account.where(:credit_limit => [ 50, 60 ]).new assert_nil new_account.credit_limit end def test_reassigning_the_parent_id_updates_the_object client = companies(:second_client) client.firm client.firm_with_condition firm_proxy = client.send(:association_instance_get, :firm) firm_with_condition_proxy = client.send(:association_instance_get, :firm_with_condition) assert !firm_proxy.stale_target? assert !firm_with_condition_proxy.stale_target? assert_equal companies(:first_firm), client.firm assert_equal companies(:first_firm), client.firm_with_condition client.client_of = companies(:another_firm).id assert firm_proxy.stale_target? assert firm_with_condition_proxy.stale_target? assert_equal companies(:another_firm), client.firm assert_equal companies(:another_firm), client.firm_with_condition end def test_polymorphic_reassignment_of_associated_id_updates_the_object sponsor = sponsors(:moustache_club_sponsor_for_groucho) sponsor.sponsorable proxy = sponsor.send(:association_instance_get, :sponsorable) assert !proxy.stale_target? assert_equal members(:groucho), sponsor.sponsorable sponsor.sponsorable_id = members(:some_other_guy).id assert proxy.stale_target? assert_equal members(:some_other_guy), sponsor.sponsorable end def test_polymorphic_reassignment_of_associated_type_updates_the_object sponsor = sponsors(:moustache_club_sponsor_for_groucho) sponsor.sponsorable proxy = sponsor.send(:association_instance_get, :sponsorable) assert !proxy.stale_target? assert_equal members(:groucho), sponsor.sponsorable sponsor.sponsorable_type = 'Firm' assert proxy.stale_target? assert_equal companies(:first_firm), sponsor.sponsorable end def test_reloading_association_with_key_change client = companies(:second_client) firm = client.association(:firm) client.firm = companies(:another_firm) firm.reload assert_equal companies(:another_firm), firm.target client.client_of = companies(:first_firm).id firm.reload assert_equal companies(:first_firm), firm.target end def test_polymorphic_counter_cache tagging = taggings(:welcome_general) post = posts(:welcome) comment = comments(:greetings) assert_difference lambda { post.reload.tags_count }, -1 do assert_difference 'comment.reload.tags_count', +1 do tagging.taggable = comment end end end def test_polymorphic_with_custom_foreign_type sponsor = sponsors(:moustache_club_sponsor_for_groucho) groucho = members(:groucho) other = members(:some_other_guy) assert_equal groucho, sponsor.sponsorable assert_equal groucho, sponsor.thing sponsor.thing = other assert_equal other, sponsor.sponsorable assert_equal other, sponsor.thing sponsor.sponsorable = groucho assert_equal groucho, sponsor.sponsorable assert_equal groucho, sponsor.thing end def test_build_with_conditions client = companies(:second_client) firm = client.build_bob_firm assert_equal "Bob", firm.name end def test_create_with_conditions client = companies(:second_client) firm = client.create_bob_firm assert_equal "Bob", firm.name end def test_create_bang_with_conditions client = companies(:second_client) firm = client.create_bob_firm! assert_equal "Bob", firm.name end def test_build_with_block client = Client.create(:name => 'Client Company') firm = client.build_firm{ |f| f.name = 'Agency Company' } assert_equal 'Agency Company', firm.name end def test_create_with_block client = Client.create(:name => 'Client Company') firm = client.create_firm{ |f| f.name = 'Agency Company' } assert_equal 'Agency Company', firm.name end def test_create_bang_with_block client = Client.create(:name => 'Client Company') firm = client.create_firm!{ |f| f.name = 'Agency Company' } assert_equal 'Agency Company', firm.name end def test_should_set_foreign_key_on_create_association client = Client.create! :name => "fuu" firm = client.create_firm :name => "baa" assert_equal firm.id, client.client_of end def test_should_set_foreign_key_on_create_association! client = Client.create! :name => "fuu" firm = client.create_firm! :name => "baa" assert_equal firm.id, client.client_of end def test_self_referential_belongs_to_with_counter_cache_assigning_nil comment = Comment.create! :post => posts(:thinking), :body => "fuu" comment.parent = nil comment.save! assert_equal nil, comment.reload.parent assert_equal 0, comments(:greetings).reload.children_count end def test_belongs_to_with_id_assigning post = posts(:welcome) comment = Comment.create! body: "foo", post: post parent = comments(:greetings) assert_equal 0, parent.reload.children_count comment.parent_id = parent.id comment.save! assert_equal 1, parent.reload.children_count end def test_polymorphic_with_custom_primary_key toy = Toy.create! sponsor = Sponsor.create!(:sponsorable => toy) assert_equal toy, sponsor.reload.sponsorable end test "stale tracking doesn't care about the type" do apple = Firm.create("name" => "Apple") citibank = Account.create("credit_limit" => 10) citibank.firm_id = apple.id citibank.firm # load it citibank.firm_id = apple.id.to_s assert !citibank.association(:firm).stale_target? end def test_reflect_the_most_recent_change author1, author2 = Author.limit(2) post = Post.new(:title => "foo", :body=> "bar") post.author = author1 post.author_id = author2.id assert post.save assert_equal post.author_id, author2.id end test 'dangerous association name raises ArgumentError' do [:errors, 'errors', :save, 'save'].each do |name| assert_raises(ArgumentError, "Association #{name} should not be allowed") do Class.new(ActiveRecord::Base) do belongs_to name end end end end test 'belongs_to works with model called Record' do record = Record.create! Column.create! record: record assert_equal 1, Column.count end end class BelongsToWithForeignKeyTest < ActiveRecord::TestCase fixtures :authors, :author_addresses def test_destroy_linked_models address = AuthorAddress.create! author = Author.create! name: "Author", author_address_id: address.id author.destroy! end end rails-4.2.6/activerecord/test/cases/associations/callbacks_test.rb000066400000000000000000000176111266740050600254020ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/author' require 'models/project' require 'models/developer' require 'models/computer' require 'models/company' class AssociationCallbacksTest < ActiveRecord::TestCase fixtures :posts, :authors, :projects, :developers def setup @david = authors(:david) @thinking = posts(:thinking) @authorless = posts(:authorless) assert @david.post_log.empty? end def test_adding_macro_callbacks @david.posts_with_callbacks << @thinking assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log @david.posts_with_callbacks << @thinking assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log end def test_adding_with_proc_callbacks @david.posts_with_proc_callbacks << @thinking assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log @david.posts_with_proc_callbacks << @thinking assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log end def test_removing_with_macro_callbacks first_post, second_post = @david.posts_with_callbacks[0, 2] @david.posts_with_callbacks.delete(first_post) assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log @david.posts_with_callbacks.delete(second_post) assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}", "after_removing#{second_post.id}"], @david.post_log end def test_removing_with_proc_callbacks first_post, second_post = @david.posts_with_callbacks[0, 2] @david.posts_with_proc_callbacks.delete(first_post) assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log @david.posts_with_proc_callbacks.delete(second_post) assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}", "after_removing#{second_post.id}"], @david.post_log end def test_multiple_callbacks @david.posts_with_multiple_callbacks << @thinking assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}"], @david.post_log @david.posts_with_multiple_callbacks << @thinking assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}", "before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}"], @david.post_log end def test_has_many_callbacks_with_create morten = Author.create :name => "Morten" post = morten.posts_with_proc_callbacks.create! :title => "Hello", :body => "How are you doing?" assert_equal ["before_adding", "after_adding#{post.id}"], morten.post_log end def test_has_many_callbacks_with_create! morten = Author.create! :name => "Morten" post = morten.posts_with_proc_callbacks.create :title => "Hello", :body => "How are you doing?" assert_equal ["before_adding", "after_adding#{post.id}"], morten.post_log end def test_has_many_callbacks_for_save_on_parent jack = Author.new :name => "Jack" jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep" callback_log = ["before_adding", "after_adding#{jack.posts_with_callbacks.first.id}"] assert_equal callback_log, jack.post_log assert jack.save assert_equal 1, jack.posts_with_callbacks.count assert_equal callback_log, jack.post_log end def test_has_many_callbacks_for_destroy_on_parent firm = Firm.create! :name => "Firm" client = firm.clients.create! :name => "Client" firm.destroy assert_equal ["before_remove#{client.id}", "after_remove#{client.id}"], firm.log end def test_has_and_belongs_to_many_add_callback david = developers(:david) ar = projects(:active_record) assert ar.developers_log.empty? ar.developers_with_callbacks << david assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log ar.developers_with_callbacks << david assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log end def test_has_and_belongs_to_many_before_add_called_before_save dev = nil new_dev = nil klass = Class.new(Project) do def self.name; Project.name; end has_and_belongs_to_many :developers_with_callbacks, :class_name => "Developer", :before_add => lambda { |o,r| dev = r new_dev = r.new_record? } end rec = klass.create! alice = Developer.new(:name => 'alice') rec.developers_with_callbacks << alice assert_equal alice, dev assert_not_nil new_dev assert new_dev, "record should not have been saved" assert_not alice.new_record? end def test_has_and_belongs_to_many_after_add_called_after_save ar = projects(:active_record) assert ar.developers_log.empty? alice = Developer.new(:name => 'alice') ar.developers_with_callbacks << alice assert_equal"after_adding#{alice.id}", ar.developers_log.last bob = ar.developers_with_callbacks.create(:name => 'bob') assert_equal "after_adding#{bob.id}", ar.developers_log.last ar.developers_with_callbacks.build(:name => 'charlie') assert_equal "after_adding", ar.developers_log.last end def test_has_and_belongs_to_many_remove_callback david = developers(:david) jamis = developers(:jamis) activerecord = projects(:active_record) assert activerecord.developers_log.empty? activerecord.developers_with_callbacks.delete(david) assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log activerecord.developers_with_callbacks.delete(jamis) assert_equal ["before_removing#{david.id}", "after_removing#{david.id}", "before_removing#{jamis.id}", "after_removing#{jamis.id}"], activerecord.developers_log end def test_has_and_belongs_to_many_does_not_fire_callbacks_on_clear activerecord = projects(:active_record) assert activerecord.developers_log.empty? if activerecord.developers_with_callbacks.size == 0 activerecord.developers << developers(:david) activerecord.developers << developers(:jamis) activerecord.reload assert activerecord.developers_with_callbacks.size == 2 end activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort assert activerecord.developers_with_callbacks.clear assert_predicate activerecord.developers_log, :empty? end def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent project = Project.new :name => "Callbacks" project.developers_with_callbacks.build :name => "Jack", :salary => 95000 callback_log = ["before_adding", "after_adding"] assert_equal callback_log, project.developers_log assert project.save assert_equal 1, project.developers_with_callbacks.size assert_equal callback_log, project.developers_log end def test_dont_add_if_before_callback_raises_exception assert !@david.unchangable_posts.include?(@authorless) begin @david.unchangable_posts << @authorless rescue Exception end assert @david.post_log.empty? assert !@david.unchangable_posts.include?(@authorless) @david.reload assert !@david.unchangable_posts.include?(@authorless) end end rails-4.2.6/activerecord/test/cases/associations/cascaded_eager_loading_test.rb000066400000000000000000000213531266740050600300500ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/comment' require 'models/author' require 'models/categorization' require 'models/category' require 'models/company' require 'models/topic' require 'models/reply' require 'models/person' require 'models/vertex' require 'models/edge' class CascadedEagerLoadingTest < ActiveRecord::TestCase fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations, :people, :categories, :edges, :vertices def test_eager_association_loading_with_cascaded_two_levels authors = Author.all.merge!(:includes=>{:posts=>:comments}, :order=>"authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} end def test_eager_association_loading_with_cascaded_two_levels_and_one_level authors = Author.all.merge!(:includes=>[{:posts=>:comments}, :categorizations], :order=>"authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} assert_equal 1, authors[0].categorizations.size assert_equal 2, authors[1].categorizations.size end def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations assert_nothing_raised do Author.joins(:posts).eager_load(:comments).where(:posts => {:tags_count => 1}).to_a end authors = Author.joins(:posts).eager_load(:comments).where(:posts => {:tags_count => 1}).to_a assert_equal 1, assert_no_queries { authors.size } assert_equal 10, assert_no_queries { authors[0].comments.size } end def test_eager_association_loading_grafts_stashed_associations_to_correct_parent assert_nothing_raised do Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').to_a end assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first end def test_cascaded_eager_association_loading_with_join_for_count categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors]) assert_equal 4, categories.count assert_equal 4, categories.to_a.count assert_equal 3, categories.distinct.count assert_equal 3, categories.to_a.uniq.size # Must uniq since instantiating with inner joins will get dupes end def test_cascaded_eager_association_loading_with_duplicated_includes categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null").references(:categorizations) assert_nothing_raised do assert_equal 3, categories.count assert_equal 3, categories.to_a.size end end def test_cascaded_eager_association_loading_with_twice_includes_edge_cases categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null").references(:posts) assert_nothing_raised do assert_equal 3, categories.count assert_equal 3, categories.to_a.size end end def test_eager_association_loading_with_join_for_count authors = Author.joins(:special_posts).includes([:posts, :categorizations]) assert_nothing_raised { authors.count } assert_queries(3) { authors.to_a } end def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations authors = Author.all.merge!(:includes=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} end def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference authors = Author.all.merge!(:includes=>{:posts=>[:comments, :author]}, :order=>"authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal authors(:david).name, authors[0].name assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq end def test_eager_association_loading_with_cascaded_two_levels_with_condition authors = Author.all.merge!(:includes=>{:posts=>:comments}, :where=>"authors.id=1", :order=>"authors.id").to_a assert_equal 1, authors.size assert_equal 5, authors[0].posts.size end def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong firms = Firm.all.merge!(:includes=>{:account=>{:firm=>:account}}, :order=>"companies.id").to_a assert_equal 2, firms.size assert_equal firms.first.account, firms.first.account.firm.account assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account } assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account } end def test_eager_association_loading_with_has_many_sti topics = Topic.all.merge!(:includes => :replies, :order => 'topics.id').to_a first, second, = topics(:first).replies.size, topics(:second).replies.size assert_no_queries do assert_equal first, topics[0].replies.size assert_equal second, topics[1].replies.size end end def test_eager_association_loading_with_has_many_sti_and_subclasses silly = SillyReply.new(:title => "gaga", :content => "boo-boo", :parent_id => 1) silly.parent_id = 1 assert silly.save topics = Topic.all.merge!(:includes => :replies, :order => ['topics.id', 'replies_topics.id']).to_a assert_no_queries do assert_equal 2, topics[0].replies.size assert_equal 0, topics[1].replies.size end end def test_eager_association_loading_with_belongs_to_sti replies = Reply.all.merge!(:includes => :topic, :order => 'topics.id').to_a assert replies.include?(topics(:second)) assert !replies.include?(topics(:first)) assert_equal topics(:first), assert_no_queries { replies.first.topic } end def test_eager_association_loading_with_multiple_stis_and_order author = Author.all.merge!(:includes => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :where => 'posts.id = 4').first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments author.posts.first.very_special_comment end end def test_eager_association_loading_of_stis_with_multiple_references authors = Author.all.merge!(:includes => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :where => 'posts.id = 4').to_a assert_equal [authors(:david)], authors assert_no_queries do authors.first.posts.first.special_comments.first.post.special_comments authors.first.posts.first.special_comments.first.post.very_special_comment end end def test_eager_association_loading_where_first_level_returns_nil authors = Author.all.merge!(:includes => {:post_about_thinking => :comments}, :order => 'authors.id DESC').to_a assert_equal [authors(:bob), authors(:mary), authors(:david)], authors assert_no_queries do authors[2].post_about_thinking.comments.first end end def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through source = Vertex.all.merge!(:includes=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id').first assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first } end def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many sink = Vertex.all.merge!(:includes=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC').first assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } end def test_eager_association_loading_with_cascaded_interdependent_one_level_and_two_levels authors_relation = Author.all.merge!(includes: [:comments, { posts: :categorizations }], order: "authors.id") authors = authors_relation.to_a assert_equal 3, authors.size assert_equal 10, authors[0].comments.size assert_equal 1, authors[1].comments.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size assert_equal 3, authors[0].posts.collect { |post| post.categorizations.size }.inject(0) { |sum, i| sum+i } end end deprecated_counter_cache_on_has_many_through_test.rb000066400000000000000000000014051266740050600344730ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/associationsrequire "cases/helper" class DeprecatedCounterCacheOnHasManyThroughTest < ActiveRecord::TestCase class Post < ActiveRecord::Base has_many :taggings, as: :taggable has_many :tags, through: :taggings end class Tagging < ActiveRecord::Base belongs_to :taggable, polymorphic: true belongs_to :tag end class Tag < ActiveRecord::Base end test "counter caches are updated in the database if the belongs_to association doesn't specify a counter cache" do post = Post.create!(title: 'Hello', body: 'World!') assert_deprecated { post.tags << Tag.create!(name: 'whatever') } assert_equal 1, post.tags.size assert_equal 1, post.tags_count assert_equal 1, post.reload.tags.size assert_equal 1, post.reload.tags_count end end rails-4.2.6/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb000066400000000000000000000017421266740050600325170ustar00rootroot00000000000000require 'cases/helper' require 'models/post' require 'models/tagging' module Namespaced class Post < ActiveRecord::Base self.table_name = 'posts' has_one :tagging, :as => :taggable, :class_name => 'Tagging' end end class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase def setup generate_test_objects end def generate_test_objects post = Namespaced::Post.create( :title => 'Great stuff', :body => 'This is not', :author_id => 1 ) Tagging.create( :taggable => post ) end def test_class_names old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = false post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff') assert_nil post.tagging ActiveRecord::Base.store_full_sti_class = true post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff') assert_instance_of Tagging, post.tagging ensure ActiveRecord::Base.store_full_sti_class = old end end rails-4.2.6/activerecord/test/cases/associations/eager_load_nested_include_test.rb000066400000000000000000000076021266740050600306110ustar00rootroot00000000000000require 'cases/helper' require 'models/post' require 'models/tag' require 'models/author' require 'models/comment' require 'models/category' require 'models/categorization' require 'models/tagging' module Remembered extend ActiveSupport::Concern included do after_create :remember protected def remember; self.class.remembered << self; end end module ClassMethods def remembered; @@remembered ||= []; end def sample; @@remembered.sample; end end end class ShapeExpression < ActiveRecord::Base belongs_to :shape, :polymorphic => true belongs_to :paint, :polymorphic => true end class Circle < ActiveRecord::Base has_many :shape_expressions, :as => :shape include Remembered end class Square < ActiveRecord::Base has_many :shape_expressions, :as => :shape include Remembered end class Triangle < ActiveRecord::Base has_many :shape_expressions, :as => :shape include Remembered end class PaintColor < ActiveRecord::Base has_many :shape_expressions, :as => :paint belongs_to :non_poly, :foreign_key => "non_poly_one_id", :class_name => "NonPolyOne" include Remembered end class PaintTexture < ActiveRecord::Base has_many :shape_expressions, :as => :paint belongs_to :non_poly, :foreign_key => "non_poly_two_id", :class_name => "NonPolyTwo" include Remembered end class NonPolyOne < ActiveRecord::Base has_many :paint_colors include Remembered end class NonPolyTwo < ActiveRecord::Base has_many :paint_textures include Remembered end class EagerLoadPolyAssocsTest < ActiveRecord::TestCase NUM_SIMPLE_OBJS = 50 NUM_SHAPE_EXPRESSIONS = 100 def setup generate_test_object_graphs end teardown do [Circle, Square, Triangle, PaintColor, PaintTexture, ShapeExpression, NonPolyOne, NonPolyTwo].each do |c| c.delete_all end end def generate_test_object_graphs 1.upto(NUM_SIMPLE_OBJS) do [Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!) end 1.upto(NUM_SIMPLE_OBJS) do PaintColor.create!(:non_poly_one_id => NonPolyOne.sample.id) PaintTexture.create!(:non_poly_two_id => NonPolyTwo.sample.id) end 1.upto(NUM_SHAPE_EXPRESSIONS) do shape_type = [Circle, Square, Triangle].sample paint_type = [PaintColor, PaintTexture].sample ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.sample.id, :paint_type => paint_type.to_s, :paint_id => paint_type.sample.id) end end def test_include_query res = ShapeExpression.all.merge!(:includes => [ :shape, { :paint => :non_poly } ]).to_a assert_equal NUM_SHAPE_EXPRESSIONS, res.size assert_queries(0) do res.each do |se| assert_not_nil se.paint.non_poly, "this is the association that was loading incorrectly before the change" assert_not_nil se.shape, "just making sure other associations still work" end end end end class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase def setup @davey_mcdave = Author.create(:name => 'Davey McDave') @first_post = @davey_mcdave.posts.create(:title => 'Davey Speaks', :body => 'Expressive wordage') @first_comment = @first_post.comments.create(:body => 'Inflamatory doublespeak') @first_categorization = @davey_mcdave.categorizations.create(:category => Category.first, :post => @first_post) end teardown do @davey_mcdave.destroy @first_post.destroy @first_comment.destroy @first_categorization.destroy end def test_missing_data_in_a_nested_include_should_not_cause_errors_when_constructing_objects assert_nothing_raised do # @davey_mcdave doesn't have any author_favorites includes = {:posts => :comments, :categorizations => :category, :author_favorites => :favorite_author } Author.all.merge!(:includes => includes, :where => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name').to_a end end end rails-4.2.6/activerecord/test/cases/associations/eager_singularization_test.rb000066400000000000000000000071361266740050600300510ustar00rootroot00000000000000require "cases/helper" if ActiveRecord::Base.connection.supports_migrations? class EagerSingularizationTest < ActiveRecord::TestCase class Virus < ActiveRecord::Base belongs_to :octopus end class Octopus < ActiveRecord::Base has_one :virus end class Pass < ActiveRecord::Base belongs_to :bus end class Bus < ActiveRecord::Base has_many :passes end class Mess < ActiveRecord::Base has_and_belongs_to_many :crises end class Crisis < ActiveRecord::Base has_and_belongs_to_many :messes has_many :analyses, :dependent => :destroy has_many :successes, :through => :analyses has_many :dresses, :dependent => :destroy has_many :compresses, :through => :dresses end class Analysis < ActiveRecord::Base belongs_to :crisis belongs_to :success end class Success < ActiveRecord::Base has_many :analyses, :dependent => :destroy has_many :crises, :through => :analyses end class Dress < ActiveRecord::Base belongs_to :crisis has_many :compresses end class Compress < ActiveRecord::Base belongs_to :dress end def setup connection.create_table :viri do |t| t.column :octopus_id, :integer t.column :species, :string end connection.create_table :octopi do |t| t.column :species, :string end connection.create_table :passes do |t| t.column :bus_id, :integer t.column :rides, :integer end connection.create_table :buses do |t| t.column :name, :string end connection.create_table :crises_messes, :id => false do |t| t.column :crisis_id, :integer t.column :mess_id, :integer end connection.create_table :messes do |t| t.column :name, :string end connection.create_table :crises do |t| t.column :name, :string end connection.create_table :successes do |t| t.column :name, :string end connection.create_table :analyses do |t| t.column :crisis_id, :integer t.column :success_id, :integer end connection.create_table :dresses do |t| t.column :crisis_id, :integer end connection.create_table :compresses do |t| t.column :dress_id, :integer end end teardown do connection.drop_table :viri connection.drop_table :octopi connection.drop_table :passes connection.drop_table :buses connection.drop_table :crises_messes connection.drop_table :messes connection.drop_table :crises connection.drop_table :successes connection.drop_table :analyses connection.drop_table :dresses connection.drop_table :compresses end def connection ActiveRecord::Base.connection end def test_eager_no_extra_singularization_belongs_to assert_nothing_raised do Virus.all.merge!(:includes => :octopus).to_a end end def test_eager_no_extra_singularization_has_one assert_nothing_raised do Octopus.all.merge!(:includes => :virus).to_a end end def test_eager_no_extra_singularization_has_many assert_nothing_raised do Bus.all.merge!(:includes => :passes).to_a end end def test_eager_no_extra_singularization_has_and_belongs_to_many assert_nothing_raised do Crisis.all.merge!(:includes => :messes).to_a Mess.all.merge!(:includes => :crises).to_a end end def test_eager_no_extra_singularization_has_many_through_belongs_to assert_nothing_raised do Crisis.all.merge!(:includes => :successes).to_a end end def test_eager_no_extra_singularization_has_many_through_has_many assert_nothing_raised do Crisis.all.merge!(:includes => :compresses).to_a end end end end rails-4.2.6/activerecord/test/cases/associations/eager_test.rb000066400000000000000000001576071266740050600245600ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/tagging' require 'models/tag' require 'models/comment' require 'models/author' require 'models/essay' require 'models/category' require 'models/company' require 'models/person' require 'models/reader' require 'models/owner' require 'models/pet' require 'models/reference' require 'models/job' require 'models/subscriber' require 'models/subscription' require 'models/book' require 'models/developer' require 'models/computer' require 'models/project' require 'models/member' require 'models/membership' require 'models/club' require 'models/categorization' require 'models/sponsor' class EagerAssociationTest < ActiveRecord::TestCase fixtures :posts, :comments, :authors, :essays, :author_addresses, :categories, :categories_posts, :companies, :accounts, :tags, :taggings, :people, :readers, :categorizations, :owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books, :developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors def test_eager_with_has_one_through_join_model_with_conditions_on_the_through member = Member.all.merge!(:includes => :favourite_club).find(members(:some_other_guy).id) assert_nil member.favourite_club end def test_loading_with_one_association posts = Post.all.merge!(:includes => :comments).to_a post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) post = Post.all.merge!(:includes => :comments, :where => "posts.title = 'Welcome to the weblog'").first assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) posts = Post.all.merge!(:includes => :last_comment).to_a post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_with_one_association_with_non_preload posts = Post.all.merge!(:includes => :last_comment, :order => 'comments.id DESC').to_a post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_conditions_with_or posts = authors(:david).posts.references(:comments).merge( :includes => :comments, :where => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'" ).to_a assert_nil posts.detect { |p| p.author_id != authors(:david).id }, "expected to find only david's posts" end def test_with_ordering list = Post.all.merge!(:includes => :comments, :order => "posts.id DESC").to_a [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome ].each_with_index do |post, index| assert_equal posts(post), list[index] end end def test_has_many_through_with_order authors = Author.includes(:favorite_authors).to_a assert authors.count > 0 assert_no_queries { authors.map(&:favorite_authors) } end def test_eager_loaded_has_one_association_with_references_does_not_run_additional_queries Post.update_all(author_id: nil) authors = Author.includes(:post).references(:post).to_a assert authors.count > 0 assert_no_queries { authors.map(&:post) } end def test_with_two_tables_in_from_without_getting_double_quoted posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a assert_equal 2, posts.first.comments.size end def test_loading_with_multiple_associations posts = Post.all.merge!(:includes => [ :comments, :author, :categories ], :order => "posts.id").to_a assert_equal 2, posts.first.comments.size assert_equal 2, posts.first.categories.size assert posts.first.comments.include?(comments(:greetings)) end def test_duplicate_middle_objects comments = Comment.all.merge!(:where => 'post_id = 1', :includes => [:post => :author]).to_a assert_no_queries do comments.each {|comment| comment.post.author.name} end end def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle Comment.connection.expects(:in_clause_length).at_least_once.returns(5) posts = Post.all.merge!(:includes=>:comments).to_a assert_equal 11, posts.size end def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) posts = Post.all.merge!(:includes=>:comments).to_a assert_equal 11, posts.size end def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle Comment.connection.expects(:in_clause_length).at_least_once.returns(5) posts = Post.all.merge!(:includes=>:categories).to_a assert_equal 11, posts.size end def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) posts = Post.all.merge!(:includes=>:categories).to_a assert_equal 11, posts.size end def test_load_associated_records_in_one_query_when_adapter_has_no_limit Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) post = posts(:welcome) assert_queries(2) do Post.includes(:comments).where(:id => post.id).to_a end end def test_load_associated_records_in_several_queries_when_many_ids_passed Comment.connection.expects(:in_clause_length).at_least_once.returns(1) post1, post2 = posts(:welcome), posts(:thinking) assert_queries(3) do Post.includes(:comments).where(:id => [post1.id, post2.id]).to_a end end def test_load_associated_records_in_one_query_when_a_few_ids_passed Comment.connection.expects(:in_clause_length).at_least_once.returns(3) post = posts(:welcome) assert_queries(2) do Post.includes(:comments).where(:id => post.id).to_a end end def test_including_duplicate_objects_from_belongs_to popular_post = Post.create!(:title => 'foo', :body => "I like cars!") comment = popular_post.comments.create!(:body => "lol") popular_post.readers.create!(:person => people(:michael)) popular_post.readers.create!(:person => people(:david)) readers = Reader.all.merge!(:where => ["post_id = ?", popular_post.id], :includes => {:post => :comments}).to_a readers.each do |reader| assert_equal [comment], reader.post.comments end end def test_including_duplicate_objects_from_has_many car_post = Post.create!(:title => 'foo', :body => "I like cars!") car_post.categories << categories(:general) car_post.categories << categories(:technology) comment = car_post.comments.create!(:body => "hmm") categories = Category.all.merge!(:where => { 'posts.id' => car_post.id }, :includes => {:posts => :comments}).to_a categories.each do |category| assert_equal [comment], category.posts[0].comments end end def test_associations_loaded_for_all_records post = Post.create!(:title => 'foo', :body => "I like cars!") SpecialComment.create!(:body => 'Come on!', :post => post) first_category = Category.create! :name => 'First!', :posts => [post] second_category = Category.create! :name => 'Second!', :posts => [post] categories = Category.where(:id => [first_category.id, second_category.id]).includes(:posts => :special_comments) assert_equal categories.map { |category| category.posts.first.special_comments.loaded? }, [true, true] end def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once author_id = authors(:david).id author = assert_queries(3) { Author.all.merge!(:includes => {:posts_with_comments => :comments}).find(author_id) } # find the author, then find the posts, then find the comments author.posts_with_comments.each do |post_with_comments| assert_equal post_with_comments.comments.length, post_with_comments.comments.count assert_nil post_with_comments.comments.to_a.uniq! end end def test_finding_with_includes_on_has_one_association_with_same_include_includes_only_once author = authors(:david) post = author.post_about_thinking_with_last_comment last_comment = post.last_comment author = assert_queries(3) { Author.all.merge!(:includes => {:post_about_thinking_with_last_comment => :last_comment}).find(author.id)} # find the author, then find the posts, then find the comments assert_no_queries do assert_equal post, author.post_about_thinking_with_last_comment assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment end end def test_finding_with_includes_on_belongs_to_association_with_same_include_includes_only_once post = posts(:welcome) author = post.author author_address = author.author_address post = assert_queries(3) { Post.all.merge!(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author, then find the address assert_no_queries do assert_equal author, post.author_with_address assert_equal author_address, post.author_with_address.author_address end end def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once post = posts(:welcome) post.update!(author: nil) post = assert_queries(1) { Post.all.merge!(includes: {author_with_address: :author_address}).find(post.id) } # find the post, then find the author which is null so no query for the author or address assert_no_queries do assert_equal nil, post.author_with_address end end def test_finding_with_includes_on_null_belongs_to_polymorphic_association sponsor = sponsors(:moustache_club_sponsor_for_groucho) sponsor.update!(sponsorable: nil) sponsor = assert_queries(1) { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) } assert_no_queries do assert_equal nil, sponsor.sponsorable end end def test_finding_with_includes_on_empty_polymorphic_type_column sponsor = sponsors(:moustache_club_sponsor_for_groucho) sponsor.update!(sponsorable_type: '', sponsorable_id: nil) # sponsorable_type column might be declared NOT NULL sponsor = assert_queries(1) do assert_nothing_raised { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) } end assert_no_queries do assert_equal nil, sponsor.sponsorable end end def test_loading_from_an_association posts = authors(:david).posts.merge(:includes => :comments, :order => "posts.id").to_a assert_equal 2, posts.first.comments.size end def test_loading_from_an_association_that_has_a_hash_of_conditions assert_nothing_raised do Author.all.merge!(:includes => :hello_posts_with_hash_conditions).to_a end assert !Author.all.merge!(:includes => :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty? end def test_loading_with_no_associations assert_nil Post.all.merge!(:includes => :author).find(posts(:authorless).id).author end # Regression test for 21c75e5 def test_nested_loading_does_not_raise_exception_when_association_does_not_exist assert_nothing_raised do Post.all.merge!(:includes => {:author => :author_addresss}).find(posts(:authorless).id) end end def test_three_level_nested_preloading_does_not_raise_exception_when_association_does_not_exist post_id = Comment.where(author_id: nil).where.not(post_id: nil).first.post_id assert_nothing_raised do Post.preload(:comments => [{:author => :essays}]).find(post_id) end end def test_nested_loading_through_has_one_association aa = AuthorAddress.all.merge!(:includes => {:author => :posts}).find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order aa = AuthorAddress.all.merge!(:includes => {:author => :posts}, :order => 'author_addresses.id').find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_association aa = AuthorAddress.all.merge!(:includes => {:author => :posts}, :order => 'authors.id').find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_nested_association aa = AuthorAddress.all.merge!(:includes => {:author => :posts}, :order => 'posts.id').find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions aa = AuthorAddress.references(:author_addresses).merge( :includes => {:author => :posts}, :where => "author_addresses.id > 0" ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions_on_association aa = AuthorAddress.references(:authors).merge( :includes => {:author => :posts}, :where => "authors.id > 0" ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions_on_nested_association aa = AuthorAddress.references(:posts).merge( :includes => {:author => :posts}, :where => "posts.id > 0" ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_eager_association_loading_with_belongs_to_and_foreign_keys pets = Pet.all.merge!(:includes => :owner).to_a assert_equal 4, pets.length end def test_eager_association_loading_with_belongs_to comments = Comment.all.merge!(:includes => :post).to_a assert_equal 11, comments.length titles = comments.map { |c| c.post.title } assert titles.include?(posts(:welcome).title) assert titles.include?(posts(:sti_post_and_comments).title) end def test_eager_association_loading_with_belongs_to_and_limit comments = Comment.all.merge!(:includes => :post, :limit => 5, :order => 'comments.id').to_a assert_equal 5, comments.length assert_equal [1,2,3,5,6], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_conditions comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').to_a assert_equal 3, comments.length assert_equal [5,6,7], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset comments = Comment.all.merge!(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').to_a assert_equal 3, comments.length assert_equal [3,5,6], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').to_a assert_equal 3, comments.length assert_equal [6,7,8], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array comments = Comment.all.merge!(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').to_a assert_equal 3, comments.length assert_equal [6,7,8], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name assert_nothing_raised do Comment.includes(:post).references(:posts).where('posts.id = ?', 4) end end def test_eager_association_loading_with_belongs_to_and_conditions_hash comments = [] assert_nothing_raised do comments = Comment.all.merge!(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').to_a end assert_equal 3, comments.length assert_equal [5,6,7], comments.collect { |c| c.id } assert_no_queries do comments.first.post end end def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do Comment.includes(:post).references(:posts).where("#{quoted_posts_id} = ?", 4) end end def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name assert_nothing_raised do Comment.all.merge!(:includes => :post, :order => 'posts.id').to_a end end def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do Comment.includes(:post).references(:posts).order(quoted_posts_id) end end def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').to_a assert_equal 1, posts.length assert_equal [1], posts.collect { |p| p.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').to_a assert_equal 1, posts.length assert_equal [2], posts.collect { |p| p.id } end def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name author_favorite = AuthorFavorite.all.merge!(:includes => :favorite_author).first assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author } end def test_eager_load_belongs_to_quotes_table_and_column_names job = Job.includes(:ideal_reference).find jobs(:unicyclist).id references(:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_unicyclist), job.ideal_reference} end def test_eager_load_has_one_quotes_table_and_column_names michael = Person.all.merge!(:includes => :favourite_reference).find(people(:michael).id) references(:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference} end def test_eager_load_has_many_quotes_table_and_column_names michael = Person.all.merge!(:includes => :references).find(people(:michael).id) references(:michael_magician,:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) } end def test_eager_load_has_many_through_quotes_table_and_column_names michael = Person.all.merge!(:includes => :jobs).find(people(:michael).id) jobs(:magician, :unicyclist) assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) } end def test_eager_load_has_many_with_string_keys subscriptions = subscriptions(:webster_awdr, :webster_rfr) subscriber =Subscriber.all.merge!(:includes => :subscriptions).find(subscribers(:second).id) assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id) end def test_string_id_column_joins s = Subscriber.create! do |c| c.id = "PL" end b = Book.create! Subscription.create!(:subscriber_id => "PL", :book_id => b.id) s.reload s.book_ids = s.book_ids end def test_eager_load_has_many_through_with_string_keys books = books(:awdr, :rfr) subscriber = Subscriber.all.merge!(:includes => :books).find(subscribers(:second).id) assert_equal books, subscriber.books.sort_by(&:id) end def test_eager_load_belongs_to_with_string_keys subscriber = subscribers(:second) subscription = Subscription.all.merge!(:includes => :subscriber).find(subscriptions(:webster_awdr).id) assert_equal subscriber, subscription.subscriber end def test_eager_association_loading_with_explicit_join posts = Post.all.merge!(:includes => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id').to_a assert_equal 1, posts.length end def test_eager_with_has_many_through posts_with_comments = people(:michael).posts.merge(:includes => :comments, :order => 'posts.id').to_a posts_with_author = people(:michael).posts.merge(:includes => :author, :order => 'posts.id').to_a posts_with_comments_and_author = people(:michael).posts.merge(:includes => [ :comments, :author ], :order => 'posts.id').to_a assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum + post.comments.size } assert_equal authors(:david), assert_no_queries { posts_with_author.first.author } assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author } end def test_eager_with_has_many_through_a_belongs_to_association author = authors(:mary) Post.create!(:author => author, :title => "TITLE", :body => "BODY") author.author_favorites.create(:favorite_author_id => 1) author.author_favorites.create(:favorite_author_id => 2) posts_with_author_favorites = author.posts.merge(:includes => :author_favorites).to_a assert_no_queries { posts_with_author_favorites.first.author_favorites.first.author_id } end def test_eager_with_has_many_through_an_sti_join_model author = Author.all.merge!(:includes => :special_post_comments, :order => 'authors.id').first assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments } end def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both author = Author.all.merge!(:includes => :special_nonexistant_post_comments, :order => 'authors.id').first assert_equal [], author.special_nonexistant_post_comments end def test_eager_with_has_many_through_join_model_with_conditions assert_equal Author.all.merge!(:includes => :hello_post_comments, :order => 'authors.id').first.hello_post_comments.sort_by(&:id), Author.all.merge!(:order => 'authors.id').first.hello_post_comments.sort_by(&:id) end def test_eager_with_has_many_through_join_model_with_conditions_on_top_level assert_equal comments(:more_greetings), Author.all.merge!(:includes => :comments_with_order_and_conditions).find(authors(:david).id).comments_with_order_and_conditions.first end def test_eager_with_has_many_through_join_model_with_include author_comments = Author.all.merge!(:includes => :comments_with_include).find(authors(:david).id).comments_with_include.to_a assert_no_queries do author_comments.first.post.title end end def test_eager_with_has_many_through_with_conditions_join_model_with_include post_tags = Post.find(posts(:welcome).id).misc_tags eager_post_tags = Post.all.merge!(:includes => :misc_tags).find(1).misc_tags assert_equal post_tags, eager_post_tags end def test_eager_with_has_many_through_join_model_ignores_default_includes assert_nothing_raised do authors(:david).comments_on_posts_with_default_include.to_a end end def test_eager_with_has_many_and_limit posts = Post.all.merge!(:order => 'posts.id asc', :includes => [ :author, :comments ], :limit => 2).to_a assert_equal 2, posts.size assert_equal 3, posts.inject(0) { |sum, post| sum + post.comments.size } end def test_eager_with_has_many_and_limit_and_conditions posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a assert_equal 2, posts.size assert_equal [4,5], posts.collect { |p| p.id } end def test_eager_with_has_many_and_limit_and_conditions_array posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a assert_equal 2, posts.size assert_equal [4,5], posts.collect { |p| p.id } end def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers posts = Post.includes(:author, :comments).limit(2).references(:author).where("authors.name = ?", 'David') assert_equal 2, posts.size count = Post.includes(:author, :comments).limit(2).references(:author).where("authors.name = ?", 'David').count assert_equal posts.size, count end def test_eager_with_has_many_and_limit_and_high_offset posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).to_a assert_equal 0, posts.size end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions assert_queries(1) do posts = Post.references(:authors, :comments). merge(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]).to_a assert_equal 0, posts.size end end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions assert_queries(1) do posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David', 'comments.body' => 'go crazy' }).to_a assert_equal 0, posts.size end end def test_count_eager_with_has_many_and_limit_and_high_offset posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).count(:all) assert_equal 0, posts end def test_eager_with_has_many_and_limit_with_no_results posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.title = 'magic forest'").to_a assert_equal 0, posts.size end def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional author = authors(:david) author_posts_without_comments = author.posts.select { |post| post.comments.blank? } assert_equal author_posts_without_comments.size, author.posts.includes(:comments).where('comments.id is null').references(:comments).count end def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional person = people(:michael) person_posts_without_comments = person.posts.select { |post| post.comments.blank? } assert_equal person_posts_without_comments.size, person.posts_with_no_comments.count end def test_eager_with_has_and_belongs_to_many_and_limit posts = Post.all.merge!(:includes => :categories, :order => "posts.id", :limit => 3).to_a assert_equal 3, posts.size assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size assert posts[0].categories.include?(categories(:technology)) assert posts[1].categories.include?(categories(:general)) end # Since the preloader for habtm gets raw row hashes from the database and then # instantiates them, this test ensures that it only instantiates one actual # object per record from the database. def test_has_and_belongs_to_many_should_not_instantiate_same_records_multiple_times welcome = posts(:welcome) categories = Category.includes(:posts) general = categories.find { |c| c == categories(:general) } technology = categories.find { |c| c == categories(:technology) } post1 = general.posts.to_a.find { |p| p == welcome } post2 = technology.posts.to_a.find { |p| p == welcome } assert_equal post1.object_id, post2.object_id end def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers posts = authors(:david).posts .includes(:comments) .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'") .references(:comments) .limit(2) .to_a assert_equal 2, posts.size count = Post.includes(:comments, :author) .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')") .references(:authors, :comments) .limit(2) .count assert_equal count, posts.size end def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers posts = nil Post.includes(:comments) .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'") .references(:comments) .scoping do posts = authors(:david).posts.limit(2).to_a assert_equal 2, posts.size end Post.includes(:comments, :author) .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')") .references(:authors, :comments) .scoping do count = Post.limit(2).count assert_equal count, posts.size end end def test_eager_association_loading_with_habtm posts = Post.all.merge!(:includes => :categories, :order => "posts.id").to_a assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size assert posts[0].categories.include?(categories(:technology)) assert posts[1].categories.include?(categories(:general)) end def test_eager_with_inheritance SpecialPost.all.merge!(:includes => [ :comments ]).to_a end def test_eager_has_one_with_association_inheritance post = Post.all.merge!(:includes => [ :very_special_comment ]).find(4) assert_equal "VerySpecialComment", post.very_special_comment.class.to_s end def test_eager_has_many_with_association_inheritance post = Post.all.merge!(:includes => [ :special_comments ]).find(4) post.special_comments.each do |special_comment| assert special_comment.is_a?(SpecialComment) end end def test_eager_habtm_with_association_inheritance post = Post.all.merge!(:includes => [ :special_categories ]).find(6) assert_equal 1, post.special_categories.size post.special_categories.each do |special_category| assert_equal "SpecialCategory", special_category.class.to_s end end def test_eager_with_has_one_dependent_does_not_destroy_dependent assert_not_nil companies(:first_firm).account f = Firm.all.merge!(:includes => :account, :where => ["companies.name = ?", "37signals"]).first assert_not_nil f.account assert_equal companies(:first_firm, :reload).account, f.account end def test_eager_with_multi_table_conditional_properly_counts_the_records_when_using_size author = authors(:david) posts_with_no_comments = author.posts.select { |post| post.comments.blank? } assert_equal posts_with_no_comments.size, author.posts_with_no_comments.size assert_equal posts_with_no_comments, author.posts_with_no_comments end def test_eager_with_invalid_association_reference assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { Post.all.merge!(:includes=> :monkeys ).find(6) } assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { Post.all.merge!(:includes=>[ :monkeys ]).find(6) } assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { Post.all.merge!(:includes=>[ 'monkeys' ]).find(6) } assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { Post.all.merge!(:includes=>[ :monkeys, :elephants ]).find(6) } end def test_eager_with_default_scope developer = EagerDeveloperWithDefaultScope.where(:name => 'David').first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) end end def test_eager_with_default_scope_as_class_method developer = EagerDeveloperWithClassMethodDefaultScope.where(:name => 'David').first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) end end def test_eager_with_default_scope_as_lambda developer = EagerDeveloperWithLambdaDefaultScope.where(:name => 'David').first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) end end def test_eager_with_default_scope_as_block # warm up the habtm cache EagerDeveloperWithBlockDefaultScope.where(:name => 'David').first.projects developer = EagerDeveloperWithBlockDefaultScope.where(:name => 'David').first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) end end def test_eager_with_default_scope_as_callable developer = EagerDeveloperWithCallableDefaultScope.where(:name => 'David').first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) end end def find_all_ordered(className, include=nil) className.all.merge!(:order=>"#{className.table_name}.#{className.primary_key}", :includes=>include).to_a end def test_limited_eager_with_order assert_equal( posts(:thinking, :sti_comments), Post.all.merge!( :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, :order => 'UPPER(posts.title)', :limit => 2, :offset => 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1 ).to_a ) end def test_limited_eager_with_multiple_order_columns assert_equal( posts(:thinking, :sti_comments), Post.all.merge!( :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1 ).to_a ) end def test_limited_eager_with_numeric_in_association assert_equal( people(:david, :susan), Person.references(:number1_fans_people).merge( :includes => [:readers, :primary_contact, :number1_fan], :where => "number1_fans_people.first_name like 'M%'", :order => 'people.id', :limit => 2, :offset => 0 ).to_a ) end def test_preload_with_interpolation assert_deprecated do post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id) assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions end assert_deprecated do post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id) assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions end end def test_polymorphic_type_condition post = Post.all.merge!(:includes => :taggings).find(posts(:thinking).id) assert post.taggings.include?(taggings(:thinking_general)) post = SpecialPost.all.merge!(:includes => :taggings).find(posts(:thinking).id) assert post.taggings.include?(taggings(:thinking_general)) end def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm # Eager includes of has many and habtm associations aren't necessarily sorted in the same way def assert_equal_after_sort(item1, item2, item3 = nil) assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3 end # Test regular association, association with conditions, association with # STI, and association with conditions assured not to be true post_types = [:posts, :other_posts, :special_posts] # test both has_many and has_and_belongs_to_many [Author, Category].each do |className| d1 = find_all_ordered(className) # test including all post types at once d2 = find_all_ordered(className, post_types) d1.each_index do |i| assert_equal(d1[i], d2[i]) assert_equal_after_sort(d1[i].posts, d2[i].posts) post_types[1..-1].each do |post_type| # test including post_types together d3 = find_all_ordered(className, [:posts, post_type]) assert_equal(d1[i], d3[i]) assert_equal_after_sort(d1[i].posts, d3[i].posts) assert_equal_after_sort(d1[i].send(post_type), d2[i].send(post_type), d3[i].send(post_type)) end end end end def test_eager_with_multiple_associations_with_same_table_has_one d1 = find_all_ordered(Firm) d2 = find_all_ordered(Firm, :account) d1.each_index do |i| assert_equal(d1[i], d2[i]) assert_equal(d1[i].account, d2[i].account) end end def test_eager_with_multiple_associations_with_same_table_belongs_to firm_types = [:firm, :firm_with_basic_id, :firm_with_other_name, :firm_with_condition] d1 = find_all_ordered(Client) d2 = find_all_ordered(Client, firm_types) d1.each_index do |i| assert_equal(d1[i], d2[i]) firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) } end end def test_eager_with_valid_association_as_string_not_symbol assert_nothing_raised { Post.all.merge!(:includes => 'comments').to_a } end def test_eager_with_floating_point_numbers assert_queries(2) do # Before changes, the floating point numbers will be interpreted as table names and will cause this to run in one query Comment.all.merge!(:where => "123.456 = 123.456", :includes => :post).to_a end end def test_preconfigured_includes_with_belongs_to author = posts(:welcome).author_with_posts assert_no_queries {assert_equal 5, author.posts.size} end def test_preconfigured_includes_with_has_one comment = posts(:sti_comments).very_special_comment_with_post assert_no_queries {assert_equal posts(:sti_comments), comment.post} end def test_eager_association_with_scope_with_joins assert_nothing_raised do Post.includes(:very_special_comment_with_post_with_joins).to_a end end def test_preconfigured_includes_with_has_many posts = authors(:david).posts_with_comments one = posts.detect { |p| p.id == 1 } assert_no_queries do assert_equal 5, posts.size assert_equal 2, one.comments.size end end def test_preconfigured_includes_with_habtm posts = authors(:david).posts_with_categories one = posts.detect { |p| p.id == 1 } assert_no_queries do assert_equal 5, posts.size assert_equal 2, one.categories.size end end def test_preconfigured_includes_with_has_many_and_habtm posts = authors(:david).posts_with_comments_and_categories one = posts.detect { |p| p.id == 1 } assert_no_queries do assert_equal 5, posts.size assert_equal 2, one.comments.size assert_equal 2, one.categories.size end end def test_count_with_include assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count end def test_association_loading_notification notifications = messages_for('instantiation.active_record') do Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size end message = notifications.first payload = message.last count = Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size # eagerloaded row count should be greater than just developer count assert_operator payload[:record_count], :>, count assert_equal Developer.name, payload[:class_name] end def test_base_messages notifications = messages_for('instantiation.active_record') do Developer.all.to_a end message = notifications.first payload = message.last assert_equal Developer.all.to_a.count, payload[:record_count] assert_equal Developer.name, payload[:class_name] end def messages_for(name) notifications = [] ActiveSupport::Notifications.subscribe(name) do |*args| notifications << args end yield notifications ensure ActiveSupport::Notifications.unsubscribe(name) end def test_load_with_sti_sharing_association assert_queries(2) do #should not do 1 query per subclass Comment.includes(:post).to_a end end def test_conditions_on_join_table_with_include_and_limit assert_equal 3, Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size end def test_dont_create_temporary_active_record_instances Developer.instance_count = 0 developers = Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a assert_equal developers.count, Developer.instance_count end def test_order_on_join_table_with_include_and_limit assert_equal 5, Developer.all.merge!(:includes => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).to_a.size end def test_eager_loading_with_order_on_joined_table_preloads posts = assert_queries(2) do Post.all.merge!(:joins => :comments, :includes => :author, :order => 'comments.id DESC').to_a end assert_equal posts(:eager_other), posts[1] assert_equal authors(:mary), assert_no_queries { posts[1].author} end def test_eager_loading_with_conditions_on_joined_table_preloads posts = assert_queries(2) do Post.all.merge!(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} posts = assert_queries(2) do Post.all.merge!(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} posts = assert_queries(2) do Post.all.merge!(:includes => :author, :joins => {:taggings => :tag}, :where => "tags.name = 'General'", :order => 'posts.id').to_a end assert_equal posts(:welcome, :thinking), posts posts = assert_queries(2) do Post.all.merge!(:includes => :author, :joins => {:taggings => {:tag => :taggings}}, :where => "taggings_tags.super_tag_id=2", :order => 'posts.id').to_a end assert_equal posts(:welcome, :thinking), posts end def test_preload_has_many_with_association_condition_and_default_scope post = Post.create!(:title => 'Beaches', :body => "I like beaches!") Reader.create! :person => people(:david), :post => post LazyReader.create! :person => people(:susan), :post => post assert_equal 1, post.lazy_readers.to_a.size assert_equal 2, post.lazy_readers_skimmers_or_not.to_a.size post_with_readers = Post.includes(:lazy_readers_skimmers_or_not).find(post.id) assert_equal 2, post_with_readers.lazy_readers_skimmers_or_not.to_a.size end def test_eager_loading_with_conditions_on_string_joined_table_preloads posts = assert_queries(2) do Post.all.merge!(:select => 'distinct posts.*', :includes => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :where => "comments.body like 'Thank you%'", :order => 'posts.id').to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} posts = assert_queries(2) do Post.all.merge!(:select => 'distinct posts.*', :includes => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :where => "comments.body like 'Thank you%'", :order => 'posts.id').to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} end def test_eager_loading_with_select_on_joined_table_preloads posts = assert_queries(2) do Post.all.merge!(:select => 'posts.*, authors.name as author_name', :includes => :comments, :joins => :author, :order => 'posts.id').to_a end assert_equal 'David', posts[0].author_name assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments} end def test_eager_loading_with_conditions_on_join_model_preloads authors = assert_queries(2) do Author.all.merge!(:includes => :author_address, :joins => :comments, :where => "posts.title like 'Welcome%'").to_a end assert_equal authors(:david), authors[0] assert_equal author_addresses(:david_address), authors[0].author_address end def test_preload_belongs_to_uses_exclusive_scope people = Person.males.merge(:includes => :primary_contact).to_a assert_not_equal people.length, 0 people.each do |person| assert_no_queries {assert_not_nil person.primary_contact} assert_equal Person.find(person.id).primary_contact, person.primary_contact end end def test_preload_has_many_uses_exclusive_scope people = Person.males.includes(:agents).to_a people.each do |person| assert_equal Person.find(person.id).agents, person.agents end end def test_preload_has_many_using_primary_key expected = Firm.first.clients_using_primary_key.to_a firm = Firm.includes(:clients_using_primary_key).first assert_no_queries do assert_equal expected, firm.clients_using_primary_key end end def test_include_has_many_using_primary_key expected = Firm.find(1).clients_using_primary_key.sort_by(&:name) # Oracle adapter truncates alias to 30 characters if current_adapter?(:OracleAdapter) firm = Firm.all.merge!(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name').find(1) else firm = Firm.all.merge!(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name').find(1) end assert_no_queries do assert_equal expected, firm.clients_using_primary_key end end def test_preload_has_one_using_primary_key expected = accounts(:signals37) firm = Firm.all.merge!(:includes => :account_using_primary_key, :order => 'companies.id').first assert_no_queries do assert_equal expected, firm.account_using_primary_key end end def test_include_has_one_using_primary_key expected = accounts(:signals37) firm = Firm.all.merge!(:includes => :account_using_primary_key, :order => 'accounts.id').to_a.detect {|f| f.id == 1} assert_no_queries do assert_equal expected, firm.account_using_primary_key end end def test_preloading_empty_belongs_to c = Client.create!(:name => 'Foo', :client_of => Company.maximum(:id) + 1) client = assert_queries(2) { Client.preload(:firm).find(c.id) } assert_no_queries { assert_nil client.firm } end def test_preloading_empty_belongs_to_polymorphic t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general)) tagging = assert_queries(2) { Tagging.preload(:taggable).find(t.id) } assert_no_queries { assert_nil tagging.taggable } end def test_preloading_through_empty_belongs_to c = Client.create!(:name => 'Foo', :client_of => Company.maximum(:id) + 1) client = assert_queries(2) { Client.preload(:accounts).find(c.id) } assert_no_queries { assert client.accounts.empty? } end def test_preloading_has_many_through_with_uniq mary = Author.includes(:unique_categorized_posts).where(:id => authors(:mary).id).first assert_equal 1, mary.unique_categorized_posts.length assert_equal 1, mary.unique_categorized_post_ids.length end def test_preloading_has_one_using_reorder klass = Class.new(ActiveRecord::Base) do def self.name; "TempAuthor"; end self.table_name = "authors" has_one :post, class_name: "PostWithDefaultScope", foreign_key: :author_id has_one :reorderd_post, -> { reorder(title: :desc) }, class_name: "PostWithDefaultScope", foreign_key: :author_id end author = klass.first # PRECONDITION: make sure ordering results in different results assert_not_equal author.post, author.reorderd_post preloaded_reorderd_post = klass.preload(:reorderd_post).first.reorderd_post assert_equal author.reorderd_post, preloaded_reorderd_post assert_equal Post.order(title: :desc).first.title, preloaded_reorderd_post.title end def test_preloading_polymorphic_with_custom_foreign_type sponsor = sponsors(:moustache_club_sponsor_for_groucho) groucho = members(:groucho) sponsor = assert_queries(2) { Sponsor.includes(:thing).where(:id => sponsor.id).first } assert_no_queries { assert_equal groucho, sponsor.thing } end def test_joins_with_includes_should_preload_via_joins post = assert_queries(1) { Post.includes(:comments).joins(:comments).order('posts.id desc').to_a.first } assert_queries(0) do assert_not_equal 0, post.comments.to_a.count end end def test_join_eager_with_empty_order_should_generate_valid_sql assert_nothing_raised(ActiveRecord::StatementInvalid) do Post.includes(:comments).order("").where(:comments => {:body => "Thank you for the welcome"}).first end end def test_join_eager_with_nil_order_should_generate_valid_sql assert_nothing_raised(ActiveRecord::StatementInvalid) do Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first end end def test_deep_including_through_habtm # warm up habtm cache posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a posts[0].categories[0].categorizations.length posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length } end test "scoping with a circular preload" do assert_equal Comment.find(1), Comment.preload(:post => :comments).scoping { Comment.find(1) } end test "circular preload does not modify unscoped" do expected = FirstPost.unscoped.find(2) FirstPost.preload(:comments => :first_post).find(1) assert_equal expected, FirstPost.unscoped.find(2) end test "preload ignores the scoping" do assert_equal( Comment.find(1).post, Post.where('1 = 0').scoping { Comment.preload(:post).find(1).post } ) end test "deep preload" do post = Post.preload(author: :posts, comments: :post).first assert_predicate post.author.association(:posts), :loaded? assert_predicate post.comments.first.association(:post), :loaded? end test "preloading does not cache has many association subset when preloaded with a through association" do author = Author.includes(:comments_with_order_and_conditions, :posts).first assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size } assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" } end test "preloading a through association twice does not reset it" do members = Member.includes(current_membership: :club).includes(:club).to_a assert_no_queries { assert_equal 3, members.map(&:current_membership).map(&:club).size } end test "works in combination with order(:symbol) and reorder(:symbol)" do author = Author.includes(:posts).references(:posts).order(:name).find_by('posts.title IS NOT NULL') assert_equal authors(:bob), author author = Author.includes(:posts).references(:posts).reorder(:name).find_by('posts.title IS NOT NULL') assert_equal authors(:bob), author end test "preloading with a polymorphic association and using the existential predicate but also using a select" do assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer assert_nothing_raised do authors(:david).essays.includes(:writer).select(:name).any? end end test "preloading the same association twice works" do Member.create! members = Member.preload(:current_membership).includes(current_membership: :club).all.to_a assert_no_queries { members_with_membership = members.select(&:current_membership) assert_equal 3, members_with_membership.map(&:current_membership).map(&:club).size } end test "preloading with a polymorphic association and using the existential predicate" do assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer assert_nothing_raised do authors(:david).essays.includes(:writer).any? end end test "preloading associations with string joins and order references" do author = assert_queries(2) { Author.includes(:posts).joins("LEFT JOIN posts ON posts.author_id = authors.id").order("posts.title DESC").first } assert_no_queries { assert_equal 5, author.posts.size } end test "including associations with where.not adds implicit references" do author = assert_queries(2) { Author.includes(:posts).where.not(posts: { title: 'Welcome to the weblog'} ).last } assert_no_queries { assert_equal 2, author.posts.size } end test "including association based on sql condition and no database column" do assert_equal pets(:parrot), Owner.including_last_pet.first.last_pet end test "include instance dependent associations is deprecated" do message = "association scope 'posts_with_signature' is" assert_deprecated message do begin Author.includes(:posts_with_signature).to_a rescue NoMethodError # it's expected that preloading of this association fails end end assert_deprecated message do Author.preload(:posts_with_signature).to_a rescue NoMethodError end assert_deprecated message do Author.eager_load(:posts_with_signature).to_a end end test "associations with extensions are not instance dependent" do assert_not_deprecated do Author.includes(:posts_with_extension).to_a end end test "including associations with extensions and an instance dependent scope is deprecated" do assert_deprecated do Author.includes(:posts_with_extension_and_instance).to_a end end test "preloading readonly association" do # has-one firm = Firm.where(id: "1").preload(:readonly_account).first! assert firm.readonly_account.readonly? # has_and_belongs_to_many project = Project.where(id: "2").preload(:readonly_developers).first! assert project.readonly_developers.first.readonly? # has-many :through david = Author.where(id: "1").preload(:readonly_comments).first! assert david.readonly_comments.first.readonly? end test "eager-loading readonly association" do skip "eager_load does not yet preserve readonly associations" # has-one firm = Firm.where(id: "1").eager_load(:readonly_account).first! assert firm.readonly_account.readonly? # has_and_belongs_to_many project = Project.where(id: "2").eager_load(:readonly_developers).first! assert project.readonly_developers.first.readonly? # has-many :through david = Author.where(id: "1").eager_load(:readonly_comments).first! assert david.readonly_comments.first.readonly? end test "preloading a polymorphic association with references to the associated table" do post = Post.includes(:tags).references(:tags).where('tags.name = ?', 'General').first assert_equal posts(:welcome), post end test "eager-loading a polymorphic association with references to the associated table" do post = Post.eager_load(:tags).where('tags.name = ?', 'General').first assert_equal posts(:welcome), post end end rails-4.2.6/activerecord/test/cases/associations/extension_test.rb000066400000000000000000000057171266740050600255030ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/comment' require 'models/project' require 'models/developer' require 'models/computer' require 'models/company_in_module' class AssociationsExtensionsTest < ActiveRecord::TestCase fixtures :projects, :developers, :developers_projects, :comments, :posts def test_extension_on_has_many assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent end def test_extension_on_habtm assert_equal projects(:action_controller), developers(:david).projects.find_most_recent end def test_named_extension_on_habtm assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent end def test_named_two_extensions_on_habtm assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_twice.find_most_recent assert_equal projects(:active_record), developers(:david).projects_extended_by_name_twice.find_least_recent end def test_named_extension_and_block_on_habtm assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_and_block.find_most_recent assert_equal projects(:active_record), developers(:david).projects_extended_by_name_and_block.find_least_recent end def test_extension_with_scopes assert_equal comments(:greetings), posts(:welcome).comments.offset(1).find_most_recent assert_equal comments(:greetings), posts(:welcome).comments.not_again.find_most_recent end def test_marshalling_extensions david = developers(:david) assert_equal projects(:action_controller), david.projects.find_most_recent marshalled = Marshal.dump(david) # Marshaling an association shouldn't make it unusable by wiping its reflection. assert_not_nil david.association(:projects).reflection david_too = Marshal.load(marshalled) assert_equal projects(:action_controller), david_too.projects.find_most_recent end def test_marshalling_named_extensions david = developers(:david) assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent marshalled = Marshal.dump(david) david = Marshal.load(marshalled) assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent end def test_extension_name extend!(Developer) extend!(MyApplication::Business::Developer) assert Object.const_get 'DeveloperAssociationNameAssociationExtension' assert MyApplication::Business.const_get 'DeveloperAssociationNameAssociationExtension' end def test_proxy_association_after_scoped post = posts(:welcome) assert_equal post.association(:comments), post.comments.the_association assert_equal post.association(:comments), post.comments.where('1=1').the_association end private def extend!(model) builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, nil, {}) { } builder.define_extensions(model) end end rails-4.2.6/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb000066400000000000000000001012771266740050600331000ustar00rootroot00000000000000require "cases/helper" require 'models/developer' require 'models/computer' require 'models/project' require 'models/company' require 'models/course' require 'models/customer' require 'models/order' require 'models/categorization' require 'models/category' require 'models/post' require 'models/author' require 'models/tag' require 'models/tagging' require 'models/parrot' require 'models/person' require 'models/pirate' require 'models/professor' require 'models/treasure' require 'models/price_estimate' require 'models/club' require 'models/member' require 'models/membership' require 'models/sponsor' require 'models/country' require 'models/treaty' require 'models/vertex' require 'models/publisher' require 'models/publisher/article' require 'models/publisher/magazine' require 'active_support/core_ext/string/conversions' class ProjectWithAfterCreateHook < ActiveRecord::Base self.table_name = 'projects' has_and_belongs_to_many :developers, :class_name => "DeveloperForProjectWithAfterCreateHook", :join_table => "developers_projects", :foreign_key => "project_id", :association_foreign_key => "developer_id" after_create :add_david def add_david david = DeveloperForProjectWithAfterCreateHook.find_by_name('David') david.projects << self end end class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base self.table_name = 'developers' has_and_belongs_to_many :projects, :class_name => "ProjectWithAfterCreateHook", :join_table => "developers_projects", :association_foreign_key => "project_id", :foreign_key => "developer_id" end class ProjectWithSymbolsForKeys < ActiveRecord::Base self.table_name = 'projects' has_and_belongs_to_many :developers, :class_name => "DeveloperWithSymbolsForKeys", :join_table => :developers_projects, :foreign_key => :project_id, :association_foreign_key => "developer_id" end class DeveloperWithSymbolsForKeys < ActiveRecord::Base self.table_name = 'developers' has_and_belongs_to_many :projects, :class_name => "ProjectWithSymbolsForKeys", :join_table => :developers_projects, :association_foreign_key => :project_id, :foreign_key => "developer_id" end class SubDeveloper < Developer self.table_name = 'developers' has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :foreign_key => "project_id", :association_foreign_key => "developer_id" end class DeveloperWithSymbolClassName < Developer has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys end class DeveloperWithExtendOption < Developer module NamedExtension def category 'sns' end end has_and_belongs_to_many :projects, extend: NamedExtension end class ProjectUnscopingDavidDefaultScope < ActiveRecord::Base self.table_name = 'projects' has_and_belongs_to_many :developers, -> { unscope(where: 'name') }, class_name: "LazyBlockDeveloperCalledDavid", join_table: "developers_projects", foreign_key: "project_id", association_foreign_key: "developer_id" end class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers def setup_data_for_habtm_case ActiveRecord::Base.connection.execute('delete from countries_treaties') country = Country.new(:name => 'India') country.country_id = 'c1' country.save! treaty = Treaty.new(:name => 'peace') treaty.treaty_id = 't1' country.treaties << treaty end def test_marshal_dump post = posts :welcome preloaded = Post.includes(:categories).find post.id assert_equal preloaded, Marshal.load(Marshal.dump(preloaded)) end def test_should_property_quote_string_primary_keys setup_data_for_habtm_case con = ActiveRecord::Base.connection sql = 'select * from countries_treaties' record = con.select_rows(sql).last assert_equal 'c1', record[0] assert_equal 't1', record[1] end def test_proper_usage_of_primary_keys_and_join_table setup_data_for_habtm_case assert_equal 'country_id', Country.primary_key assert_equal 'treaty_id', Treaty.primary_key country = Country.first assert_equal 1, country.treaties.count end def test_has_and_belongs_to_many david = Developer.find(1) assert !david.projects.empty? assert_equal 2, david.projects.size active_record = Project.find(1) assert !active_record.developers.empty? assert_equal 3, active_record.developers.size assert active_record.developers.include?(david) end def test_adding_single jamis = Developer.find(2) jamis.projects.reload # causing the collection to load action_controller = Project.find(2) assert_equal 1, jamis.projects.size assert_equal 1, action_controller.developers.size jamis.projects << action_controller assert_equal 2, jamis.projects.size assert_equal 2, jamis.projects(true).size assert_equal 2, action_controller.developers(true).size end def test_adding_type_mismatch jamis = Developer.find(2) assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << nil } assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << 1 } end def test_adding_from_the_project jamis = Developer.find(2) action_controller = Project.find(2) action_controller.developers.reload assert_equal 1, jamis.projects.size assert_equal 1, action_controller.developers.size action_controller.developers << jamis assert_equal 2, jamis.projects(true).size assert_equal 2, action_controller.developers.size assert_equal 2, action_controller.developers(true).size end def test_adding_from_the_project_fixed_timestamp jamis = Developer.find(2) action_controller = Project.find(2) action_controller.developers.reload assert_equal 1, jamis.projects.size assert_equal 1, action_controller.developers.size updated_at = jamis.updated_at action_controller.developers << jamis assert_equal updated_at, jamis.updated_at assert_equal 2, jamis.projects(true).size assert_equal 2, action_controller.developers.size assert_equal 2, action_controller.developers(true).size end def test_adding_multiple aredridel = Developer.new("name" => "Aredridel") aredridel.save aredridel.projects.reload aredridel.projects.push(Project.find(1), Project.find(2)) assert_equal 2, aredridel.projects.size assert_equal 2, aredridel.projects(true).size end def test_adding_a_collection aredridel = Developer.new("name" => "Aredridel") aredridel.save aredridel.projects.reload aredridel.projects.concat([Project.find(1), Project.find(2)]) assert_equal 2, aredridel.projects.size assert_equal 2, aredridel.projects(true).size end def test_habtm_adding_before_save no_of_devels = Developer.count no_of_projects = Project.count aredridel = Developer.new("name" => "Aredridel") aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")]) assert !aredridel.persisted? assert !p.persisted? assert aredridel.save assert aredridel.persisted? assert_equal no_of_devels+1, Developer.count assert_equal no_of_projects+1, Project.count assert_equal 2, aredridel.projects.size assert_equal 2, aredridel.projects(true).size end def test_habtm_saving_multiple_relationships new_project = Project.new("name" => "Grimetime") amount_of_developers = 4 developers = (0...amount_of_developers).collect {|i| Developer.create(:name => "JME #{i}") }.reverse new_project.developer_ids = [developers[0].id, developers[1].id] new_project.developers_with_callback_ids = [developers[2].id, developers[3].id] assert new_project.save new_project.reload assert_equal amount_of_developers, new_project.developers.size assert_equal developers, new_project.developers end def test_habtm_unique_order_preserved assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).non_unique_developers assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).developers end def test_habtm_collection_size_from_build devel = Developer.create("name" => "Fred Wu") devel.projects << Project.create("name" => "Grimetime") devel.projects.build assert_equal 2, devel.projects.size end def test_habtm_collection_size_from_params devel = Developer.new({ projects_attributes: { '0' => {} } }) assert_equal 1, devel.projects.size end def test_build devel = Developer.find(1) proj = assert_no_queries(ignore_none: false) { devel.projects.build("name" => "Projekt") } assert !devel.projects.loaded? assert_equal devel.projects.last, proj assert devel.projects.loaded? assert !proj.persisted? devel.save assert proj.persisted? assert_equal devel.projects.last, proj assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end def test_new_aliased_to_build devel = Developer.find(1) proj = assert_no_queries(ignore_none: false) { devel.projects.new("name" => "Projekt") } assert !devel.projects.loaded? assert_equal devel.projects.last, proj assert devel.projects.loaded? assert !proj.persisted? devel.save assert proj.persisted? assert_equal devel.projects.last, proj assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end def test_build_by_new_record devel = Developer.new(:name => "Marcel", :salary => 75000) devel.projects.build(:name => "Make bed") proj2 = devel.projects.build(:name => "Lie in it") assert_equal devel.projects.last, proj2 assert !proj2.persisted? devel.save assert devel.persisted? assert proj2.persisted? assert_equal devel.projects.last, proj2 assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated end def test_create devel = Developer.find(1) proj = devel.projects.create("name" => "Projekt") assert !devel.projects.loaded? assert_equal devel.projects.last, proj assert !devel.projects.loaded? assert proj.persisted? assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end def test_create_by_new_record devel = Developer.new(:name => "Marcel", :salary => 75000) devel.projects.build(:name => "Make bed") proj2 = devel.projects.build(:name => "Lie in it") assert_equal devel.projects.last, proj2 assert !proj2.persisted? devel.save assert devel.persisted? assert proj2.persisted? assert_equal devel.projects.last, proj2 assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated end def test_creation_respects_hash_condition # in Oracle '' is saved as null therefore need to save ' ' in not null column post = categories(:general).post_with_conditions.build(:body => ' ') assert post.save assert_equal 'Yet Another Testing Title', post.title # in Oracle '' is saved as null therefore need to save ' ' in not null column another_post = categories(:general).post_with_conditions.create(:body => ' ') assert another_post.persisted? assert_equal 'Yet Another Testing Title', another_post.title end def test_uniq_after_the_fact dev = developers(:jamis) dev.projects << projects(:active_record) dev.projects << projects(:active_record) assert_equal 3, dev.projects.size assert_equal 1, dev.projects.distinct.size end def test_uniq_before_the_fact projects(:active_record).developers << developers(:jamis) projects(:active_record).developers << developers(:david) assert_equal 3, projects(:active_record, :reload).developers.size end def test_uniq_option_prevents_duplicate_push project = projects(:active_record) project.developers << developers(:jamis) project.developers << developers(:david) assert_equal 3, project.developers.size project.developers << developers(:david) project.developers << developers(:jamis) assert_equal 3, project.developers.size end def test_uniq_when_association_already_loaded project = projects(:active_record) project.developers << [ developers(:jamis), developers(:david), developers(:jamis), developers(:david) ] assert_equal 3, Project.includes(:developers).find(project.id).developers.size end def test_deleting david = Developer.find(1) active_record = Project.find(1) david.projects.reload assert_equal 2, david.projects.size assert_equal 3, active_record.developers.size david.projects.delete(active_record) assert_equal 1, david.projects.size assert_equal 1, david.projects(true).size assert_equal 2, active_record.developers(true).size end def test_deleting_array david = Developer.find(1) david.projects.reload david.projects.delete(Project.all.to_a) assert_equal 0, david.projects.size assert_equal 0, david.projects(true).size end def test_deleting_all david = Developer.find(1) david.projects.reload david.projects.clear assert_equal 0, david.projects.size assert_equal 0, david.projects(true).size end def test_removing_associations_on_destroy david = DeveloperWithBeforeDestroyRaise.find(1) assert !david.projects.empty? david.destroy assert david.projects.empty? assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty? end def test_destroying david = Developer.find(1) project = Project.find(1) david.projects.reload assert_equal 2, david.projects.size assert_equal 3, project.developers.size assert_no_difference "Project.count" do david.projects.destroy(project) end join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id} AND project_id = #{project.id}") assert join_records.empty? assert_equal 1, david.reload.projects.size assert_equal 1, david.projects(true).size end def test_destroying_many david = Developer.find(1) david.projects.reload projects = Project.all.to_a assert_no_difference "Project.count" do david.projects.destroy(*projects) end join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}") assert join_records.empty? assert_equal 0, david.reload.projects.size assert_equal 0, david.projects(true).size end def test_destroy_all david = Developer.find(1) david.projects.reload assert !david.projects.empty? assert_no_difference "Project.count" do david.projects.destroy_all end join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}") assert join_records.empty? assert david.projects.empty? assert david.projects(true).empty? end def test_destroy_associations_destroys_multiple_associations george = parrots(:george) assert !george.pirates.empty? assert !george.treasures.empty? assert_no_difference "Pirate.count" do assert_no_difference "Treasure.count" do george.destroy_associations end end join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}") assert join_records.empty? assert george.pirates(true).empty? join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}") assert join_records.empty? assert george.treasures(true).empty? end def test_associations_with_conditions assert_equal 3, projects(:active_record).developers.size assert_equal 1, projects(:active_record).developers_named_david.size assert_equal 1, projects(:active_record).developers_named_david_with_hash_conditions.size assert_equal developers(:david), projects(:active_record).developers_named_david.find(developers(:david).id) assert_equal developers(:david), projects(:active_record).developers_named_david_with_hash_conditions.find(developers(:david).id) assert_equal developers(:david), projects(:active_record).salaried_developers.find(developers(:david).id) projects(:active_record).developers_named_david.clear assert_equal 2, projects(:active_record, :reload).developers.size end def test_find_in_association # Using sql assert_equal developers(:david), projects(:active_record).developers.find(developers(:david).id), "SQL find" # Using ruby active_record = projects(:active_record) active_record.developers.reload assert_equal developers(:david), active_record.developers.find(developers(:david).id), "Ruby find" end def test_include_uses_array_include_after_loaded project = projects(:active_record) project.developers.load_target developer = project.developers.first assert_no_queries(ignore_none: false) do assert project.developers.loaded? assert project.developers.include?(developer) end end def test_include_checks_if_record_exists_if_target_not_loaded project = projects(:active_record) developer = project.developers.first project.reload assert ! project.developers.loaded? assert_queries(1) do assert project.developers.include?(developer) end assert ! project.developers.loaded? end def test_include_returns_false_for_non_matching_record_to_verify_scoping project = projects(:active_record) developer = Developer.create :name => "Bryan", :salary => 50_000 assert ! project.developers.loaded? assert ! project.developers.include?(developer) end def test_find_with_merged_options assert_equal 1, projects(:active_record).limited_developers.size assert_equal 1, projects(:active_record).limited_developers.to_a.size assert_equal 3, projects(:active_record).limited_developers.limit(nil).to_a.size end def test_dynamic_find_should_respect_association_order # Developers are ordered 'name DESC, id DESC' high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis') assert_equal high_id_jamis, projects(:active_record).developers.merge(:where => "name = 'Jamis'").first assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis') end def test_find_should_append_to_association_order ordered_developers = projects(:active_record).developers.order('projects.id') assert_equal ['developers.name desc, developers.id desc', 'projects.id'], ordered_developers.order_values end def test_dynamic_find_all_should_respect_readonly_access projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?} projects(:active_record).readonly_developers.each { |d| d.readonly? } end def test_new_with_values_in_collection jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis') david = DeveloperForProjectWithAfterCreateHook.find_by_name('David') project = ProjectWithAfterCreateHook.new(:name => "Cooking with Bertie") project.developers << jamis project.save! project.reload assert project.developers.include?(jamis) assert project.developers.include?(david) end def test_find_in_association_with_options developers = projects(:active_record).developers.to_a assert_equal 3, developers.size assert_equal developers(:poor_jamis), projects(:active_record).developers.where("salary < 10000").first end def test_association_with_extend_option eponine = DeveloperWithExtendOption.create(name: 'Eponine') assert_equal 'sns', eponine.projects.category end def test_replace_with_less david = developers(:david) david.projects = [projects(:action_controller)] assert david.save assert_equal 1, david.projects.length end def test_replace_with_new david = developers(:david) david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")] david.save assert_equal 2, david.projects.length assert !david.projects.include?(projects(:active_record)) end def test_replace_on_new_object new_developer = Developer.new("name" => "Matz") new_developer.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")] new_developer.save assert_equal 2, new_developer.projects.length end def test_consider_type developer = Developer.first special_project = SpecialProject.create("name" => "Special Project") other_project = developer.projects.first developer.special_projects << special_project developer.reload assert developer.projects.include?(special_project) assert developer.special_projects.include?(special_project) assert !developer.special_projects.include?(other_project) end def test_symbol_join_table developer = Developer.first sp = developer.sym_special_projects.create("name" => "omg") developer.reload assert_includes developer.sym_special_projects, sp end def test_update_attributes_after_push_without_duplicate_join_table_rows developer = Developer.new("name" => "Kano") project = SpecialProject.create("name" => "Special Project") assert developer.save developer.projects << project developer.update_columns("name" => "Bruza") assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i SELECT count(*) FROM developers_projects WHERE project_id = #{project.id} AND developer_id = #{developer.id} end_sql end def test_updating_attributes_on_non_rich_associations welcome = categories(:technology).posts.first welcome.title = "Something else" assert welcome.save! end def test_habtm_respects_select categories(:technology).select_testing_posts(true).each do |o| assert_respond_to o, :correctness_marker end assert_respond_to categories(:technology).select_testing_posts.first, :correctness_marker end def test_habtm_selects_all_columns_by_default assert_equal Project.column_names.sort, developers(:david).projects.first.attributes.keys.sort end def test_habtm_respects_select_query_method assert_equal ['id'], developers(:david).projects.select(:id).first.attributes.keys end def test_join_table_alias # FIXME: `references` has no impact on the aliases generated for the join # query. The fact that we pass `:developers_projects_join` to `references` # and that the SQL string contains `developers_projects_join` is merely a # coincidence. assert_equal( 3, Developer.references(:developers_projects_join).merge( :includes => {:projects => :developers}, :where => 'projects_developers_projects_join.joined_on IS NOT NULL' ).to_a.size ) end def test_join_with_group # FIXME: `references` has no impact on the aliases generated for the join # query. The fact that we pass `:developers_projects_join` to `references` # and that the SQL string contains `developers_projects_join` is merely a # coincidence. group = Developer.columns.inject([]) do |g, c| g << "developers.#{c.name}" g << "developers_projects_2.#{c.name}" end Project.columns.each { |c| group << "projects.#{c.name}" } assert_equal( 3, Developer.references(:developers_projects_join).merge( :includes => {:projects => :developers}, :where => 'projects_developers_projects_join.joined_on IS NOT NULL', :group => group.join(",") ).to_a.size ) end def test_find_grouped all_posts_from_category1 = Post.all.merge!(:where => "category_id = 1", :joins => :categories).to_a grouped_posts_of_category1 = Post.all.merge!(:where => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories).to_a assert_equal 5, all_posts_from_category1.size assert_equal 2, grouped_posts_of_category1.size end def test_find_scoped_grouped assert_equal 5, categories(:general).posts_grouped_by_title.to_a.size assert_equal 1, categories(:technology).posts_grouped_by_title.to_a.size end def test_find_scoped_grouped_having assert_equal 2, projects(:active_record).well_payed_salary_groups.to_a.size assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 } end def test_get_ids assert_equal projects(:active_record, :action_controller).map(&:id).sort, developers(:david).project_ids.sort assert_equal [projects(:active_record).id], developers(:jamis).project_ids end def test_get_ids_for_loaded_associations developer = developers(:david) developer.projects(true) assert_queries(0) do developer.project_ids developer.project_ids end end def test_get_ids_for_unloaded_associations_does_not_load_them developer = developers(:david) assert !developer.projects.loaded? assert_equal projects(:active_record, :action_controller).map(&:id).sort, developer.project_ids.sort assert !developer.projects.loaded? end def test_assign_ids developer = Developer.new("name" => "Joe") developer.project_ids = projects(:active_record, :action_controller).map(&:id) developer.save developer.reload assert_equal 2, developer.projects.length assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort end def test_assign_ids_ignoring_blanks developer = Developer.new("name" => "Joe") developer.project_ids = [projects(:active_record).id, nil, projects(:action_controller).id, ''] developer.save developer.reload assert_equal 2, developer.projects.length assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort end def test_scoped_find_on_through_association_doesnt_return_read_only_records tag = Post.find(1).tags.find_by_name("General") assert_nothing_raised do tag.save! end end def test_has_many_through_polymorphic_has_manys_works assert_equal [10, 20].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set end def test_symbols_as_keys developer = DeveloperWithSymbolsForKeys.new(:name => 'David') project = ProjectWithSymbolsForKeys.new(:name => 'Rails Testing') project.developers << developer project.save! assert_equal 1, project.developers.size assert_equal 1, developer.projects.size assert_equal developer, project.developers.first assert_equal project, developer.projects.first end def test_dynamic_find_should_respect_association_include # SQL error in sort clause if :include is not included # due to Unknown column 'authors.id' assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog') end def test_count david = Developer.find(1) assert_equal 2, david.projects.count end def test_association_proxy_transaction_method_starts_transaction_in_association_class Post.expects(:transaction) Category.first.posts.transaction do # nothing end end def test_caching_of_columns david = Developer.find(1) # clear cache possibly created by other tests david.projects.reset_column_information assert_queries(:any) { david.projects.columns } assert_no_queries { david.projects.columns } ## and again to verify that reset_column_information clears the cache correctly david.projects.reset_column_information assert_queries(:any) { david.projects.columns } assert_no_queries { david.projects.columns } end def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause new_developer = projects(:action_controller).developers.where(:name => "Marcelo").build assert_equal new_developer.name, "Marcelo" end def test_attributes_are_being_set_when_initialized_from_habm_association_with_multiple_where_clauses new_developer = projects(:action_controller).developers.where(:name => "Marcelo").where(:salary => 90_000).build assert_equal new_developer.name, "Marcelo" assert_equal new_developer.salary, 90_000 end def test_include_method_in_has_and_belongs_to_many_association_should_return_true_for_instance_added_with_build project = Project.new developer = project.developers.build assert project.developers.include?(developer) end def test_destruction_does_not_error_without_primary_key redbeard = pirates(:redbeard) george = parrots(:george) redbeard.parrots << george assert_equal 2, george.pirates.count Pirate.includes(:parrots).where(parrot: redbeard.parrot).find(redbeard.id).destroy assert_equal 1, george.pirates.count assert_equal [], Pirate.where(id: redbeard.id) end def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations projects = Developer.new.projects assert_no_queries(ignore_none: false) do assert_equal [], projects assert_equal [], projects.where(title: 'omg') assert_equal [], projects.pluck(:title) assert_equal 0, projects.count end end def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_create rich_person = RichPerson.new treasure = Treasure.new treasure.rich_people << rich_person treasure.valid? assert_equal 1, treasure.rich_people.size assert_nil rich_person.first_name, 'should not run associated person validation on create when validate: false' end def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_update rich_person = RichPerson.create! person_first_name = rich_person.first_name assert_not_nil person_first_name treasure = Treasure.new treasure.rich_people << rich_person treasure.valid? assert_equal 1, treasure.rich_people.size assert_equal person_first_name, rich_person.first_name, 'should not run associated person validation on update when validate: false' end def test_custom_join_table assert_equal 'edges', Vertex.reflect_on_association(:sources).join_table end def test_has_and_belongs_to_many_in_a_namespaced_model_pointing_to_a_namespaced_model magazine = Publisher::Magazine.create article = Publisher::Article.create magazine.articles << article magazine.save assert_includes magazine.articles, article end def test_has_and_belongs_to_many_in_a_namespaced_model_pointing_to_a_non_namespaced_model article = Publisher::Article.create tag = Tag.create article.tags << tag article.save assert_includes article.tags, tag end def test_redefine_habtm child = SubDeveloper.new("name" => "Aredridel") child.special_projects << SpecialProject.new("name" => "Special Project") assert child.save, 'child object should be saved' end def test_habtm_with_reflection_using_class_name_and_fixtures assert_not_nil Developer._reflections['shared_computers'] # Checking the fixture for named association is important here, because it's the only way # we've been able to reproduce this bug assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers") assert_equal developers(:david).shared_computers.first, computers(:laptop) end def test_with_symbol_class_name assert_nothing_raised NoMethodError do DeveloperWithSymbolClassName.new end end def test_alternate_database professor = Professor.create(name: "Plum") course = Course.create(name: "Forensics") assert_equal 0, professor.courses.count assert_nothing_raised do professor.courses << course end assert_equal 1, professor.courses.count end def test_habtm_scope_can_unscope project = ProjectUnscopingDavidDefaultScope.new project.save! developer = LazyBlockDeveloperCalledDavid.new(name: "Not David") developer.save! project.developers << developer projects = ProjectUnscopingDavidDefaultScope.includes(:developers).where(id: project.id) assert_equal 1, projects.first.developers.size end def test_preloaded_associations_size assert_equal Project.first.salaried_developers.size, Project.preload(:salaried_developers).first.salaried_developers.size assert_equal Project.includes(:salaried_developers).references(:salaried_developers).first.salaried_developers.size, Project.preload(:salaried_developers).first.salaried_developers.size # Nested HATBM first_project = Developer.first.projects.first preloaded_first_project = Developer.preload(projects: :salaried_developers). first. projects. detect { |p| p.id == first_project.id } assert preloaded_first_project.salaried_developers.loaded?, true assert_equal first_project.salaried_developers.size, preloaded_first_project.salaried_developers.size end end rails-4.2.6/activerecord/test/cases/associations/has_many_associations_test.rb000066400000000000000000002164431266740050600300450ustar00rootroot00000000000000require "cases/helper" require 'models/developer' require 'models/computer' require 'models/project' require 'models/company' require 'models/contract' require 'models/topic' require 'models/reply' require 'models/category' require 'models/image' require 'models/post' require 'models/author' require 'models/essay' require 'models/comment' require 'models/person' require 'models/reader' require 'models/tagging' require 'models/tag' require 'models/invoice' require 'models/line_item' require 'models/car' require 'models/bulb' require 'models/engine' require 'models/categorization' require 'models/minivan' require 'models/speedometer' require 'models/reference' require 'models/job' require 'models/college' require 'models/student' require 'models/pirate' require 'models/ship' require 'models/treasure' require 'models/parrot' require 'models/tyre' require 'models/subscriber' require 'models/subscription' require 'models/zine' require 'models/interest' class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase fixtures :authors, :posts, :comments def test_should_generate_valid_sql author = authors(:david) # this can fail on adapters which require ORDER BY expressions to be included in the SELECT expression # if the reorder clauses are not correctly handled assert author.posts_with_comments_sorted_by_comment_id.where('comments.id > 0').reorder('posts.comments_count DESC', 'posts.tags_count DESC').last end end class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase fixtures :authors, :essays, :subscribers, :subscriptions, :people def test_custom_primary_key_on_new_record_should_fetch_with_query subscriber = Subscriber.new(nick: 'webster132') assert !subscriber.subscriptions.loaded? assert_queries 1 do assert_equal 2, subscriber.subscriptions.size end assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: 'webster132') end def test_association_primary_key_on_new_record_should_fetch_with_query author = Author.new(:name => "David") assert !author.essays.loaded? assert_queries 1 do assert_equal 1, author.essays.size end assert_equal author.essays, Essay.where(writer_id: "David") end def test_has_many_custom_primary_key david = authors(:david) assert_equal david.essays, Essay.where(writer_id: "David") end def test_has_many_assignment_with_custom_primary_key david = people(:david) assert_equal ["A Modest Proposal"], david.essays.map(&:name) david.essays = [Essay.create!(name: "Remote Work" )] assert_equal ["Remote Work"], david.essays.map(&:name) end def test_blank_custom_primary_key_on_new_record_should_not_run_queries author = Author.new assert !author.essays.loaded? assert_queries 0 do assert_equal 0, author.essays.size end end end class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, :posts, :readers, :taggings, :cars, :jobs, :tags, :categorizations, :zines, :interests def setup Client.destroyed_client_ids.clear end def test_sti_subselect_count tag = Tag.first len = Post.tagged_with(tag.id).limit(10).size assert_operator len, :>, 0 end def test_anonymous_has_many developer = Class.new(ActiveRecord::Base) { self.table_name = 'developers' dev = self developer_project = Class.new(ActiveRecord::Base) { self.table_name = 'developers_projects' belongs_to :developer, :anonymous_class => dev } has_many :developer_projects, :anonymous_class => developer_project, :foreign_key => 'developer_id' } dev = developer.first named = Developer.find(dev.id) assert_operator dev.developer_projects.count, :>, 0 assert_equal named.projects.map(&:id).sort, dev.developer_projects.map(&:project_id).sort end def test_default_scope_on_relations_is_not_cached counter = 0 posts = Class.new(ActiveRecord::Base) { self.table_name = 'posts' self.inheritance_column = 'not_there' post = self comments = Class.new(ActiveRecord::Base) { self.table_name = 'comments' self.inheritance_column = 'not_there' belongs_to :post, :anonymous_class => post default_scope -> { counter += 1 where("id = :inc", :inc => counter) } } has_many :comments, :anonymous_class => comments, :foreign_key => 'post_id' } assert_equal 0, counter post = posts.first assert_equal 0, counter sql = capture_sql { post.comments.to_a } post.comments.reset assert_not_equal sql, capture_sql { post.comments.to_a } end def test_has_many_build_with_options college = College.create(name: 'UFMT') Student.create(active: true, college_id: college.id, name: 'Sarah') assert_equal college.students, Student.where(active: true, college_id: college.id) end def test_create_from_association_should_respect_default_scope car = Car.create(:name => 'honda') assert_equal 'honda', car.name bulb = Bulb.create assert_equal 'defaulty', bulb.name bulb = car.bulbs.build assert_equal 'defaulty', bulb.name bulb = car.bulbs.create assert_equal 'defaulty', bulb.name bulb = car.bulbs.create(:name => 'exotic') assert_equal 'exotic', bulb.name end def test_build_from_association_should_respect_scope author = Author.new post = author.thinking_posts.build assert_equal 'So I was thinking', post.title end def test_create_from_association_with_nil_values_should_work car = Car.create(:name => 'honda') bulb = car.bulbs.new(nil) assert_equal 'defaulty', bulb.name bulb = car.bulbs.build(nil) assert_equal 'defaulty', bulb.name bulb = car.bulbs.create(nil) assert_equal 'defaulty', bulb.name end def test_do_not_call_callbacks_for_delete_all car = Car.create(:name => 'honda') car.funky_bulbs.create! assert_nothing_raised { car.reload.funky_bulbs.delete_all } assert_equal 0, Bulb.count, "bulbs should have been deleted using :delete_all strategy" end def test_delete_all_on_association_is_the_same_as_not_loaded author = authors :david author.thinking_posts.create!(:body => "test") author.reload expected_sql = capture_sql { author.thinking_posts.delete_all } author.thinking_posts.create!(:body => "test") author.reload author.thinking_posts.inspect loaded_sql = capture_sql { author.thinking_posts.delete_all } assert_equal(expected_sql, loaded_sql) end def test_delete_all_on_association_with_nil_dependency_is_the_same_as_not_loaded author = authors :david author.posts.create!(:title => "test", :body => "body") author.reload expected_sql = capture_sql { author.posts.delete_all } author.posts.create!(:title => "test", :body => "body") author.reload author.posts.to_a loaded_sql = capture_sql { author.posts.delete_all } assert_equal(expected_sql, loaded_sql) end def test_building_the_associated_object_with_implicit_sti_base_class firm = DependentFirm.new company = firm.companies.build assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_associated_object_with_explicit_sti_base_class firm = DependentFirm.new company = firm.companies.build(:type => "Company") assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_associated_object_with_sti_subclass firm = DependentFirm.new company = firm.companies.build(:type => "Client") assert_kind_of Client, company, "Expected #{company.class} to be a Client" end def test_building_the_associated_object_with_an_invalid_type firm = DependentFirm.new assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(:type => "Invalid") } end def test_building_the_associated_object_with_an_unrelated_type firm = DependentFirm.new assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(:type => "Account") } end test "building the association with an array" do speedometer = Speedometer.new(speedometer_id: "a") data = [{name: "first"}, {name: "second"}] speedometer.minivans.build(data) assert_equal 2, speedometer.minivans.size assert speedometer.save assert_equal ["first", "second"], speedometer.reload.minivans.map(&:name) end def test_association_keys_bypass_attribute_protection car = Car.create(:name => 'honda') bulb = car.bulbs.new assert_equal car.id, bulb.car_id bulb = car.bulbs.new :car_id => car.id + 1 assert_equal car.id, bulb.car_id bulb = car.bulbs.build assert_equal car.id, bulb.car_id bulb = car.bulbs.build :car_id => car.id + 1 assert_equal car.id, bulb.car_id bulb = car.bulbs.create assert_equal car.id, bulb.car_id bulb = car.bulbs.create :car_id => car.id + 1 assert_equal car.id, bulb.car_id end def test_association_protect_foreign_key invoice = Invoice.create line_item = invoice.line_items.new assert_equal invoice.id, line_item.invoice_id line_item = invoice.line_items.new :invoice_id => invoice.id + 1 assert_equal invoice.id, line_item.invoice_id line_item = invoice.line_items.build assert_equal invoice.id, line_item.invoice_id line_item = invoice.line_items.build :invoice_id => invoice.id + 1 assert_equal invoice.id, line_item.invoice_id line_item = invoice.line_items.create assert_equal invoice.id, line_item.invoice_id line_item = invoice.line_items.create :invoice_id => invoice.id + 1 assert_equal invoice.id, line_item.invoice_id end # When creating objects on the association, we must not do it within a scope (even though it # would be convenient), because this would cause that scope to be applied to any callbacks etc. def test_build_and_create_should_not_happen_within_scope car = cars(:honda) scoped_count = car.foo_bulbs.where_values.count bulb = car.foo_bulbs.build assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count bulb = car.foo_bulbs.create assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count bulb = car.foo_bulbs.create! assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count end def test_no_sql_should_be_fired_if_association_already_loaded Car.create(:name => 'honda') bulbs = Car.first.bulbs bulbs.to_a # to load all instances of bulbs assert_no_queries do bulbs.first() bulbs.first({}) end assert_no_queries do bulbs.second() bulbs.second({}) end assert_no_queries do bulbs.third() bulbs.third({}) end assert_no_queries do bulbs.fourth() bulbs.fourth({}) end assert_no_queries do bulbs.fifth() bulbs.fifth({}) end assert_no_queries do bulbs.forty_two() bulbs.forty_two({}) end assert_no_queries do bulbs.last() bulbs.last({}) end end def test_create_resets_cached_counters person = Person.create!(:first_name => 'tenderlove') post = Post.first assert_equal [], person.readers assert_nil person.readers.find_by_post_id(post.id) person.readers.create(:post_id => post.id) assert_equal 1, person.readers.count assert_equal 1, person.readers.length assert_equal post, person.readers.first.post assert_equal person, person.readers.first.person end def force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.each {|f| } end # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql assert_equal 3, Firm.all.merge!(:order => "id").first.clients.count end def test_counting assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count end def test_counting_with_single_hash assert_equal 1, Firm.all.merge!(:order => "id").first.plain_clients.where(:name => "Microsoft").count end def test_counting_with_column_name_and_hash assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count(:name) end def test_counting_with_association_limit firm = companies(:first_firm) assert_equal firm.limited_clients.length, firm.limited_clients.size assert_equal firm.limited_clients.length, firm.limited_clients.count end def test_finding assert_equal 3, Firm.all.merge!(:order => "id").first.clients.length end def test_finding_array_compatibility assert_equal 3, Firm.order(:id).find{|f| f.id > 0}.clients.length end def test_find_many_with_merged_options assert_equal 1, companies(:first_firm).limited_clients.size assert_equal 1, companies(:first_firm).limited_clients.to_a.size assert_equal 3, companies(:first_firm).limited_clients.limit(nil).to_a.size end def test_find_should_append_to_association_order ordered_clients = companies(:first_firm).clients_sorted_desc.order('companies.id') assert_equal ['id DESC', 'companies.id'], ordered_clients.order_values end def test_dynamic_find_should_respect_association_order assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.where("type = 'Client'").first assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client') end def test_taking posts(:other_by_bob).destroy assert_equal posts(:misc_by_bob), authors(:bob).posts.take assert_equal posts(:misc_by_bob), authors(:bob).posts.take! authors(:bob).posts.to_a assert_equal posts(:misc_by_bob), authors(:bob).posts.take assert_equal posts(:misc_by_bob), authors(:bob).posts.take! end def test_taking_not_found authors(:bob).posts.delete_all assert_raise(ActiveRecord::RecordNotFound) { authors(:bob).posts.take! } authors(:bob).posts.to_a assert_raise(ActiveRecord::RecordNotFound) { authors(:bob).posts.take! } end def test_taking_with_a_number # taking from unloaded Relation bob = Author.find(authors(:bob).id) assert_equal [posts(:misc_by_bob)], bob.posts.take(1) bob = Author.find(authors(:bob).id) assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2) # taking from loaded Relation bob.posts.to_a assert_equal [posts(:misc_by_bob)], authors(:bob).posts.take(1) assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], authors(:bob).posts.take(2) end def test_taking_with_inverse_of interests(:woodsmanship).destroy interests(:survival).destroy zine = zines(:going_out) interest = zine.interests.take assert_equal interests(:hunting), interest assert_same zine, interest.zine end def test_cant_save_has_many_readonly_association authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } } authors(:david).readonly_comments.each { |c| assert c.readonly? } end def test_finding_default_orders assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients.first.name end def test_finding_with_different_class_name_and_order assert_equal "Apex", Firm.all.merge!(:order => "id").first.clients_sorted_desc.first.name end def test_finding_with_foreign_key assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_of_firm.first.name end def test_finding_with_condition assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_like_ms.first.name end def test_finding_with_condition_hash assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients_using_primary_key.first.name end def test_update_all_on_association_accessed_before_save firm = Firm.new(name: 'Firm') firm.clients << Client.first firm.save! assert_equal firm.clients.count, firm.clients.update_all(description: 'Great!') end def test_belongs_to_sanity c = Client.new assert_nil c.firm, "belongs_to failed sanity check on new object" end def test_find_ids firm = Firm.all.merge!(:order => "id").first assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } client = firm.clients.find(2) assert_kind_of Client, client client_ary = firm.clients.find([2]) assert_kind_of Array, client_ary assert_equal client, client_ary.first client_ary = firm.clients.find(2, 3) assert_kind_of Array, client_ary assert_equal 2, client_ary.size assert_equal client, client_ary.first assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) } end def test_find_ids_and_inverse_of force_signal37_to_load_all_clients_of_firm firm = companies(:first_firm) client = firm.clients_of_firm.find(3) assert_kind_of Client, client client_ary = firm.clients_of_firm.find([3]) assert_kind_of Array, client_ary assert_equal client, client_ary.first end def test_find_all firm = Firm.all.merge!(:order => "id").first assert_equal 3, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length assert_equal 1, firm.clients.where("name = 'Summit'").to_a.length end def test_find_each firm = companies(:first_firm) assert ! firm.clients.loaded? assert_queries(4) do firm.clients.find_each(:batch_size => 1) {|c| assert_equal firm.id, c.firm_id } end assert ! firm.clients.loaded? end def test_find_each_with_conditions firm = companies(:first_firm) assert_queries(2) do firm.clients.where(name: 'Microsoft').find_each(batch_size: 1) do |c| assert_equal firm.id, c.firm_id assert_equal "Microsoft", c.name end end assert ! firm.clients.loaded? end def test_find_in_batches firm = companies(:first_firm) assert ! firm.clients.loaded? assert_queries(2) do firm.clients.find_in_batches(:batch_size => 2) do |clients| clients.each {|c| assert_equal firm.id, c.firm_id } end end assert ! firm.clients.loaded? end def test_find_all_sanitized # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first firm = Firm.all.merge!(:order => "id").first summit = firm.clients.where("name = 'Summit'").to_a assert_equal summit, firm.clients.where("name = ?", "Summit").to_a assert_equal summit, firm.clients.where("name = :name", { :name => "Summit" }).to_a end def test_find_first firm = Firm.all.merge!(:order => "id").first client2 = Client.find(2) assert_equal firm.clients.first, firm.clients.order("id").first assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").order("id").first end def test_find_first_sanitized firm = Firm.all.merge!(:order => "id").first client2 = Client.find(2) assert_equal client2, firm.clients.merge!(:where => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id").first assert_equal client2, firm.clients.merge!(:where => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id").first end def test_find_all_with_include_and_conditions assert_nothing_raised do Developer.all.merge!(:joins => :audit_logs, :where => {'audit_logs.message' => nil, :name => 'Smith'}).to_a end end def test_find_in_collection assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) } end def test_find_grouped all_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1").to_a grouped_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').to_a assert_equal 3, all_clients_of_firm1.size assert_equal 1, grouped_clients_of_firm1.size end def test_find_scoped_grouped assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.size assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.length assert_equal 3, companies(:first_firm).clients_grouped_by_name.size assert_equal 3, companies(:first_firm).clients_grouped_by_name.length end def test_find_scoped_grouped_having assert_equal 1, authors(:david).popular_grouped_posts.length assert_equal 0, authors(:mary).popular_grouped_posts.length end def test_default_select assert_equal Comment.column_names.sort, posts(:welcome).comments.first.attributes.keys.sort end def test_select_query_method assert_equal ['id', 'body'], posts(:welcome).comments.select(:id, :body).first.attributes.keys end def test_select_with_block assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id) end def test_select_without_foreign_key assert_equal companies(:first_firm).accounts.first.credit_limit, companies(:first_firm).accounts.select(:credit_limit).first.credit_limit end def test_adding force_signal37_to_load_all_clients_of_firm natural = Client.new("name" => "Natural Company") companies(:first_firm).clients_of_firm << natural assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection assert_equal 3, companies(:first_firm).clients_of_firm(true).size # checking using the db assert_equal natural, companies(:first_firm).clients_of_firm.last end def test_adding_using_create first_firm = companies(:first_firm) assert_equal 3, first_firm.plain_clients.size first_firm.plain_clients.create(:name => "Natural Company") assert_equal 4, first_firm.plain_clients.length assert_equal 4, first_firm.plain_clients.size end def test_create_with_bang_on_has_many_when_parent_is_new_raises error = assert_raise(ActiveRecord::RecordNotSaved) do firm = Firm.new firm.plain_clients.create! :name=>"Whoever" end assert_equal "You cannot call create unless the parent is saved", error.message end def test_regular_create_on_has_many_when_parent_is_new_raises error = assert_raise(ActiveRecord::RecordNotSaved) do firm = Firm.new firm.plain_clients.create :name=>"Whoever" end assert_equal "You cannot call create unless the parent is saved", error.message end def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do firm = Firm.all.merge!(:order => "id").first firm.plain_clients.create! end end def test_create_with_bang_on_habtm_when_parent_is_new_raises error = assert_raise(ActiveRecord::RecordNotSaved) do Developer.new("name" => "Aredridel").projects.create! end assert_equal "You cannot call create unless the parent is saved", error.message end def test_adding_a_mismatch_class assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil } assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 } assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << Topic.find(1) } end def test_adding_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")]) assert_equal 4, companies(:first_firm).clients_of_firm.size assert_equal 4, companies(:first_firm).clients_of_firm(true).size end def test_transactions_when_adding_to_persisted good = Client.new(:name => "Good") bad = Client.new(:name => "Bad", :raise_on_save => true) begin companies(:first_firm).clients_of_firm.concat(good, bad) rescue Client::RaisedOnSave end assert !companies(:first_firm).clients_of_firm(true).include?(good) end def test_transactions_when_adding_to_new_record assert_no_queries(ignore_none: false) do firm = Firm.new firm.clients_of_firm.concat(Client.new("name" => "Natural Company")) end end def test_inverse_on_before_validate firm = companies(:first_firm) assert_queries(1) do firm.clients_of_firm << Client.new("name" => "Natural Company") end end def test_new_aliased_to_build company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.new("name" => "Another Client") } assert !company.clients_of_firm.loaded? assert_equal "Another Client", new_client.name assert !new_client.persisted? assert_equal new_client, company.clients_of_firm.last end def test_build company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") } assert !company.clients_of_firm.loaded? assert_equal "Another Client", new_client.name assert !new_client.persisted? assert_equal new_client, company.clients_of_firm.last end def test_collection_size_after_building company = companies(:first_firm) # company already has one client company.clients_of_firm.build("name" => "Another Client") company.clients_of_firm.build("name" => "Yet Another Client") assert_equal 4, company.clients_of_firm.size end def test_collection_not_empty_after_building company = companies(:first_firm) assert_predicate company.contracts, :empty? company.contracts.build assert_not_predicate company.contracts, :empty? end def test_collection_size_twice_for_regressions post = posts(:thinking) assert_equal 0, post.readers.size # This test needs a post that has no readers, we assert it to ensure it holds, # but need to reload the post because the very call to #size hides the bug. post.reload post.readers.build size1 = post.readers.size size2 = post.readers.size assert_equal size1, size2 end def test_build_many company = companies(:first_firm) new_clients = assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) } assert_equal 2, new_clients.size end def test_build_followed_by_save_does_not_load_target companies(:first_firm).clients_of_firm.build("name" => "Another Client") assert companies(:first_firm).save assert !companies(:first_firm).clients_of_firm.loaded? end def test_build_without_loading_association first_topic = topics(:first) Reply.column_names assert_equal 1, first_topic.replies.length assert_no_queries do first_topic.replies.build(:title => "Not saved", :content => "Superstars") assert_equal 2, first_topic.replies.size end assert_equal 2, first_topic.replies.to_ary.size end def test_build_via_block company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } } assert !company.clients_of_firm.loaded? assert_equal "Another Client", new_client.name assert !new_client.persisted? assert_equal new_client, company.clients_of_firm.last end def test_build_many_via_block company = companies(:first_firm) new_clients = assert_no_queries(ignore_none: false) do company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client| client.name = "changed" end end assert_equal 2, new_clients.size assert_equal "changed", new_clients.first.name assert_equal "changed", new_clients.last.name end def test_create_without_loading_association first_firm = companies(:first_firm) Firm.column_names Client.column_names assert_equal 2, first_firm.clients_of_firm.size first_firm.clients_of_firm.reset assert_queries(1) do first_firm.clients_of_firm.create(:name => "Superstars") end assert_equal 3, first_firm.clients_of_firm.size end def test_create force_signal37_to_load_all_clients_of_firm new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert new_client.persisted? assert_equal new_client, companies(:first_firm).clients_of_firm.last assert_equal new_client, companies(:first_firm).clients_of_firm(true).last end def test_create_many companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}]) assert_equal 4, companies(:first_firm).clients_of_firm(true).size end def test_create_followed_by_save_does_not_load_target companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert companies(:first_firm).save assert !companies(:first_firm).clients_of_firm.loaded? end def test_deleting force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) assert_equal 1, companies(:first_firm).clients_of_firm.size assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_deleting_before_save new_firm = Firm.new("name" => "A New Firm, Inc.") new_client = new_firm.clients_of_firm.build("name" => "Another Client") assert_equal 1, new_firm.clients_of_firm.size new_firm.clients_of_firm.delete(new_client) assert_equal 0, new_firm.clients_of_firm.size end def test_has_many_without_counter_cache_option # Ship has a conventionally named `treasures_count` column, but the counter_cache # option is not given on the association. ship = Ship.create(name: 'Countless', treasures_count: 10) assert_not ship.treasures.instance_variable_get('@association').send(:has_cached_counter?) # Count should come from sql count() of treasures rather than treasures_count attribute assert_equal ship.treasures.size, 0 assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do ship.treasures.create(name: 'Gold') end assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do ship.treasures.destroy_all end end def test_deleting_updates_counter_cache topic = Topic.order("id ASC").first assert_equal topic.replies.to_a.size, topic.replies_count topic.replies.delete(topic.replies.first) topic.reload assert_equal topic.replies.to_a.size, topic.replies_count end def test_counter_cache_updates_in_memory_after_concat topic = Topic.create title: "Zoom-zoom-zoom" topic.replies << Reply.create(title: "re: zoom", content: "speedy quick!") assert_equal 1, topic.replies_count assert_equal 1, topic.replies.size assert_equal 1, topic.reload.replies.size end def test_counter_cache_updates_in_memory_after_create topic = Topic.create title: "Zoom-zoom-zoom" topic.replies.create!(title: "re: zoom", content: "speedy quick!") assert_equal 1, topic.replies_count assert_equal 1, topic.replies.size assert_equal 1, topic.reload.replies.size end def test_counter_cache_updates_in_memory_after_create_with_array topic = Topic.create title: "Zoom-zoom-zoom" topic.replies.create!([ { title: "re: zoom", content: "speedy quick!" }, { title: "re: zoom 2", content: "OMG lol!" }, ]) assert_equal 2, topic.replies_count assert_equal 2, topic.replies.size assert_equal 2, topic.reload.replies.size end def test_pushing_association_updates_counter_cache topic = Topic.order("id ASC").first reply = Reply.create! assert_difference "topic.reload.replies_count", 1 do topic.replies << reply end end def test_deleting_updates_counter_cache_without_dependent_option post = posts(:welcome) assert_difference "post.reload.tags_count", -1 do post.taggings.delete(post.taggings.first) end end def test_deleting_updates_counter_cache_with_dependent_delete_all post = posts(:welcome) post.update_columns(taggings_with_delete_all_count: post.tags_count) assert_difference "post.reload.taggings_with_delete_all_count", -1 do post.taggings_with_delete_all.delete(post.taggings_with_delete_all.first) end end def test_deleting_updates_counter_cache_with_dependent_destroy post = posts(:welcome) post.update_columns(taggings_with_destroy_count: post.tags_count) assert_difference "post.reload.taggings_with_destroy_count", -1 do post.taggings_with_destroy.delete(post.taggings_with_destroy.first) end end def test_calling_empty_with_counter_cache post = posts(:welcome) assert_queries(0) do assert_not post.comments.empty? end end def test_custom_named_counter_cache topic = topics(:first) assert_difference "topic.reload.replies_count", -1 do topic.approved_replies.clear end end def test_calling_update_attributes_on_id_changes_the_counter_cache topic = Topic.order("id ASC").first original_count = topic.replies.to_a.size assert_equal original_count, topic.replies_count first_reply = topic.replies.first first_reply.update_attributes(:parent_id => nil) assert_equal original_count - 1, topic.reload.replies_count first_reply.update_attributes(:parent_id => topic.id) assert_equal original_count, topic.reload.replies_count end def test_calling_update_attributes_changing_ids_doesnt_change_counter_cache topic1 = Topic.find(1) topic2 = Topic.find(3) original_count1 = topic1.replies.to_a.size original_count2 = topic2.replies.to_a.size reply1 = topic1.replies.first reply2 = topic2.replies.first reply1.update_attributes(:parent_id => topic2.id) assert_equal original_count1 - 1, topic1.reload.replies_count assert_equal original_count2 + 1, topic2.reload.replies_count reply2.update_attributes(:parent_id => topic1.id) assert_equal original_count1, topic1.reload.replies_count assert_equal original_count2, topic2.reload.replies_count end def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]]) assert_equal 0, companies(:first_firm).clients_of_firm.size assert_equal 0, companies(:first_firm).clients_of_firm(true).size end def test_delete_all force_signal37_to_load_all_clients_of_firm companies(:first_firm).dependent_clients_of_firm.create("name" => "Another Client") clients = companies(:first_firm).dependent_clients_of_firm.to_a assert_equal 3, clients.count assert_difference "Client.count", -(clients.count) do companies(:first_firm).dependent_clients_of_firm.delete_all end end def test_delete_all_with_not_yet_loaded_association_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size companies(:first_firm).clients_of_firm.reset companies(:first_firm).clients_of_firm.delete_all assert_equal 0, companies(:first_firm).clients_of_firm.size assert_equal 0, companies(:first_firm).clients_of_firm(true).size end def test_transaction_when_deleting_persisted good = Client.new(:name => "Good") bad = Client.new(:name => "Bad", :raise_on_destroy => true) companies(:first_firm).clients_of_firm = [good, bad] begin companies(:first_firm).clients_of_firm.destroy(good, bad) rescue Client::RaisedOnDestroy end assert_equal [good, bad], companies(:first_firm).clients_of_firm(true) end def test_transaction_when_deleting_new_record assert_no_queries(ignore_none: false) do firm = Firm.new client = Client.new("name" => "New Client") firm.clients_of_firm << client firm.clients_of_firm.destroy(client) end end def test_clearing_an_association_collection firm = companies(:first_firm) client_id = firm.clients_of_firm.first.id assert_equal 2, firm.clients_of_firm.size firm.clients_of_firm.clear assert_equal 0, firm.clients_of_firm.size assert_equal 0, firm.clients_of_firm(true).size assert_equal [], Client.destroyed_client_ids[firm.id] # Should not be destroyed since the association is not dependent. assert_nothing_raised do assert_nil Client.find(client_id).firm end end def test_clearing_updates_counter_cache topic = Topic.first assert_difference 'topic.reload.replies_count', -1 do topic.replies.clear end end def test_clearing_updates_counter_cache_when_inverse_counter_cache_is_a_symbol_with_dependent_destroy car = Car.first car.engines.create! assert_difference 'car.reload.engines_count', -1 do car.engines.clear end end def test_clearing_a_dependent_association_collection firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id assert_equal 2, firm.dependent_clients_of_firm.size assert_equal 1, Client.find_by_id(client_id).client_of # :delete_all is called on each client since the dependent options is :destroy firm.dependent_clients_of_firm.clear assert_equal 0, firm.dependent_clients_of_firm.size assert_equal 0, firm.dependent_clients_of_firm(true).size assert_equal [], Client.destroyed_client_ids[firm.id] # Should be destroyed since the association is dependent. assert_nil Client.find_by_id(client_id) end def test_delete_all_with_option_delete_all firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id firm.dependent_clients_of_firm.delete_all(:delete_all) assert_nil Client.find_by_id(client_id) end def test_delete_all_accepts_limited_parameters firm = companies(:first_firm) assert_raise(ArgumentError) do firm.dependent_clients_of_firm.delete_all(:destroy) end end def test_clearing_an_exclusively_dependent_association_collection firm = companies(:first_firm) client_id = firm.exclusively_dependent_clients_of_firm.first.id assert_equal 2, firm.exclusively_dependent_clients_of_firm.size assert_equal [], Client.destroyed_client_ids[firm.id] # :exclusively_dependent means each client is deleted directly from # the database without looping through them calling destroy. firm.exclusively_dependent_clients_of_firm.clear assert_equal 0, firm.exclusively_dependent_clients_of_firm.size assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size # no destroy-filters should have been called assert_equal [], Client.destroyed_client_ids[firm.id] # Should be destroyed since the association is exclusively dependent. assert_nil Client.find_by_id(client_id) end def test_dependent_association_respects_optional_conditions_on_delete firm = companies(:odegy) Client.create(:client_of => firm.id, :name => "BigShot Inc.") Client.create(:client_of => firm.id, :name => "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_conditional_clients_of_firm.size firm.destroy # only the correctly associated client should have been deleted assert_equal 1, Client.where(client_of: firm.id).size end def test_dependent_association_respects_optional_sanitized_conditions_on_delete firm = companies(:odegy) Client.create(:client_of => firm.id, :name => "BigShot Inc.") Client.create(:client_of => firm.id, :name => "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size firm.destroy # only the correctly associated client should have been deleted assert_equal 1, Client.where(client_of: firm.id).size end def test_dependent_association_respects_optional_hash_conditions_on_delete firm = companies(:odegy) Client.create(:client_of => firm.id, :name => "BigShot Inc.") Client.create(:client_of => firm.id, :name => "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size firm.destroy # only the correctly associated client should have been deleted assert_equal 1, Client.where(client_of: firm.id).size end def test_delete_all_association_with_primary_key_deletes_correct_records firm = Firm.first # break the vanilla firm_id foreign key assert_equal 3, firm.clients.count firm.clients.first.update_columns(firm_id: nil) assert_equal 2, firm.clients(true).count assert_equal 2, firm.clients_using_primary_key_with_delete_all.count old_record = firm.clients_using_primary_key_with_delete_all.first firm = Firm.first firm.destroy assert_nil Client.find_by_id(old_record.id) end def test_creation_respects_hash_condition ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build assert ms_client.save assert_equal 'Microsoft', ms_client.name another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create assert another_ms_client.persisted? assert_equal 'Microsoft', another_ms_client.name end def test_clearing_without_initial_access firm = companies(:first_firm) firm.clients_of_firm.clear assert_equal 0, firm.clients_of_firm.size assert_equal 0, firm.clients_of_firm(true).size end def test_deleting_a_item_which_is_not_in_the_collection force_signal37_to_load_all_clients_of_firm summit = Client.find_by_name('Summit') companies(:first_firm).clients_of_firm.delete(summit) assert_equal 2, companies(:first_firm).clients_of_firm.size assert_equal 2, companies(:first_firm).clients_of_firm(true).size assert_equal 2, summit.client_of end def test_deleting_by_fixnum_id david = Developer.find(1) assert_difference 'david.projects.count', -1 do assert_equal 1, david.projects.delete(1).size end assert_equal 1, david.projects.size end def test_deleting_by_string_id david = Developer.find(1) assert_difference 'david.projects.count', -1 do assert_equal 1, david.projects.delete('1').size end assert_equal 1, david.projects.size end def test_deleting_self_type_mismatch david = Developer.find(1) david.projects.reload assert_raise(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(Project.find(1).developers) } end def test_destroying force_signal37_to_load_all_clients_of_firm assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first) end assert_equal 1, companies(:first_firm).reload.clients_of_firm.size assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_by_fixnum_id force_signal37_to_load_all_clients_of_firm assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id) end assert_equal 1, companies(:first_firm).reload.clients_of_firm.size assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_by_string_id force_signal37_to_load_all_clients_of_firm assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s) end assert_equal 1, companies(:first_firm).reload.clients_of_firm.size assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size assert_difference "Client.count", -2 do companies(:first_firm).clients_of_firm.destroy([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]]) end assert_equal 1, companies(:first_firm).reload.clients_of_firm.size assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroy_all force_signal37_to_load_all_clients_of_firm clients = companies(:first_firm).clients_of_firm.to_a assert !clients.empty?, "37signals has clients after load" destroyed = companies(:first_firm).clients_of_firm.destroy_all assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id) assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen" assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all" assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh" end def test_dependence firm = companies(:first_firm) assert_equal 3, firm.clients.size firm.destroy assert Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.empty? end def test_dependence_for_associations_with_hash_condition david = authors(:david) assert_difference('Post.count', -1) { assert david.destroy } end def test_destroy_dependent_when_deleted_from_association # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first firm = Firm.all.merge!(:order => "id").first assert_equal 3, firm.clients.size client = firm.clients.first firm.clients.delete(client) assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) } assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) } assert_equal 2, firm.clients.size end def test_three_levels_of_dependence topic = Topic.create "title" => "neat and simple" reply = topic.replies.create "title" => "neat and simple", "content" => "still digging it" reply.replies.create "title" => "neat and simple", "content" => "ain't complaining" assert_nothing_raised { topic.destroy } end uses_transaction :test_dependence_with_transaction_support_on_failure def test_dependence_with_transaction_support_on_failure firm = companies(:first_firm) clients = firm.clients assert_equal 3, clients.length clients.last.instance_eval { def overwrite_to_raise() raise "Trigger rollback" end } firm.destroy rescue "do nothing" assert_equal 3, Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.size end def test_dependence_on_account num_accounts = Account.count companies(:first_firm).destroy assert_equal num_accounts - 1, Account.count end def test_depends_and_nullify num_accounts = Account.count core = companies(:rails_core) assert_equal accounts(:rails_core_account), core.account assert_equal companies(:leetsoft, :jadedpixel), core.companies core.destroy assert_nil accounts(:rails_core_account).reload.firm_id assert_nil companies(:leetsoft).reload.client_of assert_nil companies(:jadedpixel).reload.client_of assert_equal num_accounts, Account.count end def test_restrict_with_exception firm = RestrictedWithExceptionFirm.create!(:name => 'restrict') firm.companies.create(:name => 'child') assert !firm.companies.empty? assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } assert RestrictedWithExceptionFirm.exists?(:name => 'restrict') assert firm.companies.exists?(:name => 'child') end def test_restrict_with_error firm = RestrictedWithErrorFirm.create!(:name => 'restrict') firm.companies.create(:name => 'child') assert !firm.companies.empty? firm.destroy assert !firm.errors.empty? assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first assert RestrictedWithErrorFirm.exists?(:name => 'restrict') assert firm.companies.exists?(:name => 'child') end def test_included_in_collection assert_equal true, companies(:first_firm).clients.include?(Client.find(2)) end def test_included_in_collection_for_new_records client = Client.create(:name => 'Persisted') assert_nil client.client_of assert_equal false, Firm.new.clients_of_firm.include?(client), 'includes a client that does not belong to any firm' end def test_adding_array_and_collection assert_nothing_raised { Firm.first.clients + Firm.all.last.clients } end def test_replace_with_less firm = Firm.all.merge!(:order => "id").first firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload assert_equal 1, firm.clients.length end def test_replace_with_less_and_dependent_nullify num_companies = Company.count companies(:rails_core).companies = [] assert_equal num_companies, Company.count end def test_replace_with_new firm = Firm.all.merge!(:order => "id").first firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload assert_equal 2, firm.clients.length assert_equal false, firm.clients.include?(:first_client) end def test_replace_failure firm = companies(:first_firm) account = Account.new orig_accounts = firm.accounts.to_a assert !account.valid? assert !orig_accounts.empty? error = assert_raise ActiveRecord::RecordNotSaved do firm.accounts = [account] end assert_equal orig_accounts, firm.accounts assert_equal "Failed to replace accounts because one or more of the " \ "new records could not be saved.", error.message end def test_replace_with_same_content firm = Firm.first firm.clients = [] firm.save assert_queries(0, ignore_none: true) do firm.clients = [] end end def test_transactions_when_replacing_on_persisted good = Client.new(:name => "Good") bad = Client.new(:name => "Bad", :raise_on_save => true) companies(:first_firm).clients_of_firm = [good] begin companies(:first_firm).clients_of_firm = [bad] rescue Client::RaisedOnSave end assert_equal [good], companies(:first_firm).clients_of_firm(true) end def test_transactions_when_replacing_on_new_record assert_no_queries(ignore_none: false) do firm = Firm.new firm.clients_of_firm = [Client.new("name" => "New Client")] end end def test_get_ids assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], companies(:first_firm).client_ids end def test_get_ids_for_loaded_associations company = companies(:first_firm) company.clients(true) assert_queries(0) do company.client_ids company.client_ids end end def test_get_ids_for_unloaded_associations_does_not_load_them company = companies(:first_firm) assert !company.clients.loaded? assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], company.client_ids assert !company.clients.loaded? end def test_get_ids_ignores_include_option assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids end def test_get_ids_for_ordered_association assert_equal [companies(:another_first_firm_client).id, companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids end def test_get_ids_for_association_on_new_record_does_not_try_to_find_records Company.columns # Load schema information so we don't query below Contract.columns # if running just this test. company = Company.new assert_queries(0) do company.contract_ids end assert_equal [], company.contract_ids end def test_set_ids_for_association_on_new_record_applies_association_correctly contract_a = Contract.create! contract_b = Contract.create! Contract.create! # another contract company = Company.new(:name => "Some Company") company.contract_ids = [contract_a.id, contract_b.id] assert_equal [contract_a.id, contract_b.id], company.contract_ids assert_equal [contract_a, contract_b], company.contracts company.save! assert_equal company, contract_a.reload.company assert_equal company, contract_b.reload.company end def test_assign_ids_ignoring_blanks firm = Firm.create!(:name => 'Apple') firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ''] firm.save! assert_equal 2, firm.clients(true).size assert_equal true, firm.clients.include?(companies(:second_client)) end def test_get_ids_for_through assert_equal [comments(:eager_other_comment1).id], authors(:mary).comment_ids end def test_modifying_a_through_a_has_many_should_raise [ lambda { authors(:mary).comment_ids = [comments(:greetings).id, comments(:more_greetings).id] }, lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] }, lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) }, lambda { authors(:mary).comments.delete(authors(:mary).comments.first) }, ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } end def test_dynamic_find_should_respect_association_order_for_through assert_equal Comment.find(10), authors(:david).comments_desc.where("comments.type = 'SpecialComment'").first assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment') end def test_has_many_through_respects_hash_conditions assert_equal authors(:david).hello_posts, authors(:david).hello_posts_with_hash_conditions assert_equal authors(:david).hello_post_comments, authors(:david).hello_post_comments_with_hash_conditions end def test_include_uses_array_include_after_loaded firm = companies(:first_firm) firm.clients.load_target client = firm.clients.first assert_no_queries do assert firm.clients.loaded? assert_equal true, firm.clients.include?(client) end end def test_include_checks_if_record_exists_if_target_not_loaded firm = companies(:first_firm) client = firm.clients.first firm.reload assert ! firm.clients.loaded? assert_queries(1) do assert_equal true, firm.clients.include?(client) end assert ! firm.clients.loaded? end def test_include_returns_false_for_non_matching_record_to_verify_scoping firm = companies(:first_firm) client = Client.create!(:name => 'Not Associated') assert ! firm.clients.loaded? assert_equal false, firm.clients.include?(client) end def test_calling_first_nth_or_last_on_association_should_not_load_association firm = companies(:first_firm) firm.clients.first firm.clients.second firm.clients.last assert !firm.clients.loaded? end def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query firm = companies(:first_firm) firm.clients.load_target assert firm.clients.loaded? assert_no_queries(ignore_none: false) do firm.clients.first assert_equal 2, firm.clients.first(2).size firm.clients.last assert_equal 2, firm.clients.last(2).size end end def test_calling_first_or_last_on_existing_record_with_build_should_load_association firm = companies(:first_firm) firm.clients.build(:name => 'Foo') assert !firm.clients.loaded? assert_queries 1 do firm.clients.first firm.clients.second firm.clients.last end assert firm.clients.loaded? end def test_calling_first_nth_or_last_on_existing_record_with_create_should_not_load_association firm = companies(:first_firm) firm.clients.create(:name => 'Foo') assert !firm.clients.loaded? assert_queries 3 do firm.clients.first firm.clients.second firm.clients.last end assert !firm.clients.loaded? end def test_calling_first_nth_or_last_on_new_record_should_not_run_queries firm = Firm.new assert_no_queries do firm.clients.first firm.clients.second firm.clients.last end end def test_calling_first_or_last_with_integer_on_association_should_not_load_association firm = companies(:first_firm) firm.clients.create(:name => 'Foo') assert !firm.clients.loaded? assert_queries 2 do firm.clients.first(2) firm.clients.last(2) end assert !firm.clients.loaded? end def test_calling_many_should_count_instead_of_loading_association firm = companies(:first_firm) assert_queries(1) do firm.clients.many? # use count query end assert !firm.clients.loaded? end def test_calling_many_on_loaded_association_should_not_use_query firm = companies(:first_firm) firm.clients.collect # force load assert_no_queries { assert firm.clients.many? } end def test_calling_many_should_defer_to_collection_if_using_a_block firm = companies(:first_firm) assert_queries(1) do firm.clients.expects(:size).never firm.clients.many? { true } end assert firm.clients.loaded? end def test_calling_many_should_return_false_if_none_or_one firm = companies(:another_firm) assert !firm.clients_like_ms.many? assert_equal 0, firm.clients_like_ms.size firm = companies(:first_firm) assert !firm.limited_clients.many? assert_equal 1, firm.limited_clients.size end def test_calling_many_should_return_true_if_more_than_one firm = companies(:first_firm) assert firm.clients.many? assert_equal 3, firm.clients.size end def test_joins_with_namespaced_model_should_use_correct_type old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true firm = Namespaced::Firm.create({ :name => 'Some Company' }) firm.clients.create({ :name => 'Some Client' }) stats = Namespaced::Firm.all.merge!( :select => "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients", :joins => :clients, :group => "#{Namespaced::Firm.table_name}.id" ).find firm.id assert_equal 1, stats.num_clients.to_i ensure ActiveRecord::Base.store_full_sti_class = old end def test_association_proxy_transaction_method_starts_transaction_in_association_class Comment.expects(:transaction) Post.first.comments.transaction do # nothing end end def test_sending_new_to_association_proxy_should_have_same_effect_as_calling_new client_association = companies(:first_firm).clients assert_equal client_association.new.attributes, client_association.send(:new).attributes end def test_respond_to_private_class_methods client_association = companies(:first_firm).clients assert !client_association.respond_to?(:private_method) assert client_association.respond_to?(:private_method, true) end def test_creating_using_primary_key firm = Firm.all.merge!(:order => "id").first client = firm.clients_using_primary_key.create!(:name => 'test') assert_equal firm.name, client.firm_name end def test_defining_has_many_association_with_delete_all_dependency_lazily_evaluates_target_class ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never class_eval(<<-EOF, __FILE__, __LINE__ + 1) class DeleteAllModel < ActiveRecord::Base has_many :nonentities, :dependent => :delete_all end EOF end def test_defining_has_many_association_with_nullify_dependency_lazily_evaluates_target_class ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never class_eval(<<-EOF, __FILE__, __LINE__ + 1) class NullifyModel < ActiveRecord::Base has_many :nonentities, :dependent => :nullify end EOF end def test_attributes_are_being_set_when_initialized_from_has_many_association_with_where_clause new_comment = posts(:welcome).comments.where(:body => "Some content").build assert_equal new_comment.body, "Some content" end def test_attributes_are_being_set_when_initialized_from_has_many_association_with_multiple_where_clauses new_comment = posts(:welcome).comments.where(:body => "Some content").where(:type => 'SpecialComment').build assert_equal new_comment.body, "Some content" assert_equal new_comment.type, "SpecialComment" assert_equal new_comment.post_id, posts(:welcome).id end def test_include_method_in_has_many_association_should_return_true_for_instance_added_with_build post = Post.new comment = post.comments.build assert_equal true, post.comments.include?(comment) end def test_load_target_respects_protected_attributes topic = Topic.create! reply = topic.replies.create(:title => "reply 1") reply.approved = false reply.save! # Save with a different object instance, so the instance that's still held # in topic.relies doesn't know about the changed attribute. reply2 = Reply.find(reply.id) reply2.approved = true reply2.save! # Force loading the collection from the db. This will merge the existing # object (reply) with what gets loaded from the db (which includes the # changed approved attribute). approved is a protected attribute, so if mass # assignment is used, it won't get updated and will still be false. first = topic.replies.to_a.first assert_equal reply.id, first.id assert_equal true, first.approved? end def test_to_a_should_dup_target ary = topics(:first).replies.to_a target = topics(:first).replies.target assert_not_equal target.object_id, ary.object_id end def test_merging_with_custom_attribute_writer bulb = Bulb.new(:color => "red") assert_equal "RED!", bulb.color car = Car.create! car.bulbs << bulb assert_equal "RED!", car.bulbs.to_a.first.color end def test_abstract_class_with_polymorphic_has_many post = SubStiPost.create! :title => "fooo", :body => "baa" tagging = Tagging.create! :taggable => post assert_equal [tagging], post.taggings end def test_with_polymorphic_has_many_with_custom_columns_name post = Post.create! :title => 'foo', :body => 'bar' image = Image.create! post.images << image assert_equal [image], post.images end def test_build_with_polymorphic_has_many_does_not_allow_to_override_type_and_id welcome = posts(:welcome) tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange') assert_equal welcome.id, tagging.taggable_id assert_equal 'Post', tagging.taggable_type end def test_dont_call_save_callbacks_twice_on_has_many firm = companies(:first_firm) contract = firm.contracts.create! assert_equal 1, contract.hi_count assert_equal 1, contract.bye_count end def test_association_attributes_are_available_to_after_initialize car = Car.create(:name => 'honda') bulb = car.bulbs.build assert_equal car.id, bulb.attributes_after_initialize['car_id'] end def test_attributes_are_set_when_initialized_from_has_many_null_relationship car = Car.new name: 'honda' bulb = car.bulbs.where(name: 'headlight').first_or_initialize assert_equal 'headlight', bulb.name end def test_attributes_are_set_when_initialized_from_polymorphic_has_many_null_relationship post = Post.new title: 'title', body: 'bar' tag = Tag.create!(name: 'foo') tagging = post.taggings.where(tag: tag).first_or_initialize assert_equal tag.id, tagging.tag_id assert_equal 'Post', tagging.taggable_type end def test_replace car = Car.create(:name => 'honda') bulb1 = car.bulbs.create bulb2 = Bulb.create assert_equal [bulb1], car.bulbs car.bulbs.replace([bulb2]) assert_equal [bulb2], car.bulbs assert_equal [bulb2], car.reload.bulbs end def test_replace_returns_target car = Car.create(:name => 'honda') bulb1 = car.bulbs.create bulb2 = car.bulbs.create bulb3 = Bulb.create assert_equal [bulb1, bulb2], car.bulbs result = car.bulbs.replace([bulb3, bulb1]) assert_equal [bulb1, bulb3], car.bulbs assert_equal [bulb1, bulb3], result end def test_collection_association_with_private_kernel_method firm = companies(:first_firm) assert_equal [accounts(:signals37)], firm.accounts.open end test "first_or_initialize adds the record to the association" do firm = Firm.create! name: 'omg' client = firm.clients_of_firm.first_or_initialize assert_equal [client], firm.clients_of_firm end test "first_or_create adds the record to the association" do firm = Firm.create! name: 'omg' firm.clients_of_firm.load_target client = firm.clients_of_firm.first_or_create name: 'lol' assert_equal [client], firm.clients_of_firm assert_equal [client], firm.reload.clients_of_firm end test "delete_all, when not loaded, doesn't load the records" do post = posts(:welcome) assert post.taggings_with_delete_all.count > 0 assert !post.taggings_with_delete_all.loaded? # 2 queries: one DELETE and another to update the counter cache assert_queries(2) do post.taggings_with_delete_all.delete_all end end test "has many associations on new records use null relations" do post = Post.new assert_no_queries(ignore_none: false) do assert_equal [], post.comments assert_equal [], post.comments.where(body: 'omg') assert_equal [], post.comments.pluck(:body) assert_equal 0, post.comments.sum(:id) assert_equal 0, post.comments.count end end test "collection proxy respects default scope" do author = authors(:mary) assert !author.first_posts.exists? end test "association with extend option" do post = posts(:welcome) assert_equal "lifo", post.comments_with_extend.author assert_equal "hello", post.comments_with_extend.greeting end test "association with extend option with multiple extensions" do post = posts(:welcome) assert_equal "lifo", post.comments_with_extend_2.author assert_equal "hello", post.comments_with_extend_2.greeting end test "delete record with complex joins" do david = authors(:david) post = david.posts.first post.type = 'PostWithSpecialCategorization' post.save categorization = post.categorizations.first categorization.special = true categorization.save assert_not_equal [], david.posts_with_special_categorizations david.posts_with_special_categorizations = [] assert_equal [], david.posts_with_special_categorizations end test "does not duplicate associations when used with natural primary keys" do speedometer = Speedometer.create!(id: '4') speedometer.minivans.create!(minivan_id: 'a-van-red' ,name: 'a van', color: 'red') assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}" assert_equal 1, speedometer.reload.minivans.to_a.size end test "can unscope the default scope of the associated model" do car = Car.create! bulb1 = Bulb.create! name: "defaulty", car: car bulb2 = Bulb.create! name: "other", car: car assert_equal [bulb1], car.bulbs assert_equal [bulb1, bulb2], car.all_bulbs.sort_by(&:id) end test "can unscope and where the default scope of the associated model" do Car.has_many :other_bulbs, -> { unscope(where: [:name]).where(name: 'other') }, class_name: "Bulb" car = Car.create! bulb1 = Bulb.create! name: "defaulty", car: car bulb2 = Bulb.create! name: "other", car: car assert_equal [bulb1], car.bulbs assert_equal [bulb2], car.other_bulbs end test "can rewhere the default scope of the associated model" do Car.has_many :old_bulbs, -> { rewhere(name: 'old') }, class_name: "Bulb" car = Car.create! bulb1 = Bulb.create! name: "defaulty", car: car bulb2 = Bulb.create! name: "old", car: car assert_equal [bulb1], car.bulbs assert_equal [bulb2], car.old_bulbs end test 'unscopes the default scope of associated model when used with include' do car = Car.create! bulb = Bulb.create! name: "other", car: car assert_equal bulb, Car.find(car.id).all_bulbs.first assert_equal bulb, Car.includes(:all_bulbs).find(car.id).all_bulbs.first end test "raises RecordNotDestroyed when replaced child can't be destroyed" do car = Car.create! original_child = FailedBulb.create!(car: car) error = assert_raise(ActiveRecord::RecordNotDestroyed) do car.failed_bulbs = [FailedBulb.create!] end assert_equal [original_child], car.reload.failed_bulbs assert_equal "Failed to destroy the record", error.message end test 'updates counter cache when default scope is given' do topic = DefaultRejectedTopic.create approved: true assert_difference "topic.reload.replies_count", 1 do topic.approved_replies.create! end end test 'dangerous association name raises ArgumentError' do [:errors, 'errors', :save, 'save'].each do |name| assert_raises(ArgumentError, "Association #{name} should not be allowed") do Class.new(ActiveRecord::Base) do has_many name end end end end test 'passes custom context validation to validate children' do pirate = FamousPirate.new pirate.famous_ships << ship = FamousShip.new assert pirate.valid? assert_not pirate.valid?(:conference) assert_equal "can't be blank", ship.errors[:name].first end test 'association with instance dependent scope' do bob = authors(:bob) Post.create!(title: "signed post by bob", body: "stuff", author: authors(:bob)) Post.create!(title: "anonymous post", body: "more stuff", author: authors(:bob)) assert_equal ["misc post by bob", "other post by bob", "signed post by bob"], bob.posts_with_signature.map(&:title).sort assert_equal [], authors(:david).posts_with_signature.map(&:title) end test 'associations autosaves when object is already persited' do bulb = Bulb.create! tyre = Tyre.create! car = Car.create! do |c| c.bulbs << bulb c.tyres << tyre end assert_equal 1, car.bulbs.count assert_equal 1, car.tyres.count end test 'associations replace in memory when records have the same id' do bulb = Bulb.create! car = Car.create!(bulbs: [bulb]) new_bulb = Bulb.find(bulb.id) new_bulb.name = "foo" car.bulbs = [new_bulb] assert_equal "foo", car.bulbs.first.name end test 'in memory replacement executes no queries' do bulb = Bulb.create! car = Car.create!(bulbs: [bulb]) new_bulb = Bulb.find(bulb.id) assert_no_queries do car.bulbs = [new_bulb] end end test 'in memory replacements do not execute callbacks' do raise_after_add = false klass = Class.new(ActiveRecord::Base) do self.table_name = :cars has_many :bulbs, after_add: proc { raise if raise_after_add } def self.name "Car" end end bulb = Bulb.create! car = klass.create!(bulbs: [bulb]) new_bulb = Bulb.find(bulb.id) raise_after_add = true assert_nothing_raised do car.bulbs = [new_bulb] end end test 'in memory replacements sets inverse instance' do bulb = Bulb.create! car = Car.create!(bulbs: [bulb]) new_bulb = Bulb.find(bulb.id) car.bulbs = [new_bulb] assert_same car, new_bulb.car end test 'in memory replacement maintains order' do first_bulb = Bulb.create! second_bulb = Bulb.create! car = Car.create!(bulbs: [first_bulb, second_bulb]) same_bulb = Bulb.find(first_bulb.id) car.bulbs = [second_bulb, same_bulb] assert_equal [first_bulb, second_bulb], car.bulbs end end rails-4.2.6/activerecord/test/cases/associations/has_many_through_associations_test.rb000066400000000000000000001163261266740050600316040ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/person' require 'models/reference' require 'models/job' require 'models/reader' require 'models/comment' require 'models/rating' require 'models/tag' require 'models/tagging' require 'models/author' require 'models/owner' require 'models/pet' require 'models/toy' require 'models/contract' require 'models/company' require 'models/developer' require 'models/computer' require 'models/subscriber' require 'models/book' require 'models/subscription' require 'models/essay' require 'models/category' require 'models/categorization' require 'models/member' require 'models/membership' require 'models/club' require 'models/organization' class HasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags, :owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses, :subscribers, :books, :subscriptions, :developers, :categorizations, :essays, :categories_posts, :clubs, :memberships, :organizations # Dummies to force column loads so query counts are clean. def setup Person.create :first_name => 'gummy' Reader.create :person_id => 0, :post_id => 0 end def test_preload_sti_rhs_class developers = Developer.includes(:firms).all.to_a assert_no_queries do developers.each { |d| d.firms } end end def test_preload_sti_middle_relation club = Club.create!(name: 'Aaron cool banana club') member1 = Member.create!(name: 'Aaron') member2 = Member.create!(name: 'Cat') SuperMembership.create! club: club, member: member1 CurrentMembership.create! club: club, member: member2 club1 = Club.includes(:members).find_by_id club.id assert_equal [member1, member2].sort_by(&:id), club1.members.sort_by(&:id) end def make_model(name) Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } end def test_ordered_habtm person_prime = Class.new(ActiveRecord::Base) do def self.name; 'Person'; end has_many :readers has_many :posts, -> { order('posts.id DESC') }, :through => :readers end posts = person_prime.includes(:posts).first.posts assert_operator posts.length, :>, 1 posts.each_cons(2) do |left,right| assert_operator left.id, :>, right.id end end def test_singleton_has_many_through book = make_model "Book" subscription = make_model "Subscription" subscriber = make_model "Subscriber" subscriber.primary_key = 'nick' subscription.belongs_to :book, anonymous_class: book subscription.belongs_to :subscriber, anonymous_class: subscriber book.has_many :subscriptions, anonymous_class: subscription book.has_many :subscribers, through: :subscriptions, anonymous_class: subscriber anonbook = book.first namebook = Book.find anonbook.id assert_operator anonbook.subscribers.count, :>, 0 anonbook.subscribers.each do |s| assert_instance_of subscriber, s end assert_equal namebook.subscribers.map(&:id).sort, anonbook.subscribers.map(&:id).sort end def test_no_pk_join_table_append lesson, _, student = make_no_pk_hm_t sicp = lesson.new(:name => "SICP") ben = student.new(:name => "Ben Bitdiddle") sicp.students << ben assert sicp.save! end def test_no_pk_join_table_delete lesson, lesson_student, student = make_no_pk_hm_t sicp = lesson.new(:name => "SICP") ben = student.new(:name => "Ben Bitdiddle") louis = student.new(:name => "Louis Reasoner") sicp.students << ben sicp.students << louis assert sicp.save! sicp.students.reload assert_operator lesson_student.count, :>=, 2 assert_no_difference('student.count') do assert_difference('lesson_student.count', -2) do sicp.students.destroy(*student.all.to_a) end end end def test_no_pk_join_model_callbacks lesson, lesson_student, student = make_no_pk_hm_t after_destroy_called = false lesson_student.after_destroy do after_destroy_called = true end sicp = lesson.new(:name => "SICP") ben = student.new(:name => "Ben Bitdiddle") sicp.students << ben assert sicp.save! sicp.students.reload sicp.students.destroy(*student.all.to_a) assert after_destroy_called, "after destroy should be called" end def make_no_pk_hm_t lesson = make_model 'Lesson' student = make_model 'Student' lesson_student = make_model 'LessonStudent' lesson_student.table_name = 'lessons_students' lesson_student.belongs_to :lesson, :anonymous_class => lesson lesson_student.belongs_to :student, :anonymous_class => student lesson.has_many :lesson_students, :anonymous_class => lesson_student lesson.has_many :students, :through => :lesson_students, :anonymous_class => student [lesson, lesson_student, student] end def test_pk_is_not_required_for_join post = Post.includes(:scategories).first post2 = Post.includes(:categories).first assert_operator post.categories.length, :>, 0 assert_equal post2.categories, post.categories end def test_include? person = Person.new post = Post.new person.posts << post assert person.posts.include?(post) end def test_associate_existing post = posts(:thinking) person = people(:david) assert_queries(1) do post.people << person end assert_queries(1) do assert post.people.include?(person) end assert post.reload.people(true).include?(person) end def test_delete_all_for_with_dependent_option_destroy person = people(:david) assert_equal 1, person.jobs_with_dependent_destroy.count assert_no_difference 'Job.count' do assert_difference 'Reference.count', -1 do person.reload.jobs_with_dependent_destroy.delete_all end end end def test_delete_all_for_with_dependent_option_nullify person = people(:david) assert_equal 1, person.jobs_with_dependent_nullify.count assert_no_difference 'Job.count' do assert_no_difference 'Reference.count' do person.reload.jobs_with_dependent_nullify.delete_all end end end def test_delete_all_for_with_dependent_option_delete_all person = people(:david) assert_equal 1, person.jobs_with_dependent_delete_all.count assert_no_difference 'Job.count' do assert_difference 'Reference.count', -1 do person.reload.jobs_with_dependent_delete_all.delete_all end end end def test_concat person = people(:david) post = posts(:thinking) post.people.concat [person] assert_equal 1, post.people.size assert_equal 1, post.people(true).size end def test_associate_existing_record_twice_should_add_to_target_twice post = posts(:thinking) person = people(:david) assert_difference 'post.people.to_a.count', 2 do post.people << person post.people << person end end def test_associate_existing_record_twice_should_add_records_twice post = posts(:thinking) person = people(:david) assert_difference 'post.people.count', 2 do post.people << person post.people << person end end def test_add_two_instance_and_then_deleting post = posts(:thinking) person = people(:david) post.people << person post.people << person counts = ['post.people.count', 'post.people.to_a.count', 'post.readers.count', 'post.readers.to_a.count'] assert_difference counts, -2 do post.people.delete(person) end assert !post.people.reload.include?(person) end def test_associating_new assert_queries(1) { posts(:thinking) } new_person = nil # so block binding catches it assert_queries(0) do new_person = Person.new :first_name => 'bob' end # Associating new records always saves them # Thus, 1 query for the new person record, 1 query for the new join table record assert_queries(2) do posts(:thinking).people << new_person end assert_queries(1) do assert posts(:thinking).people.include?(new_person) end assert posts(:thinking).reload.people(true).include?(new_person) end def test_associate_new_by_building assert_queries(1) { posts(:thinking) } assert_queries(0) do posts(:thinking).people.build(:first_name => "Bob") posts(:thinking).people.new(:first_name => "Ted") end # Should only need to load the association once assert_queries(1) do assert posts(:thinking).people.collect(&:first_name).include?("Bob") assert posts(:thinking).people.collect(&:first_name).include?("Ted") end # 2 queries for each new record (1 to save the record itself, 1 for the join model) # * 2 new records = 4 # + 1 query to save the actual post = 5 assert_queries(5) do posts(:thinking).body += '-changed' posts(:thinking).save end assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Bob") assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Ted") end def test_build_then_save_with_has_many_inverse post = posts(:thinking) person = post.people.build(:first_name => "Bob") person.save post.reload assert post.people.include?(person) end def test_build_then_save_with_has_one_inverse post = posts(:thinking) person = post.single_people.build(:first_name => "Bob") person.save post.reload assert post.single_people.include?(person) end def test_both_parent_ids_set_when_saving_new post = Post.new(title: 'Hello', body: 'world') person = Person.new(first_name: 'Sean') post.people = [person] post.save assert post.id assert person.id assert_equal post.id, post.readers.first.post_id assert_equal person.id, post.readers.first.person_id end def test_delete_association assert_queries(2){posts(:welcome);people(:michael); } assert_queries(1) do posts(:welcome).people.delete(people(:michael)) end assert_queries(1) do assert posts(:welcome).people.empty? end assert posts(:welcome).reload.people(true).empty? end def test_destroy_association assert_no_difference "Person.count" do assert_difference "Reader.count", -1 do posts(:welcome).people.destroy(people(:michael)) end end assert posts(:welcome).reload.people.empty? assert posts(:welcome).people(true).empty? end def test_destroy_all assert_no_difference "Person.count" do assert_difference "Reader.count", -1 do posts(:welcome).people.destroy_all end end assert posts(:welcome).reload.people.empty? assert posts(:welcome).people(true).empty? end def test_should_raise_exception_for_destroying_mismatching_records assert_no_difference ["Person.count", "Reader.count"] do assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:welcome).people.destroy(posts(:thinking)) } end end def test_delete_through_belongs_to_with_dependent_nullify Reference.make_comments = true person = people(:michael) job = jobs(:magician) reference = Reference.where(:job_id => job.id, :person_id => person.id).first assert_no_difference ['Job.count', 'Reference.count'] do assert_difference 'person.jobs.count', -1 do person.jobs_with_dependent_nullify.delete(job) end end assert_equal nil, reference.reload.job_id ensure Reference.make_comments = false end def test_delete_through_belongs_to_with_dependent_delete_all Reference.make_comments = true person = people(:michael) job = jobs(:magician) # Make sure we're not deleting everything assert person.jobs.count >= 2 assert_no_difference 'Job.count' do assert_difference ['person.jobs.count', 'Reference.count'], -1 do person.jobs_with_dependent_delete_all.delete(job) end end # Check that the destroy callback on Reference did not run assert_equal nil, person.reload.comments ensure Reference.make_comments = false end def test_delete_through_belongs_to_with_dependent_destroy Reference.make_comments = true person = people(:michael) job = jobs(:magician) # Make sure we're not deleting everything assert person.jobs.count >= 2 assert_no_difference 'Job.count' do assert_difference ['person.jobs.count', 'Reference.count'], -1 do person.jobs_with_dependent_destroy.delete(job) end end # Check that the destroy callback on Reference ran assert_equal "Reference destroyed", person.reload.comments ensure Reference.make_comments = false end def test_belongs_to_with_dependent_destroy person = PersonWithDependentDestroyJobs.find(1) # Create a reference which is not linked to a job. This should not be destroyed. person.references.create! assert_no_difference 'Job.count' do assert_difference 'Reference.count', -person.jobs.count do person.destroy end end end def test_belongs_to_with_dependent_delete_all person = PersonWithDependentDeleteAllJobs.find(1) # Create a reference which is not linked to a job. This should not be destroyed. person.references.create! assert_no_difference 'Job.count' do assert_difference 'Reference.count', -person.jobs.count do person.destroy end end end def test_belongs_to_with_dependent_nullify person = PersonWithDependentNullifyJobs.find(1) references = person.references.to_a assert_no_difference ['Reference.count', 'Job.count'] do person.destroy end references.each do |reference| assert_equal nil, reference.reload.job_id end end def test_update_counter_caches_on_delete post = posts(:welcome) tag = post.tags.create!(:name => 'doomed') assert_difference ['post.reload.tags_count'], -1 do posts(:welcome).tags.delete(tag) end end def test_update_counter_caches_on_delete_with_dependent_destroy post = posts(:welcome) tag = post.tags.create!(:name => 'doomed') post.update_columns(tags_with_destroy_count: post.tags.count) assert_difference ['post.reload.tags_with_destroy_count'], -1 do posts(:welcome).tags_with_destroy.delete(tag) end end def test_update_counter_caches_on_delete_with_dependent_nullify post = posts(:welcome) tag = post.tags.create!(:name => 'doomed') post.update_columns(tags_with_nullify_count: post.tags.count) assert_no_difference 'post.reload.tags_count' do assert_difference 'post.reload.tags_with_nullify_count', -1 do posts(:welcome).tags_with_nullify.delete(tag) end end end def test_update_counter_caches_on_replace_association post = posts(:welcome) tag = post.tags.create!(:name => 'doomed') tag.tagged_posts << posts(:thinking) tag.tagged_posts = [] post.reload assert_equal(post.taggings.count, post.tags_count) end def test_update_counter_caches_on_destroy post = posts(:welcome) tag = post.tags.create!(name: 'doomed') assert_difference 'post.reload.tags_count', -1 do tag.tagged_posts.destroy(post) end end def test_replace_association assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)} # 1 query to delete the existing reader (michael) # 1 query to associate the new reader (david) assert_queries(2) do posts(:welcome).people = [people(:david)] end assert_queries(0){ assert posts(:welcome).people.include?(people(:david)) assert !posts(:welcome).people.include?(people(:michael)) } assert posts(:welcome).reload.people(true).include?(people(:david)) assert !posts(:welcome).reload.people(true).include?(people(:michael)) end def test_replace_order_is_preserved posts(:welcome).people.clear posts(:welcome).people = [people(:david), people(:michael)] assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.order('id').map(&:person_id) # Test the inverse order in case the first success was a coincidence posts(:welcome).people.clear posts(:welcome).people = [people(:michael), people(:david)] assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.order('id').map(&:person_id) end def test_replace_by_id_order_is_preserved posts(:welcome).people.clear posts(:welcome).person_ids = [people(:david).id, people(:michael).id] assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.order('id').map(&:person_id) # Test the inverse order in case the first success was a coincidence posts(:welcome).people.clear posts(:welcome).person_ids = [people(:michael).id, people(:david).id] assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.order('id').map(&:person_id) end def test_associate_with_create assert_queries(1) { posts(:thinking) } # 1 query for the new record, 1 for the join table record # No need to update the actual collection yet! assert_queries(2) do posts(:thinking).people.create(:first_name=>"Jeb") end # *Now* we actually need the collection so it's loaded assert_queries(1) do assert posts(:thinking).people.collect(&:first_name).include?("Jeb") end assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Jeb") end def test_through_record_is_built_when_created_with_where assert_difference("posts(:thinking).readers.count", 1) do posts(:thinking).people.where(first_name: "Jeb").create end end def test_associate_with_create_and_no_options peeps = posts(:thinking).people.count posts(:thinking).people.create(:first_name => 'foo') assert_equal peeps + 1, posts(:thinking).people.count end def test_associate_with_create_with_through_having_conditions impatient_people = posts(:thinking).impatient_people.count posts(:thinking).impatient_people.create!(:first_name => 'foo') assert_equal impatient_people + 1, posts(:thinking).impatient_people.count end def test_associate_with_create_exclamation_and_no_options peeps = posts(:thinking).people.count posts(:thinking).people.create!(:first_name => 'foo') assert_equal peeps + 1, posts(:thinking).people.count end def test_create_on_new_record p = Post.new error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") } assert_equal "You cannot call create unless the parent is saved", error.message error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") } assert_equal "You cannot call create unless the parent is saved", error.message end def test_associate_with_create_and_invalid_options firm = companies(:first_firm) assert_no_difference('firm.developers.count') { assert_nothing_raised { firm.developers.create(:name => '0') } } end def test_associate_with_create_and_valid_options firm = companies(:first_firm) assert_difference('firm.developers.count', 1) { firm.developers.create(:name => 'developer') } end def test_associate_with_create_bang_and_invalid_options firm = companies(:first_firm) assert_no_difference('firm.developers.count') { assert_raises(ActiveRecord::RecordInvalid) { firm.developers.create!(:name => '0') } } end def test_associate_with_create_bang_and_valid_options firm = companies(:first_firm) assert_difference('firm.developers.count', 1) { firm.developers.create!(:name => 'developer') } end def test_push_with_invalid_record firm = companies(:first_firm) assert_raises(ActiveRecord::RecordInvalid) { firm.developers << Developer.new(:name => '0') } end def test_push_with_invalid_join_record repair_validations(Contract) do Contract.validate {|r| r.errors[:base] << 'Invalid Contract' } firm = companies(:first_firm) lifo = Developer.new(:name => 'lifo') assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo } lifo = Developer.create!(:name => 'lifo') assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo } end end def test_clear_associations assert_queries(2) { posts(:welcome);posts(:welcome).people(true) } assert_queries(1) do posts(:welcome).people.clear end assert_queries(0) do assert posts(:welcome).people.empty? end assert posts(:welcome).reload.people(true).empty? end def test_association_callback_ordering Post.reset_log log = Post.log post = posts(:thinking) post.people_with_callbacks << people(:michael) assert_equal [ [:added, :before, "Michael"], [:added, :after, "Michael"] ], log.last(2) post.people_with_callbacks.push(people(:david), Person.create!(:first_name => "Bob"), Person.new(:first_name => "Lary")) assert_equal [ [:added, :before, "David"], [:added, :after, "David"], [:added, :before, "Bob"], [:added, :after, "Bob"], [:added, :before, "Lary"], [:added, :after, "Lary"] ],log.last(6) post.people_with_callbacks.build(:first_name => "Ted") assert_equal [ [:added, :before, "Ted"], [:added, :after, "Ted"] ], log.last(2) post.people_with_callbacks.create(:first_name => "Sam") assert_equal [ [:added, :before, "Sam"], [:added, :after, "Sam"] ], log.last(2) post.people_with_callbacks = [people(:michael),people(:david), Person.new(:first_name => "Julian"), Person.create!(:first_name => "Roger")] assert_equal((%w(Ted Bob Sam Lary) * 2).sort, log[-12..-5].collect(&:last).sort) assert_equal [ [:added, :before, "Julian"], [:added, :after, "Julian"], [:added, :before, "Roger"], [:added, :after, "Roger"] ], log.last(4) end def test_dynamic_find_should_respect_association_include # SQL error in sort clause if :include is not included # due to Unknown column 'comments.id' assert Person.find(1).posts_with_comments_sorted_by_comment_id.find_by_title('Welcome to the weblog') end def test_count_with_include_should_alias_join_table assert_equal 2, people(:michael).posts.includes(:readers).count end def test_inner_join_with_quoted_table_name assert_equal 2, people(:michael).jobs.size end def test_get_ids assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort end def test_get_ids_for_has_many_through_with_conditions_should_not_preload Tagging.create!(:taggable_type => 'Post', :taggable_id => posts(:welcome).id, :tag => tags(:misc)) ActiveRecord::Associations::Preloader.expects(:new).never posts(:welcome).misc_tag_ids end def test_get_ids_for_loaded_associations person = people(:michael) person.posts(true) assert_queries(0) do person.post_ids person.post_ids end end def test_get_ids_for_unloaded_associations_does_not_load_them person = people(:michael) assert !person.posts.loaded? assert_equal [posts(:welcome).id, posts(:authorless).id].sort, person.post_ids.sort assert !person.posts.loaded? end def test_association_proxy_transaction_method_starts_transaction_in_association_class Tag.expects(:transaction) Post.first.tags.transaction do # nothing end end def test_has_many_association_through_a_belongs_to_association_where_the_association_doesnt_exist post = Post.create!(:title => "TITLE", :body => "BODY") assert_equal [], post.author_favorites end def test_has_many_association_through_a_belongs_to_association author = authors(:mary) post = Post.create!(:author => author, :title => "TITLE", :body => "BODY") author.author_favorites.create(:favorite_author_id => 1) author.author_favorites.create(:favorite_author_id => 2) author.author_favorites.create(:favorite_author_id => 3) assert_equal post.author.author_favorites, post.author_favorites end def test_merge_join_association_with_has_many_through_association_proxy author = authors(:mary) assert_nothing_raised { author.comments.ratings.to_sql } end def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys assert_equal 2, owners(:blackbeard).toys.count end def test_find_on_has_many_association_collection_with_include_and_conditions post_with_no_comments = people(:michael).posts_with_no_comments.first assert_equal post_with_no_comments, posts(:authorless) end def test_has_many_through_has_one_reflection assert_equal [comments(:eager_sti_on_associations_vs_comment)], authors(:david).very_special_comments end def test_modifying_has_many_through_has_one_reflection_should_raise [ lambda { authors(:david).very_special_comments = [VerySpecialComment.create!(:body => "Gorp!", :post_id => 1011), VerySpecialComment.create!(:body => "Eep!", :post_id => 1012)] }, lambda { authors(:david).very_special_comments << VerySpecialComment.create!(:body => "Hoohah!", :post_id => 1013) }, lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) }, ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } end def test_has_many_association_through_a_has_many_association_to_self sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1) john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1) assert_equal sarah.agents, [john] assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to Categorization.create(:author => authors(:mary), :named_category_name => categories(:general).name) assert_equal categories(:general), authors(:mary).named_categories.first end def test_collection_build_with_nonstandard_primary_key_on_belongs_to author = authors(:mary) category = author.named_categories.build(:name => "Primary") author.save assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name) assert author.named_categories(true).include?(category) end def test_collection_create_with_nonstandard_primary_key_on_belongs_to author = authors(:mary) category = author.named_categories.create(:name => "Primary") assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name) assert author.named_categories(true).include?(category) end def test_collection_exists author = authors(:mary) category = Category.create!(author_ids: [author.id], name: "Primary") assert category.authors.exists?(id: author.id) assert category.reload.authors.exists?(id: author.id) end def test_collection_delete_with_nonstandard_primary_key_on_belongs_to author = authors(:mary) category = author.named_categories.create(:name => "Primary") author.named_categories.delete(category) assert !Categorization.exists?(:author_id => author.id, :named_category_name => category.name) assert author.named_categories(true).empty? end def test_collection_singular_ids_getter_with_string_primary_keys book = books(:awdr) assert_equal 2, book.subscriber_ids.size assert_equal [subscribers(:first).nick, subscribers(:second).nick].sort, book.subscriber_ids.sort end def test_collection_singular_ids_setter company = companies(:rails_core) dev = Developer.first company.developer_ids = [dev.id] assert_equal [dev], company.developers end def test_collection_singular_ids_setter_with_string_primary_keys assert_nothing_raised do book = books(:awdr) book.subscriber_ids = [subscribers(:second).nick] assert_equal [subscribers(:second)], book.subscribers(true) book.subscriber_ids = [] assert_equal [], book.subscribers(true) end end def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set company = companies(:rails_core) ids = [Developer.first.id, -9999] assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids} end def test_build_a_model_from_hm_through_association_with_where_clause assert_nothing_raised { books(:awdr).subscribers.where(:nick => "marklazz").build } end def test_attributes_are_being_set_when_initialized_from_hm_through_association_with_where_clause new_subscriber = books(:awdr).subscribers.where(:nick => "marklazz").build assert_equal new_subscriber.nick, "marklazz" end def test_attributes_are_being_set_when_initialized_from_hm_through_association_with_multiple_where_clauses new_subscriber = books(:awdr).subscribers.where(:nick => "marklazz").where(:name => 'Marcelo Giorgi').build assert_equal new_subscriber.nick, "marklazz" assert_equal new_subscriber.name, "Marcelo Giorgi" end def test_include_method_in_association_through_should_return_true_for_instance_added_with_build person = Person.new reference = person.references.build job = reference.build_job assert person.jobs.include?(job) end def test_include_method_in_association_through_should_return_true_for_instance_added_with_nested_builds author = Author.new post = author.posts.build comment = post.comments.build assert author.comments.include?(comment) end def test_through_association_readonly_should_be_false assert !people(:michael).posts.first.readonly? assert !people(:michael).posts.to_a.first.readonly? end def test_can_update_through_association assert_nothing_raised do people(:michael).posts.first.update!(title: "Can write") end end def test_has_many_through_polymorphic_with_primary_key_option assert_equal [categories(:general)], authors(:david).essay_categories authors = Author.joins(:essay_categories).where('categories.id' => categories(:general).id) assert_equal authors(:david), authors.first assert_equal [owners(:blackbeard)], authors(:david).essay_owners authors = Author.joins(:essay_owners).where("owners.name = 'blackbeard'") assert_equal authors(:david), authors.first end def test_has_many_through_with_primary_key_option assert_equal [categories(:general)], authors(:david).essay_categories_2 authors = Author.joins(:essay_categories_2).where('categories.id' => categories(:general).id) assert_equal authors(:david), authors.first end def test_size_of_through_association_should_increase_correctly_when_has_many_association_is_added post = posts(:thinking) readers = post.readers.size post.people << people(:michael) assert_equal readers + 1, post.readers.size end def test_has_many_through_with_default_scope_on_join_model assert_equal posts(:welcome).comments.order('id').to_a, authors(:david).comments_on_first_posts end def test_create_has_many_through_with_default_scope_on_join_model category = authors(:david).special_categories.create(:name => "Foo") assert_equal 1, category.categorizations.where(:special => true).count end def test_joining_has_many_through_with_uniq mary = Author.joins(:unique_categorized_posts).where(:id => authors(:mary).id).first assert_equal 1, mary.unique_categorized_posts.length assert_equal 1, mary.unique_categorized_post_ids.length end def test_joining_has_many_through_belongs_to posts = Post.joins(:author_categorizations).order('posts.id'). where('categorizations.id' => categorizations(:mary_thinking_sti).id) assert_equal [posts(:eager_other), posts(:misc_by_mary), posts(:other_by_mary)], posts end def test_select_chosen_fields_only author = authors(:david) assert_equal ['body', 'id'].sort, author.comments.select('comments.body').first.attributes.keys.sort end def test_get_has_many_through_belongs_to_ids_with_conditions assert_equal [categories(:general).id], authors(:mary).categories_like_general_ids end def test_get_collection_singular_ids_on_has_many_through_with_conditions_and_include person = Person.first assert_equal person.posts_with_no_comment_ids, person.posts_with_no_comments.map(&:id) end def test_count_has_many_through_with_named_scope assert_equal 2, authors(:mary).categories.count assert_equal 1, authors(:mary).categories.general.count end def test_has_many_through_belongs_to_should_update_when_the_through_foreign_key_changes post = posts(:eager_other) post.author_categorizations proxy = post.send(:association_instance_get, :author_categorizations) assert !proxy.stale_target? assert_equal authors(:mary).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id) post.author_id = authors(:david).id assert proxy.stale_target? assert_equal authors(:david).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id) end def test_create_with_conditions_hash_on_through_association member = members(:groucho) club = member.clubs.create! assert_equal true, club.reload.membership.favourite end def test_deleting_from_has_many_through_a_belongs_to_should_not_try_to_update_counter post = posts(:welcome) address = author_addresses(:david_address) assert post.author_addresses.include?(address) post.author_addresses.delete(address) assert post[:author_count].nil? end def test_primary_key_option_on_source post = posts(:welcome) category = categories(:general) Categorization.create!(:post_id => post.id, :named_category_name => category.name) assert_equal [category], post.named_categories assert_equal [category.name], post.named_category_ids # checks when target loaded assert_equal [category.name], post.reload.named_category_ids # checks when target no loaded end def test_create_should_not_raise_exception_when_join_record_has_errors repair_validations(Categorization) do Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } Category.create(:name => 'Fishing', :authors => [Author.first]) end end def test_save_should_not_raise_exception_when_join_record_has_errors repair_validations(Categorization) do Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } c = Category.create(:name => 'Fishing', :authors => [Author.first]) c.save end end def test_assign_array_to_new_record_builds_join_records c = Category.new(:name => 'Fishing', :authors => [Author.first]) assert_equal 1, c.categorizations.size end def test_create_bang_should_raise_exception_when_join_record_has_errors repair_validations(Categorization) do Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } assert_raises(ActiveRecord::RecordInvalid) do Category.create!(:name => 'Fishing', :authors => [Author.first]) end end end def test_save_bang_should_raise_exception_when_join_record_has_errors repair_validations(Categorization) do Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } c = Category.new(:name => 'Fishing', :authors => [Author.first]) assert_raises(ActiveRecord::RecordInvalid) do c.save! end end end def test_create_bang_returns_falsy_when_join_record_has_errors repair_validations(Categorization) do Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } c = Category.new(:name => 'Fishing', :authors => [Author.first]) assert !c.save end end def test_preloading_empty_through_association_via_joins person = Person.create!(:first_name => "Gaga") person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').references(:readers).includes(:posts).to_a.first assert person.posts.loaded?, 'person.posts should be loaded' assert_equal [], person.posts end def test_explicitly_joining_join_table assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet end def test_has_many_through_with_polymorphic_source post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar" assert_equal [tags(:general)], post.reload.tags end def test_has_many_through_obeys_order_on_through_association owner = owners(:blackbeard) assert owner.toys.to_sql.include?("pets.name desc") assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name } end def test_has_many_through_associations_on_new_records_use_null_relations person = Person.new assert_no_queries(ignore_none: false) do assert_equal [], person.posts assert_equal [], person.posts.where(body: 'omg') assert_equal [], person.posts.pluck(:body) assert_equal 0, person.posts.sum(:tags_count) assert_equal 0, person.posts.count end end def test_has_many_through_with_default_scope_on_the_target person = people(:michael) assert_equal [posts(:thinking)], person.first_posts readers(:michael_authorless).update(first_post_id: 1) assert_equal [posts(:thinking)], person.reload.first_posts end def test_has_many_through_with_includes_in_through_association_scope assert_not_empty posts(:welcome).author_address_extra_with_address end def test_insert_records_via_has_many_through_association_with_scope club = Club.create! member = Member.create! Membership.create!(club: club, member: member) club.favourites << member assert_equal [member], club.favourites club.reload assert_equal [member], club.favourites end def test_has_many_through_unscope_default_scope post = Post.create!(:title => 'Beaches', :body => "I like beaches!") Reader.create! :person => people(:david), :post => post LazyReader.create! :person => people(:susan), :post => post assert_equal 2, post.people.to_a.size assert_equal 1, post.lazy_people.to_a.size assert_equal 2, post.lazy_readers_unscope_skimmers.to_a.size assert_equal 2, post.lazy_people_unscope_skimmers.to_a.size end def test_has_many_through_add_with_sti_middle_relation club = SuperClub.create!(name: 'Fight Club') member = Member.create!(name: 'Tyler Durden') club.members << member assert_equal 1, SuperMembership.where(member_id: member.id, club_id: club.id).count end def test_build_for_has_many_through_association organization = organizations(:nsa) author = organization.author post_direct = author.posts.build post_through = organization.posts.build assert_equal post_direct.author_id, post_through.author_id end def test_has_many_through_with_scope_that_should_not_be_fully_merged Club.has_many :distinct_memberships, -> { distinct }, class_name: "Membership" Club.has_many :special_favourites, through: :distinct_memberships, source: :member assert_nil Club.new.special_favourites.distinct_value end def test_has_many_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes member = Member.create! club = Club.create! TenantMembership.create!( member: member, club: club ) TenantMembership.current_member = member tenant_clubs = member.tenant_clubs assert_equal [club], tenant_clubs TenantMembership.current_member = nil other_member = Member.create! other_club = Club.create! TenantMembership.create!( member: other_member, club: other_club ) tenant_clubs = other_member.tenant_clubs assert_equal [other_club], tenant_clubs ensure TenantMembership.current_member = nil end end rails-4.2.6/activerecord/test/cases/associations/has_one_associations_test.rb000066400000000000000000000445531266740050600276630ustar00rootroot00000000000000require "cases/helper" require 'models/developer' require 'models/computer' require 'models/project' require 'models/company' require 'models/ship' require 'models/pirate' require 'models/car' require 'models/bulb' require 'models/author' require 'models/image' require 'models/post' class HasOneAssociationsTest < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates def setup Account.destroyed_account_ids.clear end def test_has_one assert_equal companies(:first_firm).account, Account.find(1) assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit end def test_has_one_does_not_use_order_by ActiveRecord::SQLCounter.clear_log companies(:first_firm).account ensure assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query' end def test_has_one_cache_nils firm = companies(:another_firm) assert_queries(1) { assert_nil firm.account } assert_queries(0) { assert_nil firm.account } firms = Firm.all.merge!(:includes => :account).to_a assert_queries(0) { firms.each(&:account) } end def test_with_select assert_equal Firm.find(1).account_with_select.attributes.size, 2 assert_equal Firm.all.merge!(:includes => :account_with_select).find(1).account_with_select.attributes.size, 2 end def test_finding_using_primary_key firm = companies(:first_firm) assert_equal Account.find_by_firm_id(firm.id), firm.account firm.firm_id = companies(:rails_core).id assert_equal accounts(:rails_core_account), firm.account_using_primary_key end def test_update_with_foreign_and_primary_keys firm = companies(:first_firm) account = firm.account_using_foreign_and_primary_keys assert_equal Account.find_by_firm_name(firm.name), account firm.save firm.reload assert_equal account, firm.account_using_foreign_and_primary_keys end def test_can_marshal_has_one_association_with_nil_target firm = Firm.new assert_nothing_raised do assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes end firm.account assert_nothing_raised do assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes end end def test_proxy_assignment company = companies(:first_firm) assert_nothing_raised { company.account = company.account } end def test_type_mismatch assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 } assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) } end def test_natural_assignment apple = Firm.create("name" => "Apple") citibank = Account.create("credit_limit" => 10) apple.account = citibank assert_equal apple.id, citibank.firm_id end def test_natural_assignment_to_nil old_account_id = companies(:first_firm).account.id companies(:first_firm).account = nil companies(:first_firm).save assert_nil companies(:first_firm).account # account is dependent, therefore is destroyed when reference to owner is lost assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) } end def test_nullification_on_association_change firm = companies(:rails_core) old_account_id = firm.account.id firm.account = Account.new(:credit_limit => 5) # account is dependent with nullify, therefore its firm_id should be nil assert_nil Account.find(old_account_id).firm_id end def test_natural_assignment_to_nil_after_destroy firm = companies(:rails_core) old_account_id = firm.account.id firm.account.destroy firm.account = nil assert_nil companies(:rails_core).account assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) } end def test_association_change_calls_delete companies(:first_firm).deletable_account = Account.new(:credit_limit => 5) assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id] end def test_association_change_calls_destroy companies(:first_firm).account = Account.new(:credit_limit => 5) assert_equal [companies(:first_firm).id], Account.destroyed_account_ids[companies(:first_firm).id] end def test_natural_assignment_to_already_associated_record company = companies(:first_firm) account = accounts(:signals37) assert_equal company.account, account company.account = account company.reload account.reload assert_equal company.account, account end def test_dependence num_accounts = Account.count firm = Firm.find(1) assert_not_nil firm.account account_id = firm.account.id assert_equal [], Account.destroyed_account_ids[firm.id] firm.destroy assert_equal num_accounts - 1, Account.count assert_equal [account_id], Account.destroyed_account_ids[firm.id] end def test_exclusive_dependence num_accounts = Account.count firm = ExclusivelyDependentFirm.find(9) assert_not_nil firm.account assert_equal [], Account.destroyed_account_ids[firm.id] firm.destroy assert_equal num_accounts - 1, Account.count assert_equal [], Account.destroyed_account_ids[firm.id] end def test_dependence_with_nil_associate firm = DependentFirm.new(:name => 'nullify') firm.save! assert_nothing_raised { firm.destroy } end def test_restrict_with_exception firm = RestrictedWithExceptionFirm.create!(:name => 'restrict') firm.create_account(:credit_limit => 10) assert_not_nil firm.account assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } assert RestrictedWithExceptionFirm.exists?(:name => 'restrict') assert firm.account.present? end def test_restrict_with_error firm = RestrictedWithErrorFirm.create!(:name => 'restrict') firm.create_account(:credit_limit => 10) assert_not_nil firm.account firm.destroy assert !firm.errors.empty? assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first assert RestrictedWithErrorFirm.exists?(:name => 'restrict') assert firm.account.present? end def test_successful_build_association firm = Firm.new("name" => "GlobalMegaCorp") firm.save account = firm.build_account("credit_limit" => 1000) assert account.save assert_equal account, firm.account end def test_build_association_dont_create_transaction assert_no_queries(ignore_none: false) { Firm.new.build_account } end def test_building_the_associated_object_with_implicit_sti_base_class firm = DependentFirm.new company = firm.build_company assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_associated_object_with_explicit_sti_base_class firm = DependentFirm.new company = firm.build_company(:type => "Company") assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_associated_object_with_sti_subclass firm = DependentFirm.new company = firm.build_company(:type => "Client") assert_kind_of Client, company, "Expected #{company.class} to be a Client" end def test_building_the_associated_object_with_an_invalid_type firm = DependentFirm.new assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(:type => "Invalid") } end def test_building_the_associated_object_with_an_unrelated_type firm = DependentFirm.new assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(:type => "Account") } end def test_build_and_create_should_not_happen_within_scope pirate = pirates(:blackbeard) scoped_count = pirate.association(:foo_bulb).scope.where_values.count bulb = pirate.build_foo_bulb assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count bulb = pirate.create_foo_bulb assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count bulb = pirate.create_foo_bulb! assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count end def test_create_association firm = Firm.create(:name => "GlobalMegaCorp") account = firm.create_account(:credit_limit => 1000) assert_equal account, firm.reload.account end def test_create_association_with_bang firm = Firm.create(:name => "GlobalMegaCorp") account = firm.create_account!(:credit_limit => 1000) assert_equal account, firm.reload.account end def test_create_association_with_bang_failing firm = Firm.create(:name => "GlobalMegaCorp") assert_raise ActiveRecord::RecordInvalid do firm.create_account! end account = firm.account assert_not_nil account account.credit_limit = 5 account.save assert_equal account, firm.reload.account end def test_create_with_inexistent_foreign_key_failing firm = Firm.create(name: 'GlobalMegaCorp') assert_raises(ActiveRecord::UnknownAttributeError) do firm.create_account_with_inexistent_foreign_key end end def test_build firm = Firm.new("name" => "GlobalMegaCorp") firm.save firm.account = account = Account.new("credit_limit" => 1000) assert_equal account, firm.account assert account.save assert_equal account, firm.account end def test_create firm = Firm.new("name" => "GlobalMegaCorp") firm.save firm.account = account = Account.create("credit_limit" => 1000) assert_equal account, firm.account end def test_create_before_save firm = Firm.new("name" => "GlobalMegaCorp") firm.account = account = Account.create("credit_limit" => 1000) assert_equal account, firm.account end def test_dependence_with_missing_association Account.destroy_all firm = Firm.find(1) assert_nil firm.account firm.destroy end def test_dependence_with_missing_association_and_nullify Account.destroy_all firm = DependentFirm.first assert_nil firm.account firm.destroy end def test_finding_with_interpolated_condition firm = Firm.first superior = firm.clients.create(:name => 'SuperiorCo') superior.rating = 10 superior.save assert_equal 10, firm.clients_with_interpolated_conditions.first.rating end def test_assignment_before_child_saved firm = Firm.find(1) firm.account = a = Account.new("credit_limit" => 1000) assert a.persisted? assert_equal a, firm.account assert_equal a, firm.account assert_equal a, firm.account(true) end def test_save_still_works_after_accessing_nil_has_one jp = Company.new :name => 'Jaded Pixel' jp.dummy_account.nil? assert_nothing_raised do jp.save! end end def test_cant_save_readonly_association assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save! } assert companies(:first_firm).readonly_account.readonly? end def test_has_one_proxy_should_not_respond_to_private_methods assert_raise(NoMethodError) { accounts(:signals37).private_method } assert_raise(NoMethodError) { companies(:first_firm).account.private_method } end def test_has_one_proxy_should_respond_to_private_methods_via_send accounts(:signals37).send(:private_method) companies(:first_firm).account.send(:private_method) end def test_save_of_record_with_loaded_has_one @firm = companies(:first_firm) assert_not_nil @firm.account assert_nothing_raised do Firm.find(@firm.id).save! Firm.all.merge!(:includes => :account).find(@firm.id).save! end @firm.account.destroy assert_nothing_raised do Firm.find(@firm.id).save! Firm.all.merge!(:includes => :account).find(@firm.id).save! end end def test_build_respects_hash_condition account = companies(:first_firm).build_account_limit_500_with_hash_conditions assert account.save assert_equal 500, account.credit_limit end def test_create_respects_hash_condition account = companies(:first_firm).create_account_limit_500_with_hash_conditions assert account.persisted? assert_equal 500, account.credit_limit end def test_attributes_are_being_set_when_initialized_from_has_one_association_with_where_clause new_account = companies(:first_firm).build_account(:firm_name => 'Account') assert_equal new_account.firm_name, "Account" end def test_creation_failure_without_dependent_option pirate = pirates(:blackbeard) orig_ship = pirate.ship assert_equal ships(:black_pearl), orig_ship new_ship = pirate.create_ship assert_not_equal ships(:black_pearl), new_ship assert_equal new_ship, pirate.ship assert new_ship.new_record? assert_nil orig_ship.pirate_id assert !orig_ship.changed? # check it was saved end def test_creation_failure_with_dependent_option pirate = pirates(:blackbeard).becomes(DestructivePirate) orig_ship = pirate.dependent_ship new_ship = pirate.create_dependent_ship assert new_ship.new_record? assert orig_ship.destroyed? end def test_creation_failure_due_to_new_record_should_raise_error pirate = pirates(:redbeard) new_ship = Ship.new error = assert_raise(ActiveRecord::RecordNotSaved) do pirate.ship = new_ship end assert_equal "Failed to save the new associated ship.", error.message assert_nil pirate.ship assert_nil new_ship.pirate_id end def test_replacement_failure_due_to_existing_record_should_raise_error pirate = pirates(:blackbeard) pirate.ship.name = nil assert !pirate.ship.valid? error = assert_raise(ActiveRecord::RecordNotSaved) do pirate.ship = ships(:interceptor) end assert_equal ships(:black_pearl), pirate.ship assert_equal pirate.id, pirate.ship.pirate_id assert_equal "Failed to remove the existing associated ship. " + "The record failed to save after its foreign key was set to nil.", error.message end def test_replacement_failure_due_to_new_record_should_raise_error pirate = pirates(:blackbeard) new_ship = Ship.new error = assert_raise(ActiveRecord::RecordNotSaved) do pirate.ship = new_ship end assert_equal "Failed to save the new associated ship.", error.message assert_equal ships(:black_pearl), pirate.ship assert_equal pirate.id, pirate.ship.pirate_id assert_equal pirate.id, ships(:black_pearl).reload.pirate_id assert_nil new_ship.pirate_id end def test_association_keys_bypass_attribute_protection car = Car.create(:name => 'honda') bulb = car.build_bulb assert_equal car.id, bulb.car_id bulb = car.build_bulb :car_id => car.id + 1 assert_equal car.id, bulb.car_id bulb = car.create_bulb assert_equal car.id, bulb.car_id bulb = car.create_bulb :car_id => car.id + 1 assert_equal car.id, bulb.car_id end def test_association_protect_foreign_key pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") ship = pirate.build_ship assert_equal pirate.id, ship.pirate_id ship = pirate.build_ship :pirate_id => pirate.id + 1 assert_equal pirate.id, ship.pirate_id ship = pirate.create_ship assert_equal pirate.id, ship.pirate_id ship = pirate.create_ship :pirate_id => pirate.id + 1 assert_equal pirate.id, ship.pirate_id end def test_build_with_block car = Car.create(:name => 'honda') bulb = car.build_bulb{ |b| b.color = 'Red' } assert_equal 'RED!', bulb.color end def test_create_with_block car = Car.create(:name => 'honda') bulb = car.create_bulb{ |b| b.color = 'Red' } assert_equal 'RED!', bulb.color end def test_create_bang_with_block car = Car.create(:name => 'honda') bulb = car.create_bulb!{ |b| b.color = 'Red' } assert_equal 'RED!', bulb.color end def test_association_attributes_are_available_to_after_initialize car = Car.create(:name => 'honda') bulb = car.create_bulb assert_equal car.id, bulb.attributes_after_initialize['car_id'] end def test_has_one_transaction company = companies(:first_firm) account = Account.find(1) company.account # force loading assert_no_queries { company.account = account } company.account = nil assert_no_queries { company.account = nil } account = Account.find(2) assert_queries { company.account = account } assert_no_queries { Firm.new.account = account } end def test_has_one_assignment_dont_trigger_save_on_change_of_same_object pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") ship = pirate.build_ship(name: 'old name') ship.save! ship.name = 'new name' assert ship.changed? assert_queries(1) do # One query for updating name, not triggering query for updating pirate_id pirate.ship = ship end assert_equal 'new name', pirate.ship.reload.name end def test_has_one_assignment_triggers_save_on_change_on_replacing_object pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") ship = pirate.build_ship(name: 'old name') ship.save! new_ship = Ship.create(name: 'new name') assert_queries(2) do # One query for updating name and second query for updating pirate_id pirate.ship = new_ship end assert_equal 'new name', pirate.ship.reload.name end def test_has_one_autosave_with_primary_key_manually_set post = Post.create(id: 1234, title: "Some title", body: 'Some content') author = Author.new(id: 33, name: 'Hank Moody') author.post = post author.save author.reload assert_not_nil author.post assert_equal author.post, post end def test_has_one_loading_for_new_record Post.create!(author_id: 42, title: 'foo', body: 'bar') author = Author.new(id: 42) assert author.post end def test_has_one_relationship_cannot_have_a_counter_cache assert_raise(ArgumentError) do Class.new(ActiveRecord::Base) do has_one :thing, counter_cache: true end end end def test_with_polymorphic_has_one_with_custom_columns_name post = Post.create! :title => 'foo', :body => 'bar' image = Image.create! post.main_image = image post.reload assert_equal image, post.main_image end test 'dangerous association name raises ArgumentError' do [:errors, 'errors', :save, 'save'].each do |name| assert_raises(ArgumentError, "Association #{name} should not be allowed") do Class.new(ActiveRecord::Base) do has_one name end end end end end rails-4.2.6/activerecord/test/cases/associations/has_one_through_associations_test.rb000066400000000000000000000316521266740050600314170ustar00rootroot00000000000000require "cases/helper" require 'models/club' require 'models/member_type' require 'models/member' require 'models/membership' require 'models/sponsor' require 'models/organization' require 'models/member_detail' require 'models/minivan' require 'models/dashboard' require 'models/speedometer' require 'models/category' require 'models/author' require 'models/essay' require 'models/owner' require 'models/post' require 'models/comment' require 'models/customer' require 'models/carrier' require 'models/shop_account' require 'models/customer_carrier' class HasOneThroughAssociationsTest < ActiveRecord::TestCase fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, :dashboards, :speedometers, :authors, :posts, :comments, :categories, :essays, :owners def setup @member = members(:groucho) end def test_has_one_through_with_has_one assert_equal clubs(:boring_club), @member.club end def test_creating_association_creates_through_record new_member = Member.create(:name => "Chris") new_member.club = Club.create(:name => "LRUG") assert_not_nil new_member.current_membership assert_not_nil new_member.club end def test_creating_association_builds_through_record_for_new new_member = Member.new(:name => "Jane") new_member.club = clubs(:moustache_club) assert new_member.current_membership assert_equal clubs(:moustache_club), new_member.current_membership.club assert_equal clubs(:moustache_club), new_member.club assert new_member.save assert_equal clubs(:moustache_club), new_member.club end def test_creating_association_sets_both_parent_ids_for_new member = Member.new(name: 'Sean Griffin') club = Club.new(name: 'Da Club') member.club = club member.save! assert member.id assert club.id assert_equal member.id, member.current_membership.member_id assert_equal club.id, member.current_membership.club_id end def test_replace_target_record new_club = Club.create(:name => "Marx Bros") @member.club = new_club @member.reload assert_equal new_club, @member.club end def test_replacing_target_record_deletes_old_association assert_no_difference "Membership.count" do new_club = Club.create(:name => "Bananarama") @member.club = new_club @member.reload end end def test_set_record_to_nil_should_delete_association @member.club = nil @member.reload assert_equal nil, @member.current_membership assert_nil @member.club end def test_has_one_through_polymorphic assert_equal clubs(:moustache_club), @member.sponsor_club end def test_has_one_through_eager_loading members = assert_queries(3) do #base table, through table, clubs table Member.all.merge!(:includes => :club, :where => ["name = ?", "Groucho Marx"]).to_a end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].club} end def test_has_one_through_eager_loading_through_polymorphic members = assert_queries(3) do #base table, through table, clubs table Member.all.merge!(:includes => :sponsor_club, :where => ["name = ?", "Groucho Marx"]).to_a end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].sponsor_club} end def test_has_one_through_with_conditions_eager_loading # conditions on the through table assert_equal clubs(:moustache_club), Member.all.merge!(:includes => :favourite_club).find(@member.id).favourite_club memberships(:membership_of_favourite_club).update_columns(favourite: false) assert_equal nil, Member.all.merge!(:includes => :favourite_club).find(@member.id).reload.favourite_club # conditions on the source table assert_equal clubs(:moustache_club), Member.all.merge!(:includes => :hairy_club).find(@member.id).hairy_club clubs(:moustache_club).update_columns(name: "Association of Clean-Shaven Persons") assert_equal nil, Member.all.merge!(:includes => :hairy_club).find(@member.id).reload.hairy_club end def test_has_one_through_polymorphic_with_source_type assert_equal members(:groucho), clubs(:moustache_club).sponsored_member end def test_eager_has_one_through_polymorphic_with_source_type clubs = Club.all.merge!(:includes => :sponsored_member, :where => ["name = ?","Moustache and Eyebrow Fancier Club"]).to_a # Only the eyebrow fanciers club has a sponsored_member assert_not_nil assert_no_queries {clubs[0].sponsored_member} end def test_has_one_through_nonpreload_eagerloading members = assert_queries(1) do Member.all.merge!(:includes => :club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].club} end def test_has_one_through_nonpreload_eager_loading_through_polymorphic members = assert_queries(1) do Member.all.merge!(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].sponsor_club} end def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save! members = assert_queries(1) do Member.all.merge!(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC').to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } assert_equal clubs(:crazy_club), members[0].sponsor_club end def test_uninitialized_has_one_through_should_return_nil_for_unsaved_record assert_nil Member.new.club end def test_assigning_association_correctly_assigns_target new_member = Member.create(:name => "Chris") new_member.club = new_club = Club.create(:name => "LRUG") assert_equal new_club, new_member.association(:club).target end def test_has_one_through_proxy_should_not_respond_to_private_methods assert_raise(NoMethodError) { clubs(:moustache_club).private_method } assert_raise(NoMethodError) { @member.club.private_method } end def test_has_one_through_proxy_should_respond_to_private_methods_via_send clubs(:moustache_club).send(:private_method) @member.club.send(:private_method) end def test_assigning_to_has_one_through_preserves_decorated_join_record @organization = organizations(:nsa) assert_difference 'MemberDetail.count', 1 do @member_detail = MemberDetail.new(:extra_data => 'Extra') @member.member_detail = @member_detail @member.organization = @organization end assert_equal @organization, @member.organization assert @organization.members.include?(@member) assert_equal 'Extra', @member.member_detail.extra_data end def test_reassigning_has_one_through @organization = organizations(:nsa) @new_organization = organizations(:discordians) assert_difference 'MemberDetail.count', 1 do @member_detail = MemberDetail.new(:extra_data => 'Extra') @member.member_detail = @member_detail @member.organization = @organization end assert_equal @organization, @member.organization assert_equal 'Extra', @member.member_detail.extra_data assert @organization.members.include?(@member) assert !@new_organization.members.include?(@member) assert_no_difference 'MemberDetail.count' do @member.organization = @new_organization end assert_equal @new_organization, @member.organization assert_equal 'Extra', @member.member_detail.extra_data assert !@organization.members.include?(@member) assert @new_organization.members.include?(@member) end def test_preloading_has_one_through_on_belongs_to MemberDetail.delete_all assert_not_nil @member.member_type @organization = organizations(:nsa) @member_detail = MemberDetail.new @member.member_detail = @member_detail @member.organization = @organization @member_details = assert_queries(3) do MemberDetail.all.merge!(:includes => :member_type).to_a end @new_detail = @member_details[0] assert @new_detail.send(:association, :member_type).loaded? assert_no_queries { @new_detail.member_type } end def test_save_of_record_with_loaded_has_one_through @club = @member.club assert_not_nil @club.sponsored_member assert_nothing_raised do Club.find(@club.id).save! Club.all.merge!(:includes => :sponsored_member).find(@club.id).save! end @club.sponsor.destroy assert_nothing_raised do Club.find(@club.id).save! Club.all.merge!(:includes => :sponsored_member).find(@club.id).save! end end def test_through_belongs_to_after_destroy @member_detail = MemberDetail.new(:extra_data => 'Extra') @member.member_detail = @member_detail @member.save! assert_not_nil @member_detail.member_type @member_detail.destroy assert_queries(1) do assert_not_nil @member_detail.member_type(true) end @member_detail.member.destroy assert_queries(1) do assert_nil @member_detail.member_type(true) end end def test_value_is_properly_quoted minivan = Minivan.find('m1') assert_nothing_raised do minivan.dashboard end end def test_has_one_through_polymorphic_with_primary_key_option assert_equal categories(:general), authors(:david).essay_category authors = Author.joins(:essay_category).where('categories.id' => categories(:general).id) assert_equal authors(:david), authors.first assert_equal owners(:blackbeard), authors(:david).essay_owner authors = Author.joins(:essay_owner).where("owners.name = 'blackbeard'") assert_equal authors(:david), authors.first end def test_has_one_through_with_primary_key_option assert_equal categories(:general), authors(:david).essay_category_2 authors = Author.joins(:essay_category_2).where('categories.id' => categories(:general).id) assert_equal authors(:david), authors.first end def test_has_one_through_with_default_scope_on_join_model assert_equal posts(:welcome).comments.order('id').first, authors(:david).comment_on_first_post end def test_has_one_through_many_raises_exception assert_raise(ActiveRecord::HasOneThroughCantAssociateThroughCollection) do members(:groucho).club_through_many end end def test_has_one_through_polymorphic_association assert_raise(ActiveRecord::HasOneAssociationPolymorphicThroughError) do @member.premium_club end end def test_has_one_through_belongs_to_should_update_when_the_through_foreign_key_changes minivan = minivans(:cool_first) minivan.dashboard proxy = minivan.send(:association_instance_get, :dashboard) assert !proxy.stale_target? assert_equal dashboards(:cool_first), minivan.dashboard minivan.speedometer_id = speedometers(:second).id assert proxy.stale_target? assert_equal dashboards(:second), minivan.dashboard end def test_has_one_through_belongs_to_setting_belongs_to_foreign_key_after_nil_target_loaded minivan = Minivan.new minivan.dashboard proxy = minivan.send(:association_instance_get, :dashboard) minivan.speedometer_id = speedometers(:second).id assert proxy.stale_target? assert_equal dashboards(:second), minivan.dashboard end def test_assigning_has_one_through_belongs_to_with_new_record_owner minivan = Minivan.new dashboard = dashboards(:cool_first) minivan.dashboard = dashboard assert_equal dashboard, minivan.dashboard assert_equal dashboard, minivan.speedometer.dashboard end def test_has_one_through_with_custom_select_on_join_model_default_scope assert_equal clubs(:boring_club), members(:groucho).selected_club end def test_has_one_through_relationship_cannot_have_a_counter_cache assert_raise(ArgumentError) do Class.new(ActiveRecord::Base) do has_one :thing, through: :other_thing, counter_cache: true end end end def test_has_one_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes customer = Customer.create! carrier = Carrier.create! customer_carrier = CustomerCarrier.create!( customer: customer, carrier: carrier, ) account = ShopAccount.create!(customer_carrier: customer_carrier) CustomerCarrier.current_customer = customer account_carrier = account.carrier assert_equal carrier, account_carrier CustomerCarrier.current_customer = nil other_carrier = Carrier.create! other_customer = Customer.create! other_customer_carrier = CustomerCarrier.create!( customer: other_customer, carrier: other_carrier, ) other_account = ShopAccount.create!(customer_carrier: other_customer_carrier) account_carrier = other_account.carrier assert_equal other_carrier, account_carrier ensure CustomerCarrier.current_customer = nil end end rails-4.2.6/activerecord/test/cases/associations/inner_join_association_test.rb000066400000000000000000000133001266740050600302000ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/comment' require 'models/author' require 'models/essay' require 'models/category' require 'models/categorization' require 'models/person' require 'models/tagging' require 'models/tag' class InnerJoinAssociationTest < ActiveRecord::TestCase fixtures :authors, :essays, :posts, :comments, :categories, :categories_posts, :categorizations, :taggings, :tags def test_construct_finder_sql_applies_aliases_tables_on_association_conditions result = Author.joins(:thinking_posts, :welcome_posts).to_a assert_equal authors(:david), result.first end def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations assert_nothing_raised do sql = Person.joins(:agents => {:agents => :agents}).joins(:agents => {:agents => {:primary_contact => :agents}}).to_sql assert_match(/agents_people_4/i, sql) end end def test_construct_finder_sql_ignores_empty_joins_hash sql = Author.joins({}).to_sql assert_no_match(/JOIN/i, sql) end def test_construct_finder_sql_ignores_empty_joins_array sql = Author.joins([]).to_sql assert_no_match(/JOIN/i, sql) end def test_join_conditions_added_to_join_clause sql = Author.joins(:essays).to_sql assert_match(/writer_type.*?=.*?Author/i, sql) assert_no_match(/WHERE/i, sql) end def test_join_association_conditions_support_string_and_arel_expressions assert_equal 0, Author.joins(:welcome_posts_with_one_comment).count assert_equal 1, Author.joins(:welcome_posts_with_comments).count end def test_join_conditions_allow_nil_associations authors = Author.includes(:essays).where(:essays => {:id => nil}) assert_equal 2, authors.count end def test_find_with_implicit_inner_joins_without_select_does_not_imply_readonly authors = Author.joins(:posts) assert_not authors.empty?, "expected authors to be non-empty" assert authors.none? {|a| a.readonly? }, "expected no authors to be readonly" end def test_find_with_implicit_inner_joins_honors_readonly_with_select authors = Author.joins(:posts).select('authors.*').to_a assert !authors.empty?, "expected authors to be non-empty" assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly" end def test_find_with_implicit_inner_joins_honors_readonly_false authors = Author.joins(:posts).readonly(false).to_a assert !authors.empty?, "expected authors to be non-empty" assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly" end def test_find_with_implicit_inner_joins_does_not_set_associations authors = Author.joins(:posts).select('authors.*').to_a assert !authors.empty?, "expected authors to be non-empty" assert authors.all? { |a| !a.instance_variable_defined?(:@posts) }, "expected no authors to have the @posts association loaded" end def test_count_honors_implicit_inner_joins real_count = Author.all.to_a.sum{|a| a.posts.count } assert_equal real_count, Author.joins(:posts).count, "plain inner join count should match the number of referenced posts records" end def test_calculate_honors_implicit_inner_joins real_count = Author.all.to_a.sum{|a| a.posts.count } assert_equal real_count, Author.joins(:posts).calculate(:count, 'authors.id'), "plain inner join count should match the number of referenced posts records" end def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions real_count = Author.all.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length authors_with_welcoming_post_titles = Author.all.merge!(joins: :posts, where: "posts.title like 'Welcome%'").distinct.calculate(:count, 'authors.id') assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'" end def test_find_with_sti_join scope = Post.joins(:special_comments).where(:id => posts(:sti_comments).id) # The join should match SpecialComment and its subclasses only assert scope.where("comments.type" => "Comment").empty? assert !scope.where("comments.type" => "SpecialComment").empty? assert !scope.where("comments.type" => "SubSpecialComment").empty? end def test_find_with_conditions_on_reflection assert !posts(:welcome).comments.empty? assert Post.joins(:nonexistant_comments).where(:id => posts(:welcome).id).empty? # [sic!] end def test_find_with_conditions_on_through_reflection assert !posts(:welcome).tags.empty? assert Post.joins(:misc_tags).where(:id => posts(:welcome).id).empty? end test "the default scope of the target is applied when joining associations" do author = Author.create! name: "Jon" author.categorizations.create! author.categorizations.create! special: true assert_equal [author], Author.where(id: author).joins(:special_categorizations) end test "the default scope of the target is correctly aliased when joining associations" do author = Author.create! name: "Jon" author.categories.create! name: 'Not Special' author.special_categories.create! name: 'Special' categories = author.categories.includes(:special_categorizations).references(:special_categorizations).to_a assert_equal 2, categories.size end test "the correct records are loaded when including an aliased association" do author = Author.create! name: "Jon" author.categories.create! name: 'Not Special' author.special_categories.create! name: 'Special' categories = author.categories.eager_load(:special_categorizations).order(:name).to_a assert_equal 0, categories.first.special_categorizations.size assert_equal 1, categories.second.special_categorizations.size end end rails-4.2.6/activerecord/test/cases/associations/inverse_associations_test.rb000066400000000000000000001017351266740050600277160ustar00rootroot00000000000000require "cases/helper" require 'models/man' require 'models/face' require 'models/interest' require 'models/zine' require 'models/club' require 'models/sponsor' require 'models/rating' require 'models/comment' require 'models/car' require 'models/bulb' require 'models/mixed_case_monkey' require 'models/admin' require 'models/admin/account' require 'models/admin/user' require 'models/developer' require 'models/company' require 'models/project' class AutomaticInverseFindingTests < ActiveRecord::TestCase fixtures :ratings, :comments, :cars def test_has_one_and_belongs_to_should_find_inverse_automatically_on_multiple_word_name monkey_reflection = MixedCaseMonkey.reflect_on_association(:man) man_reflection = Man.reflect_on_association(:mixed_case_monkey) assert_respond_to monkey_reflection, :has_inverse? assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse" assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection" assert_respond_to man_reflection, :has_inverse? assert man_reflection.has_inverse?, "The man reflection should have an inverse" assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection" end def test_has_many_and_belongs_to_should_find_inverse_automatically_for_model_in_module account_reflection = Admin::Account.reflect_on_association(:users) user_reflection = Admin::User.reflect_on_association(:account) assert_respond_to account_reflection, :has_inverse? assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse" assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection" end def test_has_one_and_belongs_to_should_find_inverse_automatically car_reflection = Car.reflect_on_association(:bulb) bulb_reflection = Bulb.reflect_on_association(:car) assert_respond_to car_reflection, :has_inverse? assert car_reflection.has_inverse?, "The Car reflection should have an inverse" assert_equal bulb_reflection, car_reflection.inverse_of, "The Car reflection's inverse should be the Bulb reflection" assert_respond_to bulb_reflection, :has_inverse? assert bulb_reflection.has_inverse?, "The Bulb reflection should have an inverse" assert_equal car_reflection, bulb_reflection.inverse_of, "The Bulb reflection's inverse should be the Car reflection" end def test_has_many_and_belongs_to_should_find_inverse_automatically comment_reflection = Comment.reflect_on_association(:ratings) rating_reflection = Rating.reflect_on_association(:comment) assert_respond_to comment_reflection, :has_inverse? assert comment_reflection.has_inverse?, "The Comment reflection should have an inverse" assert_equal rating_reflection, comment_reflection.inverse_of, "The Comment reflection's inverse should be the Rating reflection" end def test_has_one_and_belongs_to_automatic_inverse_shares_objects car = Car.first bulb = Bulb.create!(car: car) assert_equal car.bulb, bulb, "The Car's bulb should be the original bulb" car.bulb.color = "Blue" assert_equal car.bulb.color, bulb.color, "Changing the bulb's color on the car association should change the bulb's color" bulb.color = "Red" assert_equal bulb.color, car.bulb.color, "Changing the bulb's color should change the bulb's color on the car association" end def test_has_many_and_belongs_to_automatic_inverse_shares_objects_on_rating comment = Comment.first rating = Rating.create!(comment: comment) assert_equal rating.comment, comment, "The Rating's comment should be the original Comment" rating.comment.body = "Brogramming is the act of programming, like a bro." assert_equal rating.comment.body, comment.body, "Changing the Comment's body on the association should change the original Comment's body" comment.body = "Broseiden is the king of the sea of bros." assert_equal comment.body, rating.comment.body, "Changing the original Comment's body should change the Comment's body on the association" end def test_has_many_and_belongs_to_automatic_inverse_shares_objects_on_comment rating = Rating.create! comment = Comment.first rating.comment = comment assert_equal rating.comment, comment, "The Rating's comment should be the original Comment" rating.comment.body = "Brogramming is the act of programming, like a bro." assert_equal rating.comment.body, comment.body, "Changing the Comment's body on the association should change the original Comment's body" comment.body = "Broseiden is the king of the sea of bros." assert_equal comment.body, rating.comment.body, "Changing the original Comment's body should change the Comment's body on the association" end def test_polymorphic_and_has_many_through_relationships_should_not_have_inverses sponsor_reflection = Sponsor.reflect_on_association(:sponsorable) assert_respond_to sponsor_reflection, :has_inverse? assert !sponsor_reflection.has_inverse?, "A polymorphic association should not find an inverse automatically" club_reflection = Club.reflect_on_association(:members) assert_respond_to club_reflection, :has_inverse? assert !club_reflection.has_inverse?, "A has_many_through association should not find an inverse automatically" end def test_polymorphic_relationships_should_still_not_have_inverses_when_non_polymorphic_relationship_has_the_same_name man_reflection = Man.reflect_on_association(:polymorphic_face_without_inverse) face_reflection = Face.reflect_on_association(:man) assert_respond_to face_reflection, :has_inverse? assert face_reflection.has_inverse?, "For this test, the non-polymorphic association must have an inverse" assert_respond_to man_reflection, :has_inverse? assert !man_reflection.has_inverse?, "The target of a polymorphic association should not find an inverse automatically" end end class InverseAssociationTests < ActiveRecord::TestCase def test_should_allow_for_inverse_of_options_in_associations assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_many') do Class.new(ActiveRecord::Base).has_many(:wheels, :inverse_of => :car) end assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_one') do Class.new(ActiveRecord::Base).has_one(:engine, :inverse_of => :car) end assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on belongs_to') do Class.new(ActiveRecord::Base).belongs_to(:car, :inverse_of => :driver) end end def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse has_one_with_inverse_ref = Man.reflect_on_association(:face) assert_respond_to has_one_with_inverse_ref, :has_inverse? assert has_one_with_inverse_ref.has_inverse? has_many_with_inverse_ref = Man.reflect_on_association(:interests) assert_respond_to has_many_with_inverse_ref, :has_inverse? assert has_many_with_inverse_ref.has_inverse? belongs_to_with_inverse_ref = Face.reflect_on_association(:man) assert_respond_to belongs_to_with_inverse_ref, :has_inverse? assert belongs_to_with_inverse_ref.has_inverse? has_one_without_inverse_ref = Club.reflect_on_association(:sponsor) assert_respond_to has_one_without_inverse_ref, :has_inverse? assert !has_one_without_inverse_ref.has_inverse? has_many_without_inverse_ref = Club.reflect_on_association(:memberships) assert_respond_to has_many_without_inverse_ref, :has_inverse? assert !has_many_without_inverse_ref.has_inverse? belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club) assert_respond_to belongs_to_without_inverse_ref, :has_inverse? assert !belongs_to_without_inverse_ref.has_inverse? end def test_should_be_able_to_ask_a_reflection_what_it_is_the_inverse_of has_one_ref = Man.reflect_on_association(:face) assert_respond_to has_one_ref, :inverse_of has_many_ref = Man.reflect_on_association(:interests) assert_respond_to has_many_ref, :inverse_of belongs_to_ref = Face.reflect_on_association(:man) assert_respond_to belongs_to_ref, :inverse_of end def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of has_one_ref = Man.reflect_on_association(:face) assert_equal Face.reflect_on_association(:man), has_one_ref.inverse_of has_many_ref = Man.reflect_on_association(:interests) assert_equal Interest.reflect_on_association(:man), has_many_ref.inverse_of belongs_to_ref = Face.reflect_on_association(:man) assert_equal Man.reflect_on_association(:face), belongs_to_ref.inverse_of end def test_associations_with_no_inverse_of_should_return_nil has_one_ref = Club.reflect_on_association(:sponsor) assert_nil has_one_ref.inverse_of has_many_ref = Club.reflect_on_association(:memberships) assert_nil has_many_ref.inverse_of belongs_to_ref = Sponsor.reflect_on_association(:sponsor_club) assert_nil belongs_to_ref.inverse_of end def test_this_inverse_stuff firm = Firm.create!(name: 'Adequate Holdings') Project.create!(name: 'Project 1', firm: firm) Developer.create!(name: 'Gorbypuff', firm: firm) new_project = Project.last assert Project.reflect_on_association(:lead_developer).inverse_of.present?, "Expected inverse of to be present" assert new_project.lead_developer.present?, "Expected lead developer to be present on the project" end end class InverseHasOneTests < ActiveRecord::TestCase fixtures :men, :faces def test_parent_instance_should_be_shared_with_child_on_find m = men(:gordon) f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" end def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find m = Man.all.merge!(:where => {:name => 'Gordon'}, :includes => :face).first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" m = Man.all.merge!(:where => {:name => 'Gordon'}, :includes => :face, :order => 'faces.id').first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" end def test_parent_instance_should_be_shared_with_newly_built_child m = Man.first f = m.build_face(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to just-built-child-owned instance" end def test_parent_instance_should_be_shared_with_newly_created_child m = Man.first f = m.create_face(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method m = Man.first f = m.create_face!(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_with_replaced_via_accessor_child m = Man.first f = Face.new(:description => 'haunted') m.face = f assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance" end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.dirty_face } end end class InverseHasManyTests < ActiveRecord::TestCase fixtures :men, :interests def test_parent_instance_should_be_shared_with_every_child_on_find m = men(:gordon) is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" i.man.name = 'Mungo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end end def test_parent_instance_should_be_shared_with_eager_loaded_children m = Man.all.merge!(:where => {:name => 'Gordon'}, :includes => :interests).first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" i.man.name = 'Mungo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end m = Man.all.merge!(:where => {:name => 'Gordon'}, :includes => :interests, :order => 'interests.id').first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" i.man.name = 'Mungo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end end def test_parent_instance_should_be_shared_with_newly_block_style_built_child m = Man.first i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'} assert_not_nil i.topic, "Child attributes supplied to build via blocks should be populated" assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" i.man.name = 'Mungo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance" end def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child m = Man.first i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment') assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" i.man.name = 'Mungo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_with_newly_block_style_created_child m = Man.first i = m.interests.create {|ii| ii.topic = 'Industrial Revolution Re-enactment'} assert_not_nil i.topic, "Child attributes supplied to create via blocks should be populated" assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" i.man.name = 'Mungo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_within_create_block_of_new_child man = Man.first interest = man.interests.create do |i| assert i.man.equal?(man), "Man of child should be the same instance as a parent" end assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent" end def test_parent_instance_should_be_shared_within_build_block_of_new_child man = Man.first interest = man.interests.build do |i| assert i.man.equal?(man), "Man of child should be the same instance as a parent" end assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent" end def test_parent_instance_should_be_shared_with_poked_in_child m = men(:gordon) i = Interest.create(:topic => 'Industrial Revolution Re-enactment') m.interests << i assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" i.man.name = 'Mungo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_with_replaced_via_accessor_children m = Man.first i = Interest.new(:topic => 'Industrial Revolution Re-enactment') m.interests = [i] assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" i.man.name = 'Mungo' assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance" end def test_parent_instance_should_be_shared_with_first_and_last_child man = Man.first assert man.interests.first.man.equal? man assert man.interests.last.man.equal? man end def test_parent_instance_should_be_shared_with_first_n_and_last_n_children man = Man.first interests = man.interests.first(2) assert interests[0].man.equal? man assert interests[1].man.equal? man interests = man.interests.last(2) assert interests[0].man.equal? man assert interests[1].man.equal? man end def test_parent_instance_should_find_child_instance_using_child_instance_id man = Man.create! interest = Interest.create! man.interests = [interest] assert interest.equal?(man.interests.first), "The inverse association should use the interest already created and held in memory" assert interest.equal?(man.interests.find(interest.id)), "The inverse association should use the interest already created and held in memory" assert man.equal?(man.interests.first.man), "Two inversion should lead back to the same object that was originally held" assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held" end def test_parent_instance_should_find_child_instance_using_child_instance_id_when_created man = Man.create! interest = Interest.create!(man: man) assert man.equal?(man.interests.first.man), "Two inverses should lead back to the same object that was originally held" assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held" assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed" man.name = "Ben Bitdiddle" assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the parent name is changed" man.interests.find(interest.id).man.name = "Alyssa P. Hacker" assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the child name is changed" end def test_find_on_child_instance_with_id_should_not_load_all_child_records man = Man.create! interest = Interest.create!(man: man) man.interests.find(interest.id) assert_not man.interests.loaded? end def test_raise_record_not_found_error_when_invalid_ids_are_passed # delete all interest records to ensure that hard coded invalid_id(s) # are indeed invalid. Interest.delete_all man = Man.create! invalid_id = 245324523 assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_id) } invalid_ids = [8432342, 2390102913, 2453245234523452] assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_ids) } end def test_raise_record_not_found_error_when_no_ids_are_passed man = Man.create! assert_raise(ActiveRecord::RecordNotFound) { man.interests.find() } end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests } end def test_child_instance_should_point_to_parent_without_saving man = Man.new i = Interest.create(:topic => 'Industrial Revolution Re-enactment') man.interests << i assert_not_nil i.man i.man.name = "Charles" assert_equal i.man.name, man.name assert !man.persisted? end end class InverseBelongsToTests < ActiveRecord::TestCase fixtures :men, :faces, :interests def test_child_instance_should_be_shared_with_parent_on_find f = faces(:trusting) m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" m.face.description = 'pleasing' assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" end def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find f = Face.all.merge!(:includes => :man, :where => {:description => 'trusting'}).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" m.face.description = 'pleasing' assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" f = Face.all.merge!(:includes => :man, :order => 'men.id', :where => {:description => 'trusting'}).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" m.face.description = 'pleasing' assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" end def test_child_instance_should_be_shared_with_newly_built_parent f = faces(:trusting) m = f.build_man(:name => 'Charles') assert_not_nil m.face assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" m.face.description = 'pleasing' assert_equal f.description, m.face.description, "Description of face should be the same after changes to just-built-parent-owned instance" end def test_child_instance_should_be_shared_with_newly_created_parent f = faces(:trusting) m = f.create_man(:name => 'Charles') assert_not_nil m.face assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" m.face.description = 'pleasing' assert_equal f.description, m.face.description, "Description of face should be the same after changes to newly-created-parent-owned instance" end def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many i = interests(:trainspotting) m = i.man assert_not_nil m.interests iz = m.interests.detect { |_iz| _iz.id == i.id} assert_not_nil iz assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child" i.topic = 'Eating cheese with a spoon' assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child" iz.topic = 'Cow tipping' assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance" end def test_child_instance_should_be_shared_with_replaced_via_accessor_parent f = Face.first m = Man.new(:name => 'Charles') f.man = m assert_not_nil m.face assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" m.face.description = 'pleasing' assert_equal f.description, m.face.description, "Description of face should be the same after changes to replaced-parent-owned instance" end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_man } end end class InversePolymorphicBelongsToTests < ActiveRecord::TestCase fixtures :men, :faces, :interests def test_child_instance_should_be_shared_with_parent_on_find f = Face.all.merge!(:where => {:description => 'confused'}).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance" m.polymorphic_face.description = 'pleasing' assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" end def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find f = Face.all.merge!(:where => {:description => 'confused'}, :includes => :man).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance" m.polymorphic_face.description = 'pleasing' assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" f = Face.all.merge!(:where => {:description => 'confused'}, :includes => :man, :order => 'men.id').first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance" m.polymorphic_face.description = 'pleasing' assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" end def test_child_instance_should_be_shared_with_replaced_via_accessor_parent face = faces(:confused) new_man = Man.new assert_not_nil face.polymorphic_man face.polymorphic_man = new_man assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance" face.description = 'Bongo' assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance" new_man.polymorphic_face.description = 'Mungo' assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance" end def test_child_instance_should_be_shared_with_replaced_via_method_parent face = faces(:confused) new_man = Man.new assert_not_nil face.polymorphic_man face.polymorphic_man = new_man assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance" face.description = 'Bongo' assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance" new_man.polymorphic_face.description = 'Mungo' assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance" end def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed new_man = Man.new face = Face.new new_man.face = face old_inversed_man = face.man new_man.save! new_inversed_man = face.man assert_equal old_inversed_man.object_id, new_inversed_man.object_id end def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many i = interests(:llama_wrangling) m = i.polymorphic_man assert_not_nil m.polymorphic_interests iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id} assert_not_nil iz assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child" i.topic = 'Eating cheese with a spoon' assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child" iz.topic = 'Cow tipping' assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance" end def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error # Ideally this would, if only for symmetry's sake with other association types assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man } end def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error # fails because no class has the correct inverse_of for horrible_polymorphic_man assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man = Man.first } end def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error # passes because Man does have the correct inverse_of assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Man.first } # fails because Interest does have the correct inverse_of assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first } end end # NOTE - these tests might not be meaningful, ripped as they were from the parental_control plugin # which would guess the inverse rather than look for an explicit configuration option. class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase fixtures :men, :interests, :zines def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do i = Interest.first i.zine i.man end end def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do i = Interest.first i.build_zine(:title => 'Get Some in Winter! 2008') i.build_man(:name => 'Gordon') i.save! end end end rails-4.2.6/activerecord/test/cases/associations/join_model_test.rb000066400000000000000000000666241266740050600256120ustar00rootroot00000000000000require "cases/helper" require 'models/tag' require 'models/tagging' require 'models/post' require 'models/rating' require 'models/item' require 'models/comment' require 'models/author' require 'models/category' require 'models/categorization' require 'models/vertex' require 'models/edge' require 'models/book' require 'models/citation' require 'models/aircraft' require 'models/engine' require 'models/car' class AssociationsJoinModelTest < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, # Reload edges table from fixtures as otherwise repeated test was failing :edges def test_has_many assert authors(:david).categories.include?(categories(:general)) end def test_has_many_inherited assert authors(:mary).categories.include?(categories(:sti_test)) end def test_inherited_has_many assert categories(:sti_test).authors.include?(authors(:mary)) end def test_has_many_uniq_through_join_model assert_equal 2, authors(:mary).categorized_posts.size assert_equal 1, authors(:mary).unique_categorized_posts.size end def test_has_many_uniq_through_count author = authors(:mary) assert !authors(:mary).unique_categorized_posts.loaded? assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count } assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) } assert_queries(1) { assert_equal 0, author.unique_categorized_posts.where(title: nil).count(:title) } assert !authors(:mary).unique_categorized_posts.loaded? end def test_has_many_uniq_through_find assert_equal 1, authors(:mary).unique_categorized_posts.to_a.size end def test_polymorphic_has_many_going_through_join_model assert_equal tags(:general), tag = posts(:welcome).tags.first assert_no_queries do tag.tagging end end def test_count_polymorphic_has_many assert_equal 1, posts(:welcome).taggings.count assert_equal 1, posts(:welcome).tags.count end def test_polymorphic_has_many_going_through_join_model_with_find assert_equal tags(:general), tag = posts(:welcome).tags.first assert_no_queries do tag.tagging end end def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection assert_equal tags(:general), tag = posts(:welcome).funky_tags.first assert_no_queries do tag.tagging end end def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find assert_equal tags(:general), tag = posts(:welcome).funky_tags.first assert_no_queries do tag.tagging end end def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first assert_nothing_raised(NoMethodError) { tag.author_id } end def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key assert_equal tags(:misc), taggings(:welcome_general).super_tag assert_equal tags(:misc), posts(:welcome).super_tags.first end def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body' assert_instance_of SubStiPost, post tagging = tags(:misc).taggings.create(:taggable => post) assert_equal "SubStiPost", tagging.taggable_type end def test_polymorphic_has_many_going_through_join_model_with_inheritance assert_equal tags(:general), posts(:thinking).tags.first end def test_polymorphic_has_many_going_through_join_model_with_inheritance_with_custom_class_name assert_equal tags(:general), posts(:thinking).funky_tags.first end def test_polymorphic_has_many_create_model_with_inheritance post = posts(:thinking) assert_instance_of SpecialPost, post tagging = tags(:misc).taggings.create(:taggable => post) assert_equal "Post", tagging.taggable_type end def test_polymorphic_has_one_create_model_with_inheritance tagging = tags(:misc).create_tagging(:taggable => posts(:thinking)) assert_equal "Post", tagging.taggable_type end def test_set_polymorphic_has_many tagging = tags(:misc).taggings.create posts(:thinking).taggings << tagging assert_equal "Post", tagging.taggable_type end def test_set_polymorphic_has_one tagging = tags(:misc).taggings.create posts(:thinking).tagging = tagging assert_equal "Post", tagging.taggable_type assert_equal posts(:thinking).id, tagging.taggable_id assert_equal posts(:thinking), tagging.taggable end def test_set_polymorphic_has_one_on_new_record tagging = tags(:misc).taggings.create post = Post.new :title => "foo", :body => "bar" post.tagging = tagging post.save! assert_equal "Post", tagging.taggable_type assert_equal post.id, tagging.taggable_id assert_equal post, tagging.taggable end def test_create_polymorphic_has_many_with_scope old_count = posts(:welcome).taggings.count tagging = posts(:welcome).taggings.create(:tag => tags(:misc)) assert_equal "Post", tagging.taggable_type assert_equal old_count+1, posts(:welcome).taggings.count end def test_create_bang_polymorphic_with_has_many_scope old_count = posts(:welcome).taggings.count tagging = posts(:welcome).taggings.create!(:tag => tags(:misc)) assert_equal "Post", tagging.taggable_type assert_equal old_count+1, posts(:welcome).taggings.count end def test_create_polymorphic_has_one_with_scope old_count = Tagging.count tagging = posts(:welcome).create_tagging(:tag => tags(:misc)) assert_equal "Post", tagging.taggable_type assert_equal old_count+1, Tagging.count end def test_delete_polymorphic_has_many_with_delete_all assert_equal 1, posts(:welcome).taggings.count posts(:welcome).taggings.first.update_columns taggable_type: 'PostWithHasManyDeleteAll' post = find_post_with_dependency(1, :has_many, :taggings, :delete_all) old_count = Tagging.count post.destroy assert_equal old_count-1, Tagging.count assert_equal 0, posts(:welcome).taggings.count end def test_delete_polymorphic_has_many_with_destroy assert_equal 1, posts(:welcome).taggings.count posts(:welcome).taggings.first.update_columns taggable_type: 'PostWithHasManyDestroy' post = find_post_with_dependency(1, :has_many, :taggings, :destroy) old_count = Tagging.count post.destroy assert_equal old_count-1, Tagging.count assert_equal 0, posts(:welcome).taggings.count end def test_delete_polymorphic_has_many_with_nullify assert_equal 1, posts(:welcome).taggings.count posts(:welcome).taggings.first.update_columns taggable_type: 'PostWithHasManyNullify' post = find_post_with_dependency(1, :has_many, :taggings, :nullify) old_count = Tagging.count post.destroy assert_equal old_count, Tagging.count assert_equal 0, posts(:welcome).taggings.count end def test_delete_polymorphic_has_one_with_destroy assert posts(:welcome).tagging posts(:welcome).tagging.update_columns taggable_type: 'PostWithHasOneDestroy' post = find_post_with_dependency(1, :has_one, :tagging, :destroy) old_count = Tagging.count post.destroy assert_equal old_count-1, Tagging.count assert_nil posts(:welcome).tagging(true) end def test_delete_polymorphic_has_one_with_nullify assert posts(:welcome).tagging posts(:welcome).tagging.update_columns taggable_type: 'PostWithHasOneNullify' post = find_post_with_dependency(1, :has_one, :tagging, :nullify) old_count = Tagging.count post.destroy assert_equal old_count, Tagging.count assert_nil posts(:welcome).tagging(true) end def test_has_many_with_piggyback assert_equal "2", categories(:sti_test).authors_with_select.first.post_id.to_s end def test_create_through_has_many_with_piggyback category = categories(:sti_test) ernie = category.authors_with_select.create(:name => 'Ernie') assert_nothing_raised do assert_equal ernie, category.authors_with_select.detect {|a| a.name == 'Ernie'} end end def test_include_has_many_through posts = Post.all.merge!(:order => 'posts.id').to_a posts_with_authors = Post.all.merge!(:includes => :authors, :order => 'posts.id').to_a assert_equal posts.length, posts_with_authors.length posts.length.times do |i| assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length } end end def test_include_polymorphic_has_one post = Post.includes(:tagging).find posts(:welcome).id tagging = taggings(:welcome_general) assert_no_queries do assert_equal tagging, post.tagging end end def test_include_polymorphic_has_one_defined_in_abstract_parent item = Item.includes(:tagging).find items(:dvd).id tagging = taggings(:godfather) assert_no_queries do assert_equal tagging, item.tagging end end def test_include_polymorphic_has_many_through posts = Post.all.merge!(:order => 'posts.id').to_a posts_with_tags = Post.all.merge!(:includes => :tags, :order => 'posts.id').to_a assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } end end def test_include_polymorphic_has_many posts = Post.all.merge!(:order => 'posts.id').to_a posts_with_taggings = Post.all.merge!(:includes => :taggings, :order => 'posts.id').to_a assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } end end def test_has_many_find_all assert_equal [categories(:general)], authors(:david).categories.to_a end def test_has_many_find_first assert_equal categories(:general), authors(:david).categories.first end def test_has_many_with_hash_conditions assert_equal categories(:general), authors(:david).categories_like_general.first end def test_has_many_find_conditions assert_equal categories(:general), authors(:david).categories.where("categories.name = 'General'").first assert_nil authors(:david).categories.where("categories.name = 'Technology'").first end def test_has_many_array_methods_called_by_method_missing assert authors(:david).categories.any? { |category| category.name == 'General' } assert_nothing_raised { authors(:david).categories.sort } end def test_has_many_going_through_join_model_with_custom_foreign_key assert_equal [authors(:bob)], posts(:thinking).authors assert_equal [authors(:mary)], posts(:authorless).authors end def test_has_many_going_through_join_model_with_custom_primary_key assert_equal [authors(:david)], posts(:thinking).authors_using_author_id end def test_has_many_going_through_polymorphic_join_model_with_custom_primary_key assert_equal [tags(:general)], posts(:eager_other).tags_using_author_id end def test_has_many_through_with_custom_primary_key_on_belongs_to_source assert_equal [authors(:david), authors(:david)], posts(:thinking).author_using_custom_pk end def test_has_many_through_with_custom_primary_key_on_has_many_source assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order('authors.id') end def test_belongs_to_polymorphic_with_counter_cache assert_equal 1, posts(:welcome)[:tags_count] tagging = posts(:welcome).taggings.create(:tag => tags(:general)) assert_equal 2, posts(:welcome, :reload)[:tags_count] tagging.destroy assert_equal 1, posts(:welcome, :reload)[:tags_count] end def test_unavailable_through_reflection assert_raise(ActiveRecord::HasManyThroughAssociationNotFoundError) { authors(:david).nothings } end def test_has_many_through_join_model_with_conditions assert_equal [], posts(:welcome).invalid_taggings assert_equal [], posts(:welcome).invalid_tags end def test_has_many_polymorphic assert_raise ActiveRecord::HasManyThroughAssociationPolymorphicSourceError do tags(:general).taggables end assert_raise ActiveRecord::HasManyThroughAssociationPolymorphicThroughError do taggings(:welcome_general).things end assert_raise ActiveRecord::EagerLoadPolymorphicError do tags(:general).taggings.includes(:taggable).where('bogus_table.column = 1').references(:bogus_table).to_a end end def test_has_many_polymorphic_with_source_type # added sort by ID as otherwise Oracle select sometimes returned rows in different order assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id) end def test_eager_has_many_polymorphic_with_source_type tag_with_include = Tag.all.merge!(:includes => :tagged_posts).find(tags(:general).id) desired = posts(:welcome, :thinking) assert_no_queries do # added sort by ID as otherwise test using JRuby was failing as array elements were in different order assert_equal desired.sort_by(&:id), tag_with_include.tagged_posts.sort_by(&:id) end assert_equal 5, tag_with_include.taggings.length end def test_has_many_through_has_many_find_all assert_equal comments(:greetings), authors(:david).comments.order('comments.id').to_a.first end def test_has_many_through_has_many_find_all_with_custom_class assert_equal comments(:greetings), authors(:david).funky_comments.order('comments.id').to_a.first end def test_has_many_through_has_many_find_first assert_equal comments(:greetings), authors(:david).comments.order('comments.id').first end def test_has_many_through_has_many_find_conditions options = { :where => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' } assert_equal comments(:does_it_hurt), authors(:david).comments.merge(options).first end def test_has_many_through_has_many_find_by_id assert_equal comments(:more_greetings), authors(:david).comments.find(2) end def test_has_many_through_polymorphic_has_one assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).taggings_2 end def test_has_many_through_polymorphic_has_many assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by { |t| t.id } end def test_include_has_many_through_polymorphic_has_many author = Author.includes(:taggings).find authors(:david).id expected_taggings = taggings(:welcome_general, :thinking_general) assert_no_queries do assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id } end end def test_eager_load_has_many_through_has_many author = Author.all.merge!(:where => ['name = ?', 'David'], :includes => :comments, :order => 'comments.id').first SpecialComment.new; VerySpecialComment.new assert_no_queries do assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id) end end def test_eager_load_has_many_through_has_many_with_conditions post = Post.all.merge!(:includes => :invalid_tags).first assert_no_queries do post.invalid_tags end end def test_eager_belongs_to_and_has_one_not_singularized assert_nothing_raised do Author.all.merge!(:includes => :author_address).first AuthorAddress.all.merge!(:includes => :author).first end end def test_self_referential_has_many_through assert_equal [authors(:mary)], authors(:david).favorite_authors assert_equal [], authors(:mary).favorite_authors end def test_add_to_self_referential_has_many_through new_author = Author.create(:name => "Bob") authors(:david).author_favorites.create :favorite_author => new_author assert_equal new_author, authors(:david).reload.favorite_authors.first end def test_has_many_through_uses_conditions_specified_on_the_has_many_association author = Author.first assert author.comments.present? assert author.nonexistant_comments.blank? end def test_has_many_through_uses_correct_attributes assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"] end def test_associating_unsaved_records_with_has_many_through saved_post = posts(:thinking) new_tag = Tag.new(:name => "new") saved_post.tags << new_tag assert new_tag.persisted? #consistent with habtm! assert saved_post.persisted? assert saved_post.tags.include?(new_tag) assert new_tag.persisted? assert saved_post.reload.tags(true).include?(new_tag) new_post = Post.new(:title => "Association replacement works!", :body => "You best believe it.") saved_tag = tags(:general) new_post.tags << saved_tag assert !new_post.persisted? assert saved_tag.persisted? assert new_post.tags.include?(saved_tag) new_post.save! assert new_post.persisted? assert new_post.reload.tags(true).include?(saved_tag) assert !posts(:thinking).tags.build.persisted? assert !posts(:thinking).tags.new.persisted? end def test_create_associate_when_adding_to_has_many_through count = posts(:thinking).tags.count push = Tag.create!(:name => 'pushme') post_thinking = posts(:thinking) assert_nothing_raised { post_thinking.tags << push } assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, message = "Expected a Tag in tags collection, got #{wrong.class}.") assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, message = "Expected a Tagging in taggings collection, got #{wrong.class}.") assert_equal(count + 1, post_thinking.reload.tags.size) assert_equal(count + 1, post_thinking.tags(true).size) assert_kind_of Tag, post_thinking.tags.create!(:name => 'foo') assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, message = "Expected a Tag in tags collection, got #{wrong.class}.") assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, message = "Expected a Tagging in taggings collection, got #{wrong.class}.") assert_equal(count + 2, post_thinking.reload.tags.size) assert_equal(count + 2, post_thinking.tags(true).size) assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) } assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, message = "Expected a Tag in tags collection, got #{wrong.class}.") assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, message = "Expected a Tagging in taggings collection, got #{wrong.class}.") assert_equal(count + 4, post_thinking.reload.tags.size) assert_equal(count + 4, post_thinking.tags(true).size) # Raises if the wrong reflection name is used to set the Edge belongs_to assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) } end def test_add_to_join_table_with_no_id assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) } end def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded author = authors(:david) assert_equal 10, author.comments.size assert !author.comments.loaded? end def test_has_many_through_collection_size_uses_counter_cache_if_it_exists c = categories(:general) c.categorizations_count = 100 assert_equal 100, c.categorizations.size assert !c.categorizations.loaded? end def test_adding_junk_to_has_many_through_should_raise_type_mismatch assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags << "Uhh what now?" } end def test_adding_to_has_many_through_should_return_self tags = posts(:thinking).tags assert_equal tags, posts(:thinking).tags.push(tags(:general)) end def test_delete_associate_when_deleting_from_has_many_through_with_nonstandard_id count = books(:awdr).references.count references_before = books(:awdr).references book = Book.create!(:name => 'Getting Real') book_awdr = books(:awdr) book_awdr.references << book assert_equal(count + 1, book_awdr.references(true).size) assert_nothing_raised { book_awdr.references.delete(book) } assert_equal(count, book_awdr.references.size) assert_equal(count, book_awdr.references(true).size) assert_equal(references_before.sort, book_awdr.references.sort) end def test_delete_associate_when_deleting_from_has_many_through count = posts(:thinking).tags.count tags_before = posts(:thinking).tags.sort tag = Tag.create!(:name => 'doomed') post_thinking = posts(:thinking) post_thinking.tags << tag assert_equal(count + 1, post_thinking.taggings(true).size) assert_equal(count + 1, post_thinking.reload.tags(true).size) assert_not_equal(tags_before, post_thinking.tags.sort) assert_nothing_raised { post_thinking.tags.delete(tag) } assert_equal(count, post_thinking.tags.size) assert_equal(count, post_thinking.tags(true).size) assert_equal(count, post_thinking.taggings(true).size) assert_equal(tags_before, post_thinking.tags.sort) end def test_delete_associate_when_deleting_from_has_many_through_with_multiple_tags count = posts(:thinking).tags.count tags_before = posts(:thinking).tags.sort doomed = Tag.create!(:name => 'doomed') doomed2 = Tag.create!(:name => 'doomed2') quaked = Tag.create!(:name => 'quaked') post_thinking = posts(:thinking) post_thinking.tags << doomed << doomed2 assert_equal(count + 2, post_thinking.reload.tags(true).size) assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) } assert_equal(count, post_thinking.tags.size) assert_equal(count, post_thinking.tags(true).size) assert_equal(tags_before, post_thinking.tags.sort) end def test_deleting_junk_from_has_many_through_should_raise_type_mismatch assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete(Object.new) } end def test_deleting_by_fixnum_id_from_has_many_through post = posts(:thinking) assert_difference 'post.tags.count', -1 do assert_equal 1, post.tags.delete(1).size end assert_equal 0, post.tags.size end def test_deleting_by_string_id_from_has_many_through post = posts(:thinking) assert_difference 'post.tags.count', -1 do assert_equal 1, post.tags.delete('1').size end assert_equal 0, post.tags.size end def test_has_many_through_sum_uses_calculations assert_nothing_raised { authors(:david).comments.sum(:post_id) } end def test_calculations_on_has_many_through_should_disambiguate_fields assert_nothing_raised { authors(:david).categories.maximum(:id) } end def test_calculations_on_has_many_through_should_not_disambiguate_fields_unless_necessary assert_nothing_raised { authors(:david).categories.maximum("categories.id") } end def test_has_many_through_has_many_with_sti assert_equal [comments(:does_it_hurt)], authors(:david).special_post_comments end def test_uniq_has_many_through_should_retain_order comment_ids = authors(:david).comments.map(&:id) assert_equal comment_ids.sort, authors(:david).ordered_uniq_comments.map(&:id) assert_equal comment_ids.sort.reverse, authors(:david).ordered_uniq_comments_desc.map(&:id) end def test_polymorphic_has_many expected = taggings(:welcome_general) p = Post.all.merge!(:includes => :taggings).find(posts(:welcome).id) assert_no_queries {assert p.taggings.include?(expected)} assert posts(:welcome).taggings.include?(taggings(:welcome_general)) end def test_polymorphic_has_one expected = posts(:welcome) tagging = Tagging.all.merge!(:includes => :taggable).find(taggings(:welcome_general).id) assert_no_queries { assert_equal expected, tagging.taggable} end def test_polymorphic_belongs_to p = Post.all.merge!(:includes => {:taggings => :taggable}).find(posts(:welcome).id) assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable} end def test_preload_polymorphic_has_many_through posts = Post.all.merge!(:order => 'posts.id').to_a posts_with_tags = Post.all.merge!(:includes => :tags, :order => 'posts.id').to_a assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } end end def test_preload_polymorph_many_types taggings = Tagging.all.merge!(:includes => :taggable, :where => ['taggable_type != ?', 'FakeModel']).to_a assert_no_queries do taggings.first.taggable.id taggings[1].taggable.id end taggables = taggings.map(&:taggable) assert taggables.include?(items(:dvd)) assert taggables.include?(posts(:welcome)) end def test_preload_nil_polymorphic_belongs_to assert_nothing_raised do Tagging.all.merge!(:includes => :taggable, :where => ['taggable_type IS NULL']).to_a end end def test_preload_polymorphic_has_many posts = Post.all.merge!(:order => 'posts.id').to_a posts_with_taggings = Post.all.merge!(:includes => :taggings, :order => 'posts.id').to_a assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } end end def test_belongs_to_shared_parent comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 1').to_a assert_no_queries do assert_equal comments.first.post, comments[1].post end end def test_has_many_through_include_uses_array_include_after_loaded david = authors(:david) david.categories.load_target category = david.categories.first assert_no_queries do assert david.categories.loaded? assert david.categories.include?(category) end end def test_has_many_through_include_checks_if_record_exists_if_target_not_loaded david = authors(:david) category = david.categories.first david.reload assert ! david.categories.loaded? assert_queries(1) do assert david.categories.include?(category) end assert ! david.categories.loaded? end def test_has_many_through_include_returns_false_for_non_matching_record_to_verify_scoping david = authors(:david) category = Category.create!(:name => 'Not Associated') assert ! david.categories.loaded? assert ! david.categories.include?(category) end def test_has_many_through_goes_through_all_sti_classes sub_sti_post = SubStiPost.create!(:title => 'test', :body => 'test', :author_id => 1) new_comment = sub_sti_post.comments.create(:body => 'test') assert_equal [9, 10, new_comment.id], authors(:david).sti_post_comments.map(&:id).sort end def test_has_many_with_pluralize_table_names_false aircraft = Aircraft.create!(:name => "Airbus 380") engine = Engine.create!(:car_id => aircraft.id) assert_equal aircraft.engines, [engine] end private # create dynamic Post models to allow different dependency options def find_post_with_dependency(post_id, association, association_name, dependency) class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}" Post.find(post_id).update_columns type: class_name klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) klass.table_name = 'posts' klass.send(association, association_name, :as => :taggable, :dependent => dependency) klass.find(post_id) end end rails-4.2.6/activerecord/test/cases/associations/nested_through_associations_test.rb000066400000000000000000000537221266740050600312670ustar00rootroot00000000000000require "cases/helper" require 'models/author' require 'models/post' require 'models/person' require 'models/reference' require 'models/job' require 'models/reader' require 'models/comment' require 'models/tag' require 'models/tagging' require 'models/subscriber' require 'models/book' require 'models/subscription' require 'models/rating' require 'models/member' require 'models/member_detail' require 'models/member_type' require 'models/sponsor' require 'models/club' require 'models/organization' require 'models/category' require 'models/categorization' require 'models/membership' require 'models/essay' class NestedThroughAssociationsTest < ActiveRecord::TestCase fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings, :people, :readers, :references, :jobs, :ratings, :comments, :members, :member_details, :member_types, :sponsors, :clubs, :organizations, :categories, :categories_posts, :categorizations, :memberships, :essays # Through associations can either use the has_many or has_one macros. # # has_many # - Source reflection can be has_many, has_one, belongs_to or has_and_belongs_to_many # - Through reflection can be has_many, has_one, belongs_to or has_and_belongs_to_many # # has_one # - Source reflection can be has_one or belongs_to # - Through reflection can be has_one or belongs_to # # Additionally, the source reflection and/or through reflection may be subject to # polymorphism and/or STI. # # When testing these, we need to make sure it works via loading the association directly, or # joining the association, or including the association. We also need to ensure that associations # are readonly where relevant. # has_many through # Source: has_many through # Through: has_many def test_has_many_through_has_many_with_has_many_through_source_reflection general = tags(:general) assert_equal [general, general], authors(:david).tags end def test_has_many_through_has_many_with_has_many_through_source_reflection_preload authors = assert_queries(5) { Author.includes(:tags).to_a } general = tags(:general) assert_no_queries do assert_equal [general, general], authors.first.tags end end def test_has_many_through_has_many_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( Author.where('tags.id' => tags(:general).id), [authors(:david)], :tags ) # This ensures that the polymorphism of taggings is being observed correctly authors = Author.joins(:tags).where('taggings.taggable_type' => 'FakeModel') assert authors.empty? end # has_many through # Source: has_many # Through: has_many through def test_has_many_through_has_many_through_with_has_many_source_reflection luke, david = subscribers(:first), subscribers(:second) assert_equal [luke, david, david], authors(:david).subscribers.order('subscribers.nick') end def test_has_many_through_has_many_through_with_has_many_source_reflection_preload luke, david = subscribers(:first), subscribers(:second) authors = assert_queries(4) { Author.includes(:subscribers).to_a } assert_no_queries do assert_equal [luke, david, david], authors.first.subscribers.sort_by(&:nick) end end def test_has_many_through_has_many_through_with_has_many_source_reflection_preload_via_joins # All authors with subscribers where one of the subscribers' nick is 'alterself' assert_includes_and_joins_equal( Author.where('subscribers.nick' => 'alterself'), [authors(:david)], :subscribers ) end # has_many through # Source: has_one through # Through: has_one def test_has_many_through_has_one_with_has_one_through_source_reflection assert_equal [member_types(:founding)], members(:groucho).nested_member_types end def test_has_many_through_has_one_with_has_one_through_source_reflection_preload members = assert_queries(4) { Member.includes(:nested_member_types).to_a } founding = member_types(:founding) assert_no_queries do assert_equal [founding], members.first.nested_member_types end end def test_has_many_through_has_one_with_has_one_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( Member.where('member_types.id' => member_types(:founding).id), [members(:groucho)], :nested_member_types ) end # has_many through # Source: has_one # Through: has_one through def test_has_many_through_has_one_through_with_has_one_source_reflection assert_equal [sponsors(:moustache_club_sponsor_for_groucho)], members(:groucho).nested_sponsors end def test_has_many_through_has_one_through_with_has_one_source_reflection_preload members = assert_queries(4) { Member.includes(:nested_sponsors).to_a } mustache = sponsors(:moustache_club_sponsor_for_groucho) assert_no_queries(ignore_none: false) do assert_equal [mustache], members.first.nested_sponsors end end def test_has_many_through_has_one_through_with_has_one_source_reflection_preload_via_joins assert_includes_and_joins_equal( Member.where('sponsors.id' => sponsors(:moustache_club_sponsor_for_groucho).id), [members(:groucho)], :nested_sponsors ) end # has_many through # Source: has_many through # Through: has_one def test_has_many_through_has_one_with_has_many_through_source_reflection groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_equal [groucho_details, other_details], members(:groucho).organization_member_details.order('member_details.id') end def test_has_many_through_has_one_with_has_many_through_source_reflection_preload ActiveRecord::Base.connection.table_alias_length # preheat cache members = assert_queries(4) { Member.includes(:organization_member_details).to_a.sort_by(&:id) } groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_no_queries do assert_equal [groucho_details, other_details], members.first.organization_member_details.sort_by(&:id) end end def test_has_many_through_has_one_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( Member.where('member_details.id' => member_details(:groucho).id).order('member_details.id'), [members(:groucho), members(:some_other_guy)], :organization_member_details ) members = Member.joins(:organization_member_details). where('member_details.id' => 9) assert members.empty? end # has_many through # Source: has_many # Through: has_one through def test_has_many_through_has_one_through_with_has_many_source_reflection groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_equal [groucho_details, other_details], members(:groucho).organization_member_details_2.order('member_details.id') end def test_has_many_through_has_one_through_with_has_many_source_reflection_preload members = assert_queries(4) { Member.includes(:organization_member_details_2).to_a.sort_by(&:id) } groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) # postgresql test if randomly executed then executes "SHOW max_identifier_length". Hence # the need to ignore certain predefined sqls that deal with system calls. assert_no_queries(ignore_none: false) do assert_equal [groucho_details, other_details], members.first.organization_member_details_2.sort_by(&:id) end end def test_has_many_through_has_one_through_with_has_many_source_reflection_preload_via_joins assert_includes_and_joins_equal( Member.where('member_details.id' => member_details(:groucho).id).order('member_details.id'), [members(:groucho), members(:some_other_guy)], :organization_member_details_2 ) members = Member.joins(:organization_member_details_2). where('member_details.id' => 9) assert members.empty? end # has_many through # Source: has_and_belongs_to_many # Through: has_many def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection general, cooking = categories(:general), categories(:cooking) assert_equal [general, cooking], authors(:bob).post_categories.order('categories.id') end def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload authors = assert_queries(4) { Author.includes(:post_categories).to_a.sort_by(&:id) } general, cooking = categories(:general), categories(:cooking) assert_no_queries do assert_equal [general, cooking], authors[2].post_categories.sort_by(&:id) end end def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload_via_joins # preload table schemas Author.joins(:post_categories).first assert_includes_and_joins_equal( Author.where('categories.id' => categories(:cooking).id), [authors(:bob)], :post_categories ) end # has_many through # Source: has_many # Through: has_and_belongs_to_many def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection greetings, more = comments(:greetings), comments(:more_greetings) assert_equal [greetings, more], categories(:technology).post_comments.order('comments.id') end def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload Category.includes(:post_comments).to_a # preheat cache categories = assert_queries(4) { Category.includes(:post_comments).to_a.sort_by(&:id) } greetings, more = comments(:greetings), comments(:more_greetings) assert_no_queries do assert_equal [greetings, more], categories[1].post_comments.sort_by(&:id) end end def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins # preload table schemas Category.joins(:post_comments).first assert_includes_and_joins_equal( Category.where('comments.id' => comments(:more_greetings).id).order('categories.id'), [categories(:general), categories(:technology)], :post_comments ) end # has_many through # Source: has_many through a habtm # Through: has_many through def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection greetings, more = comments(:greetings), comments(:more_greetings) assert_equal [greetings, more], authors(:bob).category_post_comments.order('comments.id') end def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload authors = assert_queries(6) { Author.includes(:category_post_comments).to_a.sort_by(&:id) } greetings, more = comments(:greetings), comments(:more_greetings) assert_no_queries do assert_equal [greetings, more], authors[2].category_post_comments.sort_by(&:id) end end def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins # preload table schemas Author.joins(:category_post_comments).first assert_includes_and_joins_equal( Author.where('comments.id' => comments(:does_it_hurt).id).order('authors.id'), [authors(:david), authors(:mary)], :category_post_comments ) end # has_many through # Source: belongs_to # Through: has_many through def test_has_many_through_has_many_through_with_belongs_to_source_reflection assert_equal [tags(:general), tags(:general)], authors(:david).tagging_tags end def test_has_many_through_has_many_through_with_belongs_to_source_reflection_preload authors = assert_queries(5) { Author.includes(:tagging_tags).to_a } general = tags(:general) assert_no_queries do assert_equal [general, general], authors.first.tagging_tags end end def test_has_many_through_has_many_through_with_belongs_to_source_reflection_preload_via_joins assert_includes_and_joins_equal( Author.where('tags.id' => tags(:general).id), [authors(:david)], :tagging_tags ) end # has_many through # Source: has_many through # Through: belongs_to def test_has_many_through_belongs_to_with_has_many_through_source_reflection welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general) assert_equal [welcome_general, thinking_general], categorizations(:david_welcome_general).post_taggings.order('taggings.id') end def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload categorizations = assert_queries(4) { Categorization.includes(:post_taggings).to_a.sort_by(&:id) } welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general) assert_no_queries do assert_equal [welcome_general, thinking_general], categorizations.first.post_taggings.sort_by(&:id) end end def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( Categorization.where('taggings.id' => taggings(:welcome_general).id).order('taggings.id'), [categorizations(:david_welcome_general)], :post_taggings ) end # has_one through # Source: has_one through # Through: has_one def test_has_one_through_has_one_with_has_one_through_source_reflection assert_equal member_types(:founding), members(:groucho).nested_member_type end def test_has_one_through_has_one_with_has_one_through_source_reflection_preload members = assert_queries(4) { Member.includes(:nested_member_type).to_a.sort_by(&:id) } founding = member_types(:founding) assert_no_queries do assert_equal founding, members.first.nested_member_type end end def test_has_one_through_has_one_with_has_one_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( Member.where('member_types.id' => member_types(:founding).id), [members(:groucho)], :nested_member_type ) end # has_one through # Source: belongs_to # Through: has_one through def test_has_one_through_has_one_through_with_belongs_to_source_reflection assert_equal categories(:general), members(:groucho).club_category end def test_joins_and_includes_from_through_models_not_included_in_association prev_default_scope = Club.default_scopes [:includes, :preload, :joins, :eager_load].each do |q| Club.default_scopes = [proc { Club.send(q, :category) }] assert_equal categories(:general), members(:groucho).reload.club_category end ensure Club.default_scopes = prev_default_scope end def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload members = assert_queries(4) { Member.includes(:club_category).to_a.sort_by(&:id) } general = categories(:general) assert_no_queries do assert_equal general, members.first.club_category end end def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload_via_joins assert_includes_and_joins_equal( Member.where('categories.id' => categories(:technology).id), [members(:blarpy_winkup)], :club_category ) end def test_distinct_has_many_through_a_has_many_through_association_on_source_reflection author = authors(:david) assert_equal [tags(:general)], author.distinct_tags end def test_distinct_has_many_through_a_has_many_through_association_on_through_reflection author = authors(:david) assert_equal [subscribers(:first), subscribers(:second)], author.distinct_subscribers.order('subscribers.nick') end def test_nested_has_many_through_with_a_table_referenced_multiple_times author = authors(:bob) assert_equal [posts(:misc_by_bob), posts(:misc_by_mary), posts(:other_by_bob), posts(:other_by_mary)], author.similar_posts.sort_by(&:id) # Mary and Bob both have posts in misc, but they are the only ones. authors = Author.joins(:similar_posts).where('posts.id' => posts(:misc_by_bob).id) assert_equal [authors(:mary), authors(:bob)], authors.distinct.sort_by(&:id) # Check the polymorphism of taggings is being observed correctly (in both joins) authors = Author.joins(:similar_posts).where('taggings.taggable_type' => 'FakeModel') assert authors.empty? authors = Author.joins(:similar_posts).where('taggings_authors_join.taggable_type' => 'FakeModel') assert authors.empty? end def test_has_many_through_with_foreign_key_option_on_through_reflection assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order('posts.id') assert_equal [authors(:david)], references(:david_unicyclist).agents_posts_authors references = Reference.joins(:agents_posts_authors).where('authors.id' => authors(:david).id) assert_equal [references(:david_unicyclist)], references end def test_has_many_through_with_foreign_key_option_on_source_reflection assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order('people.id') jobs = Job.joins(:agents) assert_equal [jobs(:unicyclist), jobs(:unicyclist)], jobs end def test_has_many_through_with_sti_on_through_reflection ratings = posts(:sti_comments).special_comments_ratings.sort_by(&:id) assert_equal [ratings(:special_comment_rating), ratings(:sub_special_comment_rating)], ratings # Ensure STI is respected in the join scope = Post.joins(:special_comments_ratings).where(:id => posts(:sti_comments).id) assert scope.where("comments.type" => "Comment").empty? assert !scope.where("comments.type" => "SpecialComment").empty? assert !scope.where("comments.type" => "SubSpecialComment").empty? end def test_has_many_through_with_sti_on_nested_through_reflection taggings = posts(:sti_comments).special_comments_ratings_taggings assert_equal [taggings(:special_comment_rating)], taggings scope = Post.joins(:special_comments_ratings_taggings).where(:id => posts(:sti_comments).id) assert scope.where("comments.type" => "Comment").empty? assert !scope.where("comments.type" => "SpecialComment").empty? end def test_nested_has_many_through_writers_should_raise_error david = authors(:david) subscriber = subscribers(:first) assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do david.subscribers = [subscriber] end assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do david.subscriber_ids = [subscriber.id] end assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do david.subscribers << subscriber end assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do david.subscribers.delete(subscriber) end assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do david.subscribers.clear end assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do david.subscribers.build end assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do david.subscribers.create end end def test_nested_has_one_through_writers_should_raise_error groucho = members(:groucho) founding = member_types(:founding) assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do groucho.nested_member_type = founding end end def test_nested_has_many_through_with_conditions_on_through_associations assert_equal [tags(:blue)], authors(:bob).misc_post_first_blue_tags end def test_nested_has_many_through_with_conditions_on_through_associations_preload assert Author.where('tags.id' => 100).joins(:misc_post_first_blue_tags).empty? authors = assert_queries(3) { Author.includes(:misc_post_first_blue_tags).to_a.sort_by(&:id) } blue = tags(:blue) assert_no_queries do assert_equal [blue], authors[2].misc_post_first_blue_tags end end def test_nested_has_many_through_with_conditions_on_through_associations_preload_via_joins # Pointless condition to force single-query loading assert_includes_and_joins_equal( Author.where('tags.id = tags.id').references(:tags), [authors(:bob)], :misc_post_first_blue_tags ) end def test_nested_has_many_through_with_conditions_on_source_associations assert_equal [tags(:blue)], authors(:bob).misc_post_first_blue_tags_2 end def test_nested_has_many_through_with_conditions_on_source_associations_preload authors = assert_queries(4) { Author.includes(:misc_post_first_blue_tags_2).to_a.sort_by(&:id) } blue = tags(:blue) assert_no_queries do assert_equal [blue], authors[2].misc_post_first_blue_tags_2 end end def test_nested_has_many_through_with_conditions_on_source_associations_preload_via_joins # Pointless condition to force single-query loading assert_includes_and_joins_equal( Author.where('tags.id = tags.id').references(:tags), [authors(:bob)], :misc_post_first_blue_tags_2 ) end def test_nested_has_many_through_with_foreign_key_option_on_the_source_reflection_through_reflection assert_equal [categories(:general)], organizations(:nsa).author_essay_categories organizations = Organization.joins(:author_essay_categories). where('categories.id' => categories(:general).id) assert_equal [organizations(:nsa)], organizations assert_equal categories(:general), organizations(:nsa).author_owned_essay_category organizations = Organization.joins(:author_owned_essay_category). where('categories.id' => categories(:general).id) assert_equal [organizations(:nsa)], organizations end def test_nested_has_many_through_should_not_be_autosaved c = Categorization.new c.author = authors(:david) c.post_taggings.to_a assert !c.post_taggings.empty? c.save assert !c.post_taggings.empty? end private def assert_includes_and_joins_equal(query, expected, association) actual = assert_queries(1) { query.joins(association).to_a.uniq } assert_equal expected, actual actual = assert_queries(1) { query.includes(association).to_a.uniq } assert_equal expected, actual end end rails-4.2.6/activerecord/test/cases/associations/required_test.rb000066400000000000000000000041221266740050600252740ustar00rootroot00000000000000require "cases/helper" class RequiredAssociationsTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class Parent < ActiveRecord::Base end class Child < ActiveRecord::Base end setup do @connection = ActiveRecord::Base.connection @connection.create_table :parents, force: true @connection.create_table :children, force: true do |t| t.belongs_to :parent end end teardown do @connection.drop_table 'parents' if @connection.table_exists? 'parents' @connection.drop_table 'children' if @connection.table_exists? 'children' end test "belongs_to associations are not required by default" do model = subclass_of(Child) do belongs_to :parent, inverse_of: false, class_name: "RequiredAssociationsTest::Parent" end assert model.new.save assert model.new(parent: Parent.new).save end test "required belongs_to associations have presence validated" do model = subclass_of(Child) do belongs_to :parent, required: true, inverse_of: false, class_name: "RequiredAssociationsTest::Parent" end record = model.new assert_not record.save assert_equal ["Parent can't be blank"], record.errors.full_messages record.parent = Parent.new assert record.save end test "has_one associations are not required by default" do model = subclass_of(Parent) do has_one :child, inverse_of: false, class_name: "RequiredAssociationsTest::Child" end assert model.new.save assert model.new(child: Child.new).save end test "required has_one associations have presence validated" do model = subclass_of(Parent) do has_one :child, required: true, inverse_of: false, class_name: "RequiredAssociationsTest::Child" end record = model.new assert_not record.save assert_equal ["Child can't be blank"], record.errors.full_messages record.child = Child.new assert record.save end private def subclass_of(klass, &block) subclass = Class.new(klass, &block) def subclass.name superclass.name end subclass end end rails-4.2.6/activerecord/test/cases/associations_test.rb000066400000000000000000000317411266740050600234630ustar00rootroot00000000000000require "cases/helper" require 'models/computer' require 'models/developer' require 'models/computer' require 'models/project' require 'models/company' require 'models/categorization' require 'models/category' require 'models/post' require 'models/author' require 'models/comment' require 'models/tag' require 'models/tagging' require 'models/person' require 'models/reader' require 'models/parrot' require 'models/ship_part' require 'models/ship' require 'models/liquid' require 'models/molecule' require 'models/electron' require 'models/man' require 'models/interest' class AssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :developers_projects, :computers, :people, :readers, :authors, :author_favorites def test_eager_loading_should_not_change_count_of_children liquid = Liquid.create(:name => 'salty') molecule = liquid.molecules.create(:name => 'molecule_1') molecule.electrons.create(:name => 'electron_1') molecule.electrons.create(:name => 'electron_2') liquids = Liquid.includes(:molecules => :electrons).references(:molecules).where('molecules.id is not null') assert_equal 1, liquids[0].molecules.length end def test_subselect author = authors :david favs = author.author_favorites fav2 = author.author_favorites.where(:author => Author.where(id: author.id)).to_a assert_equal favs, fav2 end def test_clear_association_cache_stored firm = Firm.find(1) assert_kind_of Firm, firm firm.clear_association_cache assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort end def test_clear_association_cache_new_record firm = Firm.new client_stored = Client.find(3) client_new = Client.new client_new.name = "The Joneses" clients = [ client_stored, client_new ] firm.clients << clients assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set firm.clear_association_cache assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set end def test_loading_the_association_target_should_keep_child_records_marked_for_destruction ship = Ship.create!(:name => "The good ship Dollypop") part = ship.parts.create!(:name => "Mast") part.mark_for_destruction ship.parts.send(:load_target) assert ship.parts[0].marked_for_destruction? end def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction ship = Ship.create!(:name => "The good ship Dollypop") part = ship.parts.create!(:name => "Mast") part.mark_for_destruction ShipPart.find(part.id).update_columns(name: 'Deck') ship.parts.send(:load_target) assert_equal 'Deck', ship.parts[0].name end def test_include_with_order_works assert_nothing_raised {Account.all.merge!(:order => 'id', :includes => :firm).first} assert_nothing_raised {Account.all.merge!(:order => :id, :includes => :firm).first} end def test_bad_collection_keys assert_raise(ArgumentError, 'ActiveRecord should have barked on bad collection keys') do Class.new(ActiveRecord::Base).has_many(:wheels, :name => 'wheels') end end def test_should_construct_new_finder_sql_after_create person = Person.new :first_name => 'clark' assert_equal [], person.readers.to_a person.save! reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar") assert person.readers.find(reader.id) end def test_force_reload firm = Firm.new("name" => "A New Firm, Inc") firm.save firm.clients.each {} # forcing to load all clients assert firm.clients.empty?, "New firm shouldn't have client objects" assert_equal 0, firm.clients.size, "New firm should have 0 clients" client = Client.new("name" => "TheClient.com", "firm_id" => firm.id) client.save assert firm.clients.empty?, "New firm should have cached no client objects" assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count" assert !firm.clients(true).empty?, "New firm should have reloaded client objects" assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count" end def test_using_limitable_reflections_helper using_limitable_reflections = lambda { |reflections| Tagging.all.send :using_limitable_reflections?, reflections } belongs_to_reflections = [Tagging.reflect_on_association(:tag), Tagging.reflect_on_association(:super_tag)] has_many_reflections = [Tag.reflect_on_association(:taggings), Developer.reflect_on_association(:projects)] mixed_reflections = (belongs_to_reflections + has_many_reflections).uniq assert using_limitable_reflections.call(belongs_to_reflections), "Belong to associations are limitable" assert !using_limitable_reflections.call(has_many_reflections), "All has many style associations are not limitable" assert !using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass" end def test_force_reload_is_uncached firm = Firm.create!("name" => "A New Firm, Inc") Client.create!("name" => "TheClient.com", :firm => firm) ActiveRecord::Base.cache do firm.clients.each {} assert_queries(0) { assert_not_nil firm.clients.each {} } assert_queries(1) { assert_not_nil firm.clients(true).each {} } end end def test_association_with_references firm = companies(:first_firm) assert_equal ['foo'], firm.association_with_references.references_values end end class AssociationProxyTest < ActiveRecord::TestCase fixtures :authors, :posts, :categorizations, :categories, :developers, :projects, :developers_projects def test_push_does_not_load_target david = authors(:david) david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!")) assert !david.posts.loaded? assert david.posts.include?(post) end def test_push_has_many_through_does_not_load_target david = authors(:david) david.categories << categories(:technology) assert !david.categories.loaded? assert david.categories.include?(categories(:technology)) end def test_push_followed_by_save_does_not_load_target david = authors(:david) david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!")) assert !david.posts.loaded? david.save assert !david.posts.loaded? assert david.posts.include?(post) end def test_push_does_not_lose_additions_to_new_record josh = Author.new(:name => "Josh") josh.posts << Post.new(:title => "New on Edge", :body => "More cool stuff!") assert josh.posts.loaded? assert_equal 1, josh.posts.size end def test_append_behaves_like_push josh = Author.new(:name => "Josh") josh.posts.append Post.new(:title => "New on Edge", :body => "More cool stuff!") assert josh.posts.loaded? assert_equal 1, josh.posts.size end def test_prepend_is_not_defined josh = Author.new(:name => "Josh") assert_raises(NoMethodError) { josh.posts.prepend Post.new } end def test_save_on_parent_does_not_load_target david = developers(:david) assert !david.projects.loaded? david.update_columns(created_at: Time.now) assert !david.projects.loaded? end def test_inspect_does_not_reload_a_not_yet_loaded_target andreas = Developer.new :name => 'Andreas', :log => 'new developer added' assert !andreas.audit_logs.loaded? assert_match(/message: "new developer added"/, andreas.audit_logs.inspect) end def test_save_on_parent_saves_children developer = Developer.create :name => "Bryan", :salary => 50_000 assert_equal 1, developer.reload.audit_logs.size end def test_create_via_association_with_block post = authors(:david).posts.create(:title => "New on Edge") {|p| p.body = "More cool stuff!"} assert_equal post.title, "New on Edge" assert_equal post.body, "More cool stuff!" end def test_create_with_bang_via_association_with_block post = authors(:david).posts.create!(:title => "New on Edge") {|p| p.body = "More cool stuff!"} assert_equal post.title, "New on Edge" assert_equal post.body, "More cool stuff!" end def test_reload_returns_association david = developers(:david) assert_nothing_raised do assert_equal david.projects, david.projects.reload.reload end end def test_proxy_association_accessor david = developers(:david) assert_equal david.association(:projects), david.projects.proxy_association end def test_scoped_allows_conditions assert developers(:david).projects.merge!(where: 'foo').where_values.include?('foo') end test "getting a scope from an association" do david = developers(:david) assert david.projects.scope.is_a?(ActiveRecord::Relation) assert_equal david.projects, david.projects.scope end test "proxy object is cached" do david = developers(:david) assert david.projects.equal?(david.projects) end test "inverses get set of subsets of the association" do man = Man.create man.interests.create man = Man.find(man.id) assert_queries(1) do assert_equal man, man.interests.where("1=1").first.man end end test "first! works on loaded associations" do david = authors(:david) assert_equal david.posts.first, david.posts.reload.first! end def test_reset_unloads_target david = authors(:david) david.posts.reload assert david.posts.loaded? david.posts.reset assert !david.posts.loaded? end end class OverridingAssociationsTest < ActiveRecord::TestCase class DifferentPerson < ActiveRecord::Base; end class PeopleList < ActiveRecord::Base has_and_belongs_to_many :has_and_belongs_to_many, :before_add => :enlist has_many :has_many, :before_add => :enlist belongs_to :belongs_to has_one :has_one end class DifferentPeopleList < PeopleList # Different association with the same name, callbacks should be omitted here. has_and_belongs_to_many :has_and_belongs_to_many, :class_name => 'DifferentPerson' has_many :has_many, :class_name => 'DifferentPerson' belongs_to :belongs_to, :class_name => 'DifferentPerson' has_one :has_one, :class_name => 'DifferentPerson' end def test_habtm_association_redefinition_callbacks_should_differ_and_not_inherited # redeclared association on AR descendant should not inherit callbacks from superclass callbacks = PeopleList.before_add_for_has_and_belongs_to_many assert_equal(1, callbacks.length) callbacks = DifferentPeopleList.before_add_for_has_and_belongs_to_many assert_equal([], callbacks) end def test_has_many_association_redefinition_callbacks_should_differ_and_not_inherited # redeclared association on AR descendant should not inherit callbacks from superclass callbacks = PeopleList.before_add_for_has_many assert_equal(1, callbacks.length) callbacks = DifferentPeopleList.before_add_for_has_many assert_equal([], callbacks) end def test_habtm_association_redefinition_reflections_should_differ_and_not_inherited assert_not_equal( PeopleList.reflect_on_association(:has_and_belongs_to_many), DifferentPeopleList.reflect_on_association(:has_and_belongs_to_many) ) end def test_has_many_association_redefinition_reflections_should_differ_and_not_inherited assert_not_equal( PeopleList.reflect_on_association(:has_many), DifferentPeopleList.reflect_on_association(:has_many) ) end def test_belongs_to_association_redefinition_reflections_should_differ_and_not_inherited assert_not_equal( PeopleList.reflect_on_association(:belongs_to), DifferentPeopleList.reflect_on_association(:belongs_to) ) end def test_has_one_association_redefinition_reflections_should_differ_and_not_inherited assert_not_equal( PeopleList.reflect_on_association(:has_one), DifferentPeopleList.reflect_on_association(:has_one) ) end def test_requires_symbol_argument assert_raises ArgumentError do Class.new(Post) do belongs_to "author" end end end end class GeneratedMethodsTest < ActiveRecord::TestCase fixtures :developers, :computers, :posts, :comments def test_association_methods_override_attribute_methods_of_same_name assert_equal(developers(:david), computers(:workstation).developer) # this next line will fail if the attribute methods module is generated lazily # after the association methods module is generated assert_equal(developers(:david), computers(:workstation).developer) assert_equal(developers(:david).id, computers(:workstation)[:developer]) end def test_model_method_overrides_association_method assert_equal(comments(:greetings).body, posts(:welcome).first_comment) end module MyModule def comments; :none end end class MyArticle < ActiveRecord::Base self.table_name = "articles" include MyModule has_many :comments, inverse_of: false end def test_included_module_overwrites_association_methods assert_equal :none, MyArticle.new.comments end end rails-4.2.6/activerecord/test/cases/attribute_decorators_test.rb000066400000000000000000000103351266740050600252100ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class AttributeDecoratorsTest < ActiveRecord::TestCase class Model < ActiveRecord::Base self.table_name = 'attribute_decorators_model' end class StringDecorator < SimpleDelegator def initialize(delegate, decoration = "decorated!") @decoration = decoration super(delegate) end def type_cast_from_user(value) "#{super} #{@decoration}" end alias type_cast_from_database type_cast_from_user end setup do @connection = ActiveRecord::Base.connection @connection.create_table :attribute_decorators_model, force: true do |t| t.string :a_string end end teardown do return unless @connection @connection.drop_table 'attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model' Model.attribute_type_decorations.clear Model.reset_column_information end test "attributes can be decorated" do model = Model.new(a_string: 'Hello') assert_equal 'Hello', model.a_string Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } model = Model.new(a_string: 'Hello') assert_equal 'Hello decorated!', model.a_string end test "decoration does not eagerly load existing columns" do Model.reset_column_information assert_no_queries do Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } end end test "undecorated columns are not touched" do Model.attribute :another_string, Type::String.new, default: 'something or other' Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } assert_equal 'something or other', Model.new.another_string end test "decorators can be chained" do Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } Model.decorate_attribute_type(:a_string, :other) { |t| StringDecorator.new(t) } model = Model.new(a_string: 'Hello!') assert_equal 'Hello! decorated! decorated!', model.a_string end test "decoration of the same type multiple times is idempotent" do Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } model = Model.new(a_string: 'Hello') assert_equal 'Hello decorated!', model.a_string end test "decorations occur in order of declaration" do Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } Model.decorate_attribute_type(:a_string, :other) do |type| StringDecorator.new(type, 'decorated again!') end model = Model.new(a_string: 'Hello!') assert_equal 'Hello! decorated! decorated again!', model.a_string end test "decorating attributes does not modify parent classes" do Model.attribute :another_string, Type::String.new, default: 'whatever' Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } child_class = Class.new(Model) child_class.decorate_attribute_type(:another_string, :test) { |t| StringDecorator.new(t) } child_class.decorate_attribute_type(:a_string, :other) { |t| StringDecorator.new(t) } model = Model.new(a_string: 'Hello!') child = child_class.new(a_string: 'Hello!') assert_equal 'Hello! decorated!', model.a_string assert_equal 'whatever', model.another_string assert_equal 'Hello! decorated! decorated!', child.a_string assert_equal 'whatever decorated!', child.another_string end class Multiplier < SimpleDelegator def type_cast_from_user(value) return if value.nil? value * 2 end alias type_cast_from_database type_cast_from_user end test "decorating with a proc" do Model.attribute :an_int, Type::Integer.new type_is_integer = proc { |_, type| type.type == :integer } Model.decorate_matching_attribute_types type_is_integer, :multiplier do |type| Multiplier.new(type) end model = Model.new(a_string: 'whatever', an_int: 1) assert_equal 'whatever', model.a_string assert_equal 2, model.an_int end end end rails-4.2.6/activerecord/test/cases/attribute_methods/000077500000000000000000000000001266740050600231205ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/attribute_methods/read_test.rb000066400000000000000000000027201266740050600254200ustar00rootroot00000000000000require "cases/helper" require 'thread' module ActiveRecord module AttributeMethods class ReadTest < ActiveRecord::TestCase class FakeColumn < Struct.new(:name) def type; :integer; end end def setup @klass = Class.new do def self.superclass; Base; end def self.base_class; self; end def self.decorate_matching_attribute_types(*); end def self.initialize_generated_modules; end include ActiveRecord::AttributeMethods def self.column_names %w{ one two three } end def self.primary_key end def self.columns column_names.map { FakeColumn.new(name) } end def self.columns_hash Hash[column_names.map { |name| [name, FakeColumn.new(name)] }] end end end def test_define_attribute_methods instance = @klass.new @klass.column_names.each do |name| assert !instance.methods.map(&:to_s).include?(name) end @klass.define_attribute_methods @klass.column_names.each do |name| assert instance.methods.map(&:to_s).include?(name), "#{name} is not defined" end end def test_attribute_methods_generated? assert_not @klass.method_defined?(:one) @klass.define_attribute_methods assert @klass.method_defined?(:one) end end end end rails-4.2.6/activerecord/test/cases/attribute_methods_test.rb000066400000000000000000000730711266740050600245140ustar00rootroot00000000000000require "cases/helper" require 'models/minimalistic' require 'models/developer' require 'models/computer' require 'models/auto_id' require 'models/boolean' require 'models/computer' require 'models/topic' require 'models/company' require 'models/category' require 'models/reply' require 'models/contact' require 'models/keyboard' class AttributeMethodsTest < ActiveRecord::TestCase include InTimeZone fixtures :topics, :developers, :companies, :computers def setup @old_matchers = ActiveRecord::Base.send(:attribute_method_matchers).dup @target = Class.new(ActiveRecord::Base) @target.table_name = 'topics' end teardown do ActiveRecord::Base.send(:attribute_method_matchers).clear ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers) end def test_attribute_for_inspect t = topics(:first) t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters" assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on) assert_equal '"The First Topic Now Has A Title With\nNewlines And ..."', t.attribute_for_inspect(:title) end def test_attribute_present t = Topic.new t.title = "hello there!" t.written_on = Time.now t.author_name = "" assert t.attribute_present?("title") assert t.attribute_present?("written_on") assert !t.attribute_present?("content") assert !t.attribute_present?("author_name") end def test_attribute_present_with_booleans b1 = Boolean.new b1.value = false assert b1.attribute_present?(:value) b2 = Boolean.new b2.value = true assert b2.attribute_present?(:value) b3 = Boolean.new assert !b3.attribute_present?(:value) b4 = Boolean.new b4.value = false b4.save! assert Boolean.find(b4.id).attribute_present?(:value) end def test_caching_nil_primary_key klass = Class.new(Minimalistic) klass.expects(:reset_primary_key).returns(nil).once 2.times { klass.primary_key } end def test_attribute_keys_on_new_instance t = Topic.new assert_equal nil, t.title, "The topics table has a title column, so it should be nil" assert_raise(NoMethodError) { t.title2 } end def test_boolean_attributes assert !Topic.find(1).approved? assert Topic.find(2).approved? end def test_set_attributes topic = Topic.find(1) topic.attributes = { "title" => "Budget", "author_name" => "Jason" } topic.save assert_equal("Budget", topic.title) assert_equal("Jason", topic.author_name) assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) end def test_set_attributes_without_hash topic = Topic.new assert_raise(ArgumentError) { topic.attributes = '' } end def test_integers_as_nil test = AutoId.create('value' => '') assert_nil AutoId.find(test.id).value end def test_set_attributes_with_block topic = Topic.new do |t| t.title = "Budget" t.author_name = "Jason" end assert_equal("Budget", topic.title) assert_equal("Jason", topic.author_name) end def test_respond_to? topic = Topic.find(1) assert_respond_to topic, "title" assert_respond_to topic, "title?" assert_respond_to topic, "title=" assert_respond_to topic, :title assert_respond_to topic, :title? assert_respond_to topic, :title= assert_respond_to topic, "author_name" assert_respond_to topic, "attribute_names" assert !topic.respond_to?("nothingness") assert !topic.respond_to?(:nothingness) end def test_respond_to_with_custom_primary_key keyboard = Keyboard.create assert_not_nil keyboard.key_number assert_equal keyboard.key_number, keyboard.id assert keyboard.respond_to?('key_number') assert keyboard.respond_to?('id') end def test_id_before_type_cast_with_custom_primary_key keyboard = Keyboard.create keyboard.key_number = '10' assert_equal '10', keyboard.id_before_type_cast assert_equal nil, keyboard.read_attribute_before_type_cast('id') assert_equal '10', keyboard.read_attribute_before_type_cast('key_number') assert_equal '10', keyboard.read_attribute_before_type_cast(:key_number) end # Syck calls respond_to? before actually calling initialize def test_respond_to_with_allocated_object klass = Class.new(ActiveRecord::Base) do self.table_name = 'topics' end topic = klass.allocate assert !topic.respond_to?("nothingness") assert !topic.respond_to?(:nothingness) assert_respond_to topic, "title" assert_respond_to topic, :title end # IRB inspects the return value of "MyModel.allocate". def test_allocated_object_can_be_inspected topic = Topic.allocate assert_equal "#", topic.inspect end def test_array_content topic = Topic.new topic.content = %w( one two three ) topic.save assert_equal(%w( one two three ), Topic.find(topic.id).content) end def test_read_attributes_before_type_cast category = Category.new({:name=>"Test category", :type => nil}) category_attrs = {"name"=>"Test category", "id" => nil, "type" => nil, "categorizations_count" => nil} assert_equal category_attrs , category.attributes_before_type_cast end if current_adapter?(:MysqlAdapter) def test_read_attributes_before_type_cast_on_boolean bool = Boolean.create({ "value" => false }) if RUBY_PLATFORM =~ /java/ # JRuby will return the value before typecast as string assert_equal "0", bool.reload.attributes_before_type_cast["value"] else assert_equal 0, bool.reload.attributes_before_type_cast["value"] end end end def test_read_attributes_before_type_cast_on_datetime in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = "345643456" assert_equal "345643456", record.written_on_before_type_cast assert_equal nil, record.written_on record.written_on = "2009-10-11 12:13:14" assert_equal "2009-10-11 12:13:14", record.written_on_before_type_cast assert_equal Time.zone.parse("2009-10-11 12:13:14"), record.written_on assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone end end def test_read_attributes_after_type_cast_on_datetime tz = "Pacific Time (US & Canada)" in_time_zone tz do record = @target.new date_string = "2011-03-24" time = Time.zone.parse date_string record.written_on = date_string assert_equal date_string, record.written_on_before_type_cast assert_equal time, record.written_on assert_equal ActiveSupport::TimeZone[tz], record.written_on.time_zone record.save record.reload assert_equal time, record.written_on end end def test_hash_content topic = Topic.new topic.content = { "one" => 1, "two" => 2 } topic.save assert_equal 2, Topic.find(topic.id).content["two"] topic.content_will_change! topic.content["three"] = 3 topic.save assert_equal 3, Topic.find(topic.id).content["three"] end def test_update_array_content topic = Topic.new topic.content = %w( one two three ) topic.content.push "four" assert_equal(%w( one two three four ), topic.content) topic.save topic = Topic.find(topic.id) topic.content << "five" assert_equal(%w( one two three four five ), topic.content) end def test_case_sensitive_attributes_hash # DB2 is not case-sensitive return true if current_adapter?(:DB2Adapter) assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.first.attributes end def test_attributes_without_primary_key klass = Class.new(ActiveRecord::Base) do self.table_name = 'developers_projects' end assert_equal klass.column_names, klass.new.attributes.keys assert_not klass.new.has_attribute?('id') end def test_hashes_not_mangled new_topic = { :title => "New Topic" } new_topic_values = { :title => "AnotherTopic" } topic = Topic.new(new_topic) assert_equal new_topic[:title], topic.title topic.attributes= new_topic_values assert_equal new_topic_values[:title], topic.title end def test_create_through_factory topic = Topic.create("title" => "New Topic") topicReloaded = Topic.find(topic.id) assert_equal(topic, topicReloaded) end def test_write_attribute topic = Topic.new topic.send(:write_attribute, :title, "Still another topic") assert_equal "Still another topic", topic.title topic[:title] = "Still another topic: part 2" assert_equal "Still another topic: part 2", topic.title topic.send(:write_attribute, "title", "Still another topic: part 3") assert_equal "Still another topic: part 3", topic.title topic["title"] = "Still another topic: part 4" assert_equal "Still another topic: part 4", topic.title end def test_read_attribute topic = Topic.new topic.title = "Don't change the topic" assert_equal "Don't change the topic", topic.read_attribute("title") assert_equal "Don't change the topic", topic["title"] assert_equal "Don't change the topic", topic.read_attribute(:title) assert_equal "Don't change the topic", topic[:title] end def test_read_attribute_raises_missing_attribute_error_when_not_exists computer = Computer.select('id').first assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] } assert_raises(ActiveModel::MissingAttributeError) { computer[:extendedWarranty] } assert_raises(ActiveModel::MissingAttributeError) { computer[:no_column_exists] = 'Hello!' } assert_nothing_raised { computer[:developer] = 'Hello!' } end def test_read_attribute_when_false topic = topics(:first) topic.approved = false assert !topic.approved?, "approved should be false" topic.approved = "false" assert !topic.approved?, "approved should be false" end def test_read_attribute_when_true topic = topics(:first) topic.approved = true assert topic.approved?, "approved should be true" topic.approved = "true" assert topic.approved?, "approved should be true" end def test_read_write_boolean_attribute topic = Topic.new topic.approved = "false" assert !topic.approved?, "approved should be false" topic.approved = "false" assert !topic.approved?, "approved should be false" topic.approved = "true" assert topic.approved?, "approved should be true" topic.approved = "true" assert topic.approved?, "approved should be true" end def test_overridden_write_attribute topic = Topic.new def topic.write_attribute(attr_name, value) super(attr_name, value.downcase) end topic.send(:write_attribute, :title, "Yet another topic") assert_equal "yet another topic", topic.title topic[:title] = "Yet another topic: part 2" assert_equal "yet another topic: part 2", topic.title topic.send(:write_attribute, "title", "Yet another topic: part 3") assert_equal "yet another topic: part 3", topic.title topic["title"] = "Yet another topic: part 4" assert_equal "yet another topic: part 4", topic.title end def test_overridden_read_attribute topic = Topic.new topic.title = "Stop changing the topic" def topic.read_attribute(attr_name) super(attr_name).upcase end assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute("title") assert_equal "STOP CHANGING THE TOPIC", topic["title"] assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute(:title) assert_equal "STOP CHANGING THE TOPIC", topic[:title] end def test_read_overridden_attribute topic = Topic.new(:title => 'a') def topic.title() 'b' end assert_equal 'a', topic[:title] end def test_query_attribute_string [nil, "", " "].each do |value| assert_equal false, Topic.new(:author_name => value).author_name? end assert_equal true, Topic.new(:author_name => "Name").author_name? end def test_query_attribute_number [nil, 0, "0"].each do |value| assert_equal false, Developer.new(:salary => value).salary? end assert_equal true, Developer.new(:salary => 1).salary? assert_equal true, Developer.new(:salary => "1").salary? end def test_query_attribute_boolean [nil, "", false, "false", "f", 0].each do |value| assert_equal false, Topic.new(:approved => value).approved? end [true, "true", "1", 1].each do |value| assert_equal true, Topic.new(:approved => value).approved? end end def test_query_attribute_with_custom_fields object = Company.find_by_sql(<<-SQL).first SELECT c1.*, c2.type as string_value, c2.rating as int_value FROM companies c1, companies c2 WHERE c1.firm_id = c2.id AND c1.id = 2 SQL assert_equal "Firm", object.string_value assert object.string_value? object.string_value = " " assert !object.string_value? assert_equal 1, object.int_value.to_i assert object.int_value? object.int_value = "0" assert !object.int_value? end def test_non_attribute_access_and_assignment topic = Topic.new assert !topic.respond_to?("mumbo") assert_raise(NoMethodError) { topic.mumbo } assert_raise(NoMethodError) { topic.mumbo = 5 } end def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing topic = @target.new(:title => 'Budget') assert topic.respond_to?('title') assert_equal 'Budget', topic.title assert !topic.respond_to?('title_hello_world') assert_raise(NoMethodError) { topic.title_hello_world } end def test_declared_prefixed_attribute_method_affects_respond_to_and_method_missing topic = @target.new(:title => 'Budget') %w(default_ title_).each do |prefix| @target.class_eval "def #{prefix}attribute(*args) args end" @target.attribute_method_prefix prefix meth = "#{prefix}title" assert topic.respond_to?(meth) assert_equal ['title'], topic.send(meth) assert_equal ['title', 'a'], topic.send(meth, 'a') assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) end end def test_declared_suffixed_attribute_method_affects_respond_to_and_method_missing %w(_default _title_default _it! _candidate= able?).each do |suffix| @target.class_eval "def attribute#{suffix}(*args) args end" @target.attribute_method_suffix suffix topic = @target.new(:title => 'Budget') meth = "title#{suffix}" assert topic.respond_to?(meth) assert_equal ['title'], topic.send(meth) assert_equal ['title', 'a'], topic.send(meth, 'a') assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) end end def test_declared_affixed_attribute_method_affects_respond_to_and_method_missing [['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix| @target.class_eval "def #{prefix}attribute#{suffix}(*args) args end" @target.attribute_method_affix({ :prefix => prefix, :suffix => suffix }) topic = @target.new(:title => 'Budget') meth = "#{prefix}title#{suffix}" assert topic.respond_to?(meth) assert_equal ['title'], topic.send(meth) assert_equal ['title', 'a'], topic.send(meth, 'a') assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) end end def test_should_unserialize_attributes_for_frozen_records myobj = {:value1 => :value2} topic = Topic.create("content" => myobj) topic.freeze assert_equal myobj, topic.content end def test_typecast_attribute_from_select_to_false Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT if current_adapter?(:OracleAdapter) topic = Topic.all.merge!(:select => "topics.*, 0 as is_test").first else topic = Topic.all.merge!(:select => "topics.*, 1=2 as is_test").first end assert !topic.is_test? end def test_typecast_attribute_from_select_to_true Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT if current_adapter?(:OracleAdapter) topic = Topic.all.merge!(:select => "topics.*, 1 as is_test").first else topic = Topic.all.merge!(:select => "topics.*, 2=2 as is_test").first end assert topic.is_test? end def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model %w(save create_or_update).each do |method| klass = Class.new ActiveRecord::Base klass.class_eval "def #{method}() 'defined #{method}' end" assert_raise ActiveRecord::DangerousAttributeError do klass.instance_method_already_implemented?(method) end end end def test_deprecated_cache_attributes assert_deprecated do Topic.cache_attributes :replies_count end assert_deprecated do Topic.cached_attributes end assert_deprecated do Topic.cache_attribute? :replies_count end end def test_converted_values_are_returned_after_assignment developer = Developer.new(name: 1337, salary: "50000") assert_equal "50000", developer.salary_before_type_cast assert_equal 1337, developer.name_before_type_cast assert_equal 50000, developer.salary assert_equal "1337", developer.name developer.save! assert_equal "50000", developer.salary_before_type_cast assert_equal 1337, developer.name_before_type_cast assert_equal 50000, developer.salary assert_equal "1337", developer.name end def test_write_nil_to_time_attributes in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = nil assert_nil record.written_on end end def test_write_time_to_date_attributes in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.last_read = Time.utc(2010, 1, 1, 10) assert_equal Date.civil(2010, 1, 1), record.last_read end end def test_time_attributes_are_retrieved_in_current_time_zone in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) record = @target.new record[:written_on] = utc_time assert_equal utc_time, record.written_on # record.written on is equal to (i.e., simultaneous with) utc_time assert_kind_of ActiveSupport::TimeWithZone, record.written_on # but is a TimeWithZone assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone # and is in the current Time.zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time # and represents time values adjusted accordingly end end def test_setting_time_zone_aware_attribute_to_utc in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) record = @target.new record.written_on = utc_time assert_equal utc_time, record.written_on assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time end end def test_setting_time_zone_aware_attribute_in_other_time_zone utc_time = Time.utc(2008, 1, 1) cst_time = utc_time.in_time_zone("Central Time (US & Canada)") in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = cst_time assert_equal utc_time, record.written_on assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time end end def test_setting_time_zone_aware_read_attribute utc_time = Time.utc(2008, 1, 1) cst_time = utc_time.in_time_zone("Central Time (US & Canada)") in_time_zone "Pacific Time (US & Canada)" do record = @target.create(:written_on => cst_time).reload assert_equal utc_time, record[:written_on] assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record[:written_on].time_zone assert_equal Time.utc(2007, 12, 31, 16), record[:written_on].time end end def test_setting_time_zone_aware_attribute_with_string utc_time = Time.utc(2008, 1, 1) (-11..13).each do |timezone_offset| time_string = utc_time.in_time_zone(timezone_offset).to_s in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = time_string assert_equal Time.zone.parse(time_string), record.written_on assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time end end end def test_time_zone_aware_attribute_saved in_time_zone 1 do record = @target.create(:written_on => '2012-02-20 10:00') record.written_on = '2012-02-20 09:00' record.save assert_equal Time.zone.local(2012, 02, 20, 9), record.reload.written_on end end def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = ' ' assert_nil record.written_on assert_nil record[:written_on] end end def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone time_string = 'Tue Jan 01 00:00:00 2008' (-11..13).each do |timezone_offset| in_time_zone timezone_offset do record = @target.new record.written_on = time_string assert_equal Time.zone.parse(time_string), record.written_on assert_equal ActiveSupport::TimeZone[timezone_offset], record.written_on.time_zone assert_equal Time.utc(2008, 1, 1), record.written_on.time end end end def test_setting_time_zone_aware_attribute_in_current_time_zone utc_time = Time.utc(2008, 1, 1) in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = utc_time.in_time_zone assert_equal utc_time, record.written_on assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time end end def test_yaml_dumping_record_with_time_zone_aware_attribute in_time_zone "Pacific Time (US & Canada)" do record = Topic.new(id: 1) record.written_on = "Jan 01 00:00:00 2014" assert_equal record, YAML.load(YAML.dump(record)) end end def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable Topic.skip_time_zone_conversion_for_attributes = [:field_a] Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b] assert_equal [:field_a], Topic.skip_time_zone_conversion_for_attributes assert_equal [:field_b], Minimalistic.skip_time_zone_conversion_for_attributes end def test_read_attributes_respect_access_control privatize("title") topic = @target.new(:title => "The pros and cons of programming naked.") assert !topic.respond_to?(:title) exception = assert_raise(NoMethodError) { topic.title } assert exception.message.include?("private method") assert_equal "I'm private", topic.send(:title) end def test_write_attributes_respect_access_control privatize("title=(value)") topic = @target.new assert !topic.respond_to?(:title=) exception = assert_raise(NoMethodError) { topic.title = "Pants"} assert exception.message.include?("private method") topic.send(:title=, "Very large pants") end def test_question_attributes_respect_access_control privatize("title?") topic = @target.new(:title => "Isaac Newton's pants") assert !topic.respond_to?(:title?) exception = assert_raise(NoMethodError) { topic.title? } assert exception.message.include?("private method") assert topic.send(:title?) end def test_bulk_update_respects_access_control privatize("title=(value)") assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") } assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } end def test_bulk_update_raise_unknown_attribute_error error = assert_raises(ActiveRecord::UnknownAttributeError) { Topic.new(hello: "world") } assert_instance_of Topic, error.record assert_equal "hello", error.attribute assert_equal "unknown attribute 'hello' for Topic.", error.message end # This test is related to a bug in Ruby 2.2.1. # It can be safely removed once that bug is fixed. # # xref: https://bugs.ruby-lang.org/issues/10969 def test_bulk_does_not_raise_name_error nope rescue nil # necessary to trigger the bug assert_raises(ActiveRecord::UnknownAttributeError) { Topic.new(hello: "world") } end def test_methods_override_in_multi_level_subclass klass = Class.new(Developer) do def name "dev:#{read_attribute(:name)}" end end 2.times { klass = Class.new klass } dev = klass.new(name: 'arthurnn') dev.save! assert_equal 'dev:arthurnn', dev.reload.name end def test_global_methods_are_overwritten klass = Class.new(ActiveRecord::Base) do self.table_name = 'computers' end assert !klass.instance_method_already_implemented?(:system) computer = klass.new assert_nil computer.system end def test_global_methods_are_overwritte_when_subclassing klass = Class.new(ActiveRecord::Base) { self.abstract_class = true } subklass = Class.new(klass) do self.table_name = 'computers' end assert !klass.instance_method_already_implemented?(:system) assert !subklass.instance_method_already_implemented?(:system) computer = subklass.new assert_nil computer.system end def test_instance_method_should_be_defined_on_the_base_class subklass = Class.new(Topic) Topic.define_attribute_methods instance = subklass.new instance.id = 5 assert_equal 5, instance.id assert subklass.method_defined?(:id), "subklass is missing id method" Topic.undefine_attribute_methods assert_equal 5, instance.id assert subklass.method_defined?(:id), "subklass is missing id method" end def test_read_attribute_with_nil_should_not_asplode assert_equal nil, Topic.new.read_attribute(nil) end # If B < A, and A defines an accessor for 'foo', we don't want to override # that by defining a 'foo' method in the generated methods module for B. # (That module will be inserted between the two, e.g. [B, , A].) def test_inherited_custom_accessors klass = new_topic_like_ar_class do self.abstract_class = true def title; "omg"; end def title=(val); self.author_name = val; end end subklass = Class.new(klass) [klass, subklass].each(&:define_attribute_methods) topic = subklass.find(1) assert_equal "omg", topic.title topic.title = "lol" assert_equal "lol", topic.author_name end def test_inherited_custom_accessors_with_reserved_names klass = Class.new(ActiveRecord::Base) do self.table_name = 'computers' self.abstract_class = true def system; "omg"; end def system=(val); self.developer = val; end end subklass = Class.new(klass) [klass, subklass].each(&:define_attribute_methods) computer = subklass.find(1) assert_equal "omg", computer.system computer.developer = 99 assert_equal 99, computer.developer end def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing klass = new_topic_like_ar_class do def title super + '!' end end real_topic = topics(:first) assert_equal real_topic.title + '!', klass.find(real_topic.id).title end def test_on_the_fly_super_invokable_generated_predicate_attribute_methods_via_method_missing klass = new_topic_like_ar_class do def title? !super end end real_topic = topics(:first) assert_equal !real_topic.title?, klass.find(real_topic.id).title? end def test_calling_super_when_parent_does_not_define_method_raises_error klass = new_topic_like_ar_class do def some_method_that_is_not_on_super super end end assert_raise(NoMethodError) do klass.new.some_method_that_is_not_on_super end end def test_attribute_method? assert @target.attribute_method?(:title) assert @target.attribute_method?(:title=) assert_not @target.attribute_method?(:wibble) end def test_attribute_method_returns_false_if_table_does_not_exist @target.table_name = 'wibble' assert_not @target.attribute_method?(:title) end def test_attribute_names_on_new_record model = @target.new assert_equal @target.column_names, model.attribute_names end def test_attribute_names_on_queried_record model = @target.last! assert_equal @target.column_names, model.attribute_names end def test_attribute_names_with_custom_select model = @target.select('id').last! assert_equal ['id'], model.attribute_names # Sanity check, make sure other columns exist assert_not_equal ['id'], @target.column_names end def test_came_from_user model = @target.first assert_not model.id_came_from_user? model.id = "omg" assert model.id_came_from_user? end private def new_topic_like_ar_class(&block) klass = Class.new(ActiveRecord::Base) do self.table_name = 'topics' class_eval(&block) end assert_empty klass.generated_attribute_methods.instance_methods(false) klass end def cached_columns Topic.columns.map(&:name) end def time_related_columns_on_topic Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) } end def privatize(method_signature) @target.class_eval(<<-private_method, __FILE__, __LINE__ + 1) private def #{method_signature} "I'm private" end private_method end end rails-4.2.6/activerecord/test/cases/attribute_set_test.rb000066400000000000000000000162661266740050600236470ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class AttributeSetTest < ActiveRecord::TestCase test "building a new set from raw attributes" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) attributes = builder.build_from_database(foo: '1.1', bar: '2.2') assert_equal 1, attributes[:foo].value assert_equal 2.2, attributes[:bar].value assert_equal :foo, attributes[:foo].name assert_equal :bar, attributes[:bar].name end test "building with custom types" do builder = AttributeSet::Builder.new(foo: Type::Float.new) attributes = builder.build_from_database({ foo: '3.3', bar: '4.4' }, { bar: Type::Integer.new }) assert_equal 3.3, attributes[:foo].value assert_equal 4, attributes[:bar].value end test "[] returns a null object" do builder = AttributeSet::Builder.new(foo: Type::Float.new) attributes = builder.build_from_database(foo: '3.3') assert_equal '3.3', attributes[:foo].value_before_type_cast assert_equal nil, attributes[:bar].value_before_type_cast assert_equal :bar, attributes[:bar].name end test "duping creates a new hash and dups each attribute" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new) attributes = builder.build_from_database(foo: 1, bar: 'foo') # Ensure the type cast value is cached attributes[:foo].value attributes[:bar].value duped = attributes.dup duped.write_from_database(:foo, 2) duped[:bar].value << 'bar' assert_equal 1, attributes[:foo].value assert_equal 2, duped[:foo].value assert_equal 'foo', attributes[:bar].value assert_equal 'foobar', duped[:bar].value end test "freezing cloned set does not freeze original" do attributes = AttributeSet.new({}) clone = attributes.clone clone.freeze assert clone.frozen? assert_not attributes.frozen? end test "to_hash returns a hash of the type cast values" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) attributes = builder.build_from_database(foo: '1.1', bar: '2.2') assert_equal({ foo: 1, bar: 2.2 }, attributes.to_hash) assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h) end test "to_hash maintains order" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) attributes = builder.build_from_database(foo: '2.2', bar: '3.3') attributes[:bar] hash = attributes.to_h assert_equal [[:foo, 2], [:bar, 3.3]], hash.to_a end test "values_before_type_cast" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new) attributes = builder.build_from_database(foo: '1.1', bar: '2.2') assert_equal({ foo: '1.1', bar: '2.2' }, attributes.values_before_type_cast) end test "known columns are built with uninitialized attributes" do attributes = attributes_with_uninitialized_key assert attributes[:foo].initialized? assert_not attributes[:bar].initialized? end test "uninitialized attributes are not included in the attributes hash" do attributes = attributes_with_uninitialized_key assert_equal({ foo: 1 }, attributes.to_hash) end test "uninitialized attributes are not included in keys" do attributes = attributes_with_uninitialized_key assert_equal [:foo], attributes.keys end test "uninitialized attributes return false for key?" do attributes = attributes_with_uninitialized_key assert attributes.key?(:foo) assert_not attributes.key?(:bar) end test "unknown attributes return false for key?" do attributes = attributes_with_uninitialized_key assert_not attributes.key?(:wibble) end test "fetch_value returns the value for the given initialized attribute" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) attributes = builder.build_from_database(foo: '1.1', bar: '2.2') assert_equal 1, attributes.fetch_value(:foo) assert_equal 2.2, attributes.fetch_value(:bar) end test "fetch_value returns nil for unknown attributes" do attributes = attributes_with_uninitialized_key assert_nil attributes.fetch_value(:wibble) { "hello" } end test "fetch_value returns nil for unknown attributes when types has a default" do types = Hash.new(Type::Value.new) builder = AttributeSet::Builder.new(types) attributes = builder.build_from_database assert_nil attributes.fetch_value(:wibble) { "hello" } end test "fetch_value uses the given block for uninitialized attributes" do attributes = attributes_with_uninitialized_key value = attributes.fetch_value(:bar) { |n| n.to_s + '!' } assert_equal 'bar!', value end test "fetch_value returns nil for uninitialized attributes if no block is given" do attributes = attributes_with_uninitialized_key assert_nil attributes.fetch_value(:bar) end test "the primary_key is always initialized" do builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, :foo) attributes = builder.build_from_database assert attributes.key?(:foo) assert_equal [:foo], attributes.keys assert attributes[:foo].initialized? end class MyType def type_cast_from_user(value) return if value.nil? value + " from user" end def type_cast_from_database(value) return if value.nil? value + " from database" end end test "write_from_database sets the attribute with database typecasting" do builder = AttributeSet::Builder.new(foo: MyType.new) attributes = builder.build_from_database assert_nil attributes.fetch_value(:foo) attributes.write_from_database(:foo, "value") assert_equal "value from database", attributes.fetch_value(:foo) end test "write_from_user sets the attribute with user typecasting" do builder = AttributeSet::Builder.new(foo: MyType.new) attributes = builder.build_from_database assert_nil attributes.fetch_value(:foo) attributes.write_from_user(:foo, "value") assert_equal "value from user", attributes.fetch_value(:foo) end def attributes_with_uninitialized_key builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) builder.build_from_database(foo: '1.1') end test "freezing doesn't prevent the set from materializing" do builder = AttributeSet::Builder.new(foo: Type::String.new) attributes = builder.build_from_database(foo: "1") attributes.freeze assert_equal({ foo: "1" }, attributes.to_hash) end test "comparison for equality is correctly implemented" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new) attributes = builder.build_from_database(foo: "1", bar: "2") attributes2 = builder.build_from_database(foo: "1", bar: "2") attributes3 = builder.build_from_database(foo: "2", bar: "2") assert_equal attributes, attributes2 assert_not_equal attributes2, attributes3 end end end rails-4.2.6/activerecord/test/cases/attribute_test.rb000066400000000000000000000141121266740050600227600ustar00rootroot00000000000000require 'cases/helper' require 'minitest/mock' module ActiveRecord class AttributeTest < ActiveRecord::TestCase setup do @type = Minitest::Mock.new end teardown do assert @type.verify end test "from_database + read type casts from database" do @type.expect(:type_cast_from_database, 'type cast from database', ['a value']) attribute = Attribute.from_database(nil, 'a value', @type) type_cast_value = attribute.value assert_equal 'type cast from database', type_cast_value end test "from_user + read type casts from user" do @type.expect(:type_cast_from_user, 'type cast from user', ['a value']) attribute = Attribute.from_user(nil, 'a value', @type) type_cast_value = attribute.value assert_equal 'type cast from user', type_cast_value end test "reading memoizes the value" do @type.expect(:type_cast_from_database, 'from the database', ['whatever']) attribute = Attribute.from_database(nil, 'whatever', @type) type_cast_value = attribute.value second_read = attribute.value assert_equal 'from the database', type_cast_value assert_same type_cast_value, second_read end test "reading memoizes falsy values" do @type.expect(:type_cast_from_database, false, ['whatever']) attribute = Attribute.from_database(nil, 'whatever', @type) attribute.value attribute.value end test "read_before_typecast returns the given value" do attribute = Attribute.from_database(nil, 'raw value', @type) raw_value = attribute.value_before_type_cast assert_equal 'raw value', raw_value end test "from_database + read_for_database type casts to and from database" do @type.expect(:type_cast_from_database, 'read from database', ['whatever']) @type.expect(:type_cast_for_database, 'ready for database', ['read from database']) attribute = Attribute.from_database(nil, 'whatever', @type) type_cast_for_database = attribute.value_for_database assert_equal 'ready for database', type_cast_for_database end test "from_user + read_for_database type casts from the user to the database" do @type.expect(:type_cast_from_user, 'read from user', ['whatever']) @type.expect(:type_cast_for_database, 'ready for database', ['read from user']) attribute = Attribute.from_user(nil, 'whatever', @type) type_cast_for_database = attribute.value_for_database assert_equal 'ready for database', type_cast_for_database end test "duping dups the value" do @type.expect(:type_cast_from_database, 'type cast', ['a value']) attribute = Attribute.from_database(nil, 'a value', @type) value_from_orig = attribute.value value_from_clone = attribute.dup.value value_from_orig << ' foo' assert_equal 'type cast foo', value_from_orig assert_equal 'type cast', value_from_clone end test "duping does not dup the value if it is not dupable" do @type.expect(:type_cast_from_database, false, ['a value']) attribute = Attribute.from_database(nil, 'a value', @type) assert_same attribute.value, attribute.dup.value end test "duping does not eagerly type cast if we have not yet type cast" do attribute = Attribute.from_database(nil, 'a value', @type) attribute.dup end class MyType def type_cast_from_user(value) value + " from user" end def type_cast_from_database(value) value + " from database" end end test "with_value_from_user returns a new attribute with the value from the user" do old = Attribute.from_database(nil, "old", MyType.new) new = old.with_value_from_user("new") assert_equal "old from database", old.value assert_equal "new from user", new.value end test "with_value_from_database returns a new attribute with the value from the database" do old = Attribute.from_user(nil, "old", MyType.new) new = old.with_value_from_database("new") assert_equal "old from user", old.value assert_equal "new from database", new.value end test "uninitialized attributes yield their name if a block is given to value" do block = proc { |name| name.to_s + "!" } foo = Attribute.uninitialized(:foo, nil) bar = Attribute.uninitialized(:bar, nil) assert_equal "foo!", foo.value(&block) assert_equal "bar!", bar.value(&block) end test "uninitialized attributes have no value" do assert_nil Attribute.uninitialized(:foo, nil).value end test "attributes equal other attributes with the same constructor arguments" do first = Attribute.from_database(:foo, 1, Type::Integer.new) second = Attribute.from_database(:foo, 1, Type::Integer.new) assert_equal first, second end test "attributes do not equal attributes with different names" do first = Attribute.from_database(:foo, 1, Type::Integer.new) second = Attribute.from_database(:bar, 1, Type::Integer.new) assert_not_equal first, second end test "attributes do not equal attributes with different types" do first = Attribute.from_database(:foo, 1, Type::Integer.new) second = Attribute.from_database(:foo, 1, Type::Float.new) assert_not_equal first, second end test "attributes do not equal attributes with different values" do first = Attribute.from_database(:foo, 1, Type::Integer.new) second = Attribute.from_database(:foo, 2, Type::Integer.new) assert_not_equal first, second end test "attributes do not equal attributes of other classes" do first = Attribute.from_database(:foo, 1, Type::Integer.new) second = Attribute.from_user(:foo, 1, Type::Integer.new) assert_not_equal first, second end test "an attribute can not be mutated if it has not been read, and skips expensive calculations" do type_which_raises_from_all_methods = Object.new attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods) assert_not attribute.changed_in_place_from?("bar") end end end rails-4.2.6/activerecord/test/cases/attributes_test.rb000066400000000000000000000102371266740050600231470ustar00rootroot00000000000000require 'cases/helper' class OverloadedType < ActiveRecord::Base attribute :overloaded_float, Type::Integer.new attribute :overloaded_string_with_limit, Type::String.new(limit: 50) attribute :non_existent_decimal, Type::Decimal.new attribute :string_with_default, Type::String.new, default: 'the overloaded default' end class ChildOfOverloadedType < OverloadedType end class GrandchildOfOverloadedType < ChildOfOverloadedType attribute :overloaded_float, Type::Float.new end class UnoverloadedType < ActiveRecord::Base self.table_name = 'overloaded_types' end module ActiveRecord class CustomPropertiesTest < ActiveRecord::TestCase test "overloading types" do data = OverloadedType.new data.overloaded_float = "1.1" data.unoverloaded_float = "1.1" assert_equal 1, data.overloaded_float assert_equal 1.1, data.unoverloaded_float end test "overloaded properties save" do data = OverloadedType.new data.overloaded_float = "2.2" data.save! data.reload assert_equal 2, data.overloaded_float assert_kind_of Fixnum, OverloadedType.last.overloaded_float assert_equal 2.0, UnoverloadedType.last.overloaded_float assert_kind_of Float, UnoverloadedType.last.overloaded_float end test "properties assigned in constructor" do data = OverloadedType.new(overloaded_float: '3.3') assert_equal 3, data.overloaded_float end test "overloaded properties with limit" do assert_equal 50, OverloadedType.columns_hash['overloaded_string_with_limit'].limit assert_equal 255, UnoverloadedType.columns_hash['overloaded_string_with_limit'].limit end test "nonexistent attribute" do data = OverloadedType.new(non_existent_decimal: 1) assert_equal BigDecimal.new(1), data.non_existent_decimal assert_raise ActiveRecord::UnknownAttributeError do UnoverloadedType.new(non_existent_decimal: 1) end end test "changing defaults" do data = OverloadedType.new unoverloaded_data = UnoverloadedType.new assert_equal 'the overloaded default', data.string_with_default assert_equal 'the original default', unoverloaded_data.string_with_default end test "defaults are not touched on the columns" do assert_equal 'the original default', OverloadedType.columns_hash['string_with_default'].default end test "children inherit custom properties" do data = ChildOfOverloadedType.new(overloaded_float: '4.4') assert_equal 4, data.overloaded_float end test "children can override parents" do data = GrandchildOfOverloadedType.new(overloaded_float: '4.4') assert_equal 4.4, data.overloaded_float end test "overloading properties does not change column order" do column_names = OverloadedType.column_names assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), column_names end test "caches are cleared" do klass = Class.new(OverloadedType) assert_equal 6, klass.columns.length assert_not klass.columns_hash.key?('wibble') assert_equal 6, klass.column_types.length assert_equal 6, klass.column_defaults.length assert_not klass.column_names.include?('wibble') assert_equal 5, klass.content_columns.length klass.attribute :wibble, Type::Value.new assert_equal 7, klass.columns.length assert klass.columns_hash.key?('wibble') assert_equal 7, klass.column_types.length assert_equal 7, klass.column_defaults.length assert klass.column_names.include?('wibble') assert_equal 6, klass.content_columns.length end test "non string/integers use custom types for queries" do klass = Class.new(OverloadedType) type = Type::Value.new def type.cast_value(value) !!value end def type.type_cast_for_database(value) if value "Y" else "N" end end klass.attribute(:string_with_default, type, default: false) klass.create!(string_with_default: true) assert_equal 1, klass.where(string_with_default: true).count end end end rails-4.2.6/activerecord/test/cases/autosave_association_test.rb000066400000000000000000001421061266740050600252050ustar00rootroot00000000000000require 'cases/helper' require 'models/bird' require 'models/comment' require 'models/company' require 'models/customer' require 'models/developer' require 'models/computer' require 'models/invoice' require 'models/line_item' require 'models/order' require 'models/parrot' require 'models/person' require 'models/pirate' require 'models/post' require 'models/reader' require 'models/ship' require 'models/ship_part' require 'models/tag' require 'models/tagging' require 'models/treasure' require 'models/eye' require 'models/electron' require 'models/molecule' require 'models/member' require 'models/member_detail' require 'models/organization' class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase def test_autosave_validation person = Class.new(ActiveRecord::Base) { self.table_name = 'people' validate :should_be_cool, :on => :create def self.name; 'Person'; end private def should_be_cool unless self.first_name == 'cool' errors.add :first_name, "not cool" end end } reference = Class.new(ActiveRecord::Base) { self.table_name = "references" def self.name; 'Reference'; end belongs_to :person, autosave: true, anonymous_class: person } u = person.create!(first_name: 'cool') u.update_attributes!(first_name: 'nah') # still valid because validation only applies on 'create' assert reference.create!(person: u).persisted? end def test_should_not_add_the_same_callbacks_multiple_times_for_has_one assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship end def test_should_not_add_the_same_callbacks_multiple_times_for_belongs_to assert_no_difference_when_adding_callbacks_twice_for Ship, :pirate end def test_should_not_add_the_same_callbacks_multiple_times_for_has_many assert_no_difference_when_adding_callbacks_twice_for Pirate, :birds end def test_should_not_add_the_same_callbacks_multiple_times_for_has_and_belongs_to_many assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots end private def assert_no_difference_when_adding_callbacks_twice_for(model, association_name) reflection = model.reflect_on_association(association_name) assert_no_difference "callbacks_for_model(#{model.name}).length" do model.send(:add_autosave_association_callbacks, reflection) end end def callbacks_for_model(model) model.instance_variables.grep(/_callbacks$/).flat_map do |ivar| model.instance_variable_get(ivar) end end end class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase fixtures :companies, :accounts def test_should_save_parent_but_not_invalid_child firm = Firm.new(:name => 'GlobalMegaCorp') assert firm.valid? firm.build_account_using_primary_key assert !firm.build_account_using_primary_key.valid? assert firm.save assert !firm.account_using_primary_key.persisted? end def test_save_fails_for_invalid_has_one firm = Firm.first assert firm.valid? firm.build_account assert !firm.account.valid? assert !firm.valid? assert !firm.save assert_equal ["is invalid"], firm.errors["account"] end def test_save_succeeds_for_invalid_has_one_with_validate_false firm = Firm.first assert firm.valid? firm.build_unvalidated_account assert !firm.unvalidated_account.valid? assert firm.valid? assert firm.save end def test_build_before_child_saved firm = Firm.find(1) account = firm.build_account("credit_limit" => 1000) assert_equal account, firm.account assert !account.persisted? assert firm.save assert_equal account, firm.account assert account.persisted? end def test_build_before_either_saved firm = Firm.new("name" => "GlobalMegaCorp") firm.account = account = Account.new("credit_limit" => 1000) assert_equal account, firm.account assert !account.persisted? assert firm.save assert_equal account, firm.account assert account.persisted? end def test_assignment_before_parent_saved firm = Firm.new("name" => "GlobalMegaCorp") firm.account = a = Account.find(1) assert !firm.persisted? assert_equal a, firm.account assert firm.save assert_equal a, firm.account assert_equal a, firm.account(true) end def test_assignment_before_either_saved firm = Firm.new("name" => "GlobalMegaCorp") firm.account = a = Account.new("credit_limit" => 1000) assert !firm.persisted? assert !a.persisted? assert_equal a, firm.account assert firm.save assert firm.persisted? assert a.persisted? assert_equal a, firm.account assert_equal a, firm.account(true) end def test_not_resaved_when_unchanged firm = Firm.all.merge!(:includes => :account).first firm.name += '-changed' assert_queries(1) { firm.save! } firm = Firm.first firm.account = Account.first assert_queries(Firm.partial_writes? ? 0 : 1) { firm.save! } firm = Firm.first.dup firm.account = Account.first assert_queries(2) { firm.save! } firm = Firm.first.dup firm.account = Account.first.dup assert_queries(2) { firm.save! } end def test_callbacks_firing_order_on_create eye = Eye.create(:iris_attributes => {:color => 'honey'}) assert_equal [true, false], eye.after_create_callbacks_stack end def test_callbacks_firing_order_on_update eye = Eye.create(iris_attributes: {color: 'honey'}) eye.update(iris_attributes: {color: 'green'}) assert_equal [true, false], eye.after_update_callbacks_stack end def test_callbacks_firing_order_on_save eye = Eye.create(iris_attributes: {color: 'honey'}) assert_equal [false, false], eye.after_save_callbacks_stack eye.update(iris_attributes: {color: 'blue'}) assert_equal [false, false, false, false], eye.after_save_callbacks_stack end end class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase fixtures :companies, :posts, :tags, :taggings def test_should_save_parent_but_not_invalid_child client = Client.new(:name => 'Joe (the Plumber)') assert client.valid? client.build_firm assert !client.firm.valid? assert client.save assert !client.firm.persisted? end def test_save_fails_for_invalid_belongs_to # Oracle saves empty string as NULL therefore :message changed to one space assert log = AuditLog.create(:developer_id => 0, :message => " ") log.developer = Developer.new assert !log.developer.valid? assert !log.valid? assert !log.save assert_equal ["is invalid"], log.errors["developer"] end def test_save_succeeds_for_invalid_belongs_to_with_validate_false # Oracle saves empty string as NULL therefore :message changed to one space assert log = AuditLog.create(:developer_id => 0, :message=> " ") log.unvalidated_developer = Developer.new assert !log.unvalidated_developer.valid? assert log.valid? assert log.save end def test_assignment_before_parent_saved client = Client.first apple = Firm.new("name" => "Apple") client.firm = apple assert_equal apple, client.firm assert !apple.persisted? assert client.save assert apple.save assert apple.persisted? assert_equal apple, client.firm assert_equal apple, client.firm(true) end def test_assignment_before_either_saved final_cut = Client.new("name" => "Final Cut") apple = Firm.new("name" => "Apple") final_cut.firm = apple assert !final_cut.persisted? assert !apple.persisted? assert final_cut.save assert final_cut.persisted? assert apple.persisted? assert_equal apple, final_cut.firm assert_equal apple, final_cut.firm(true) end def test_store_two_association_with_one_save num_orders = Order.count num_customers = Customer.count order = Order.new customer1 = order.billing = Customer.new customer2 = order.shipping = Customer.new assert order.save assert_equal customer1, order.billing assert_equal customer2, order.shipping order.reload assert_equal customer1, order.billing assert_equal customer2, order.shipping assert_equal num_orders + 1, Order.count assert_equal num_customers + 2, Customer.count end def test_store_association_in_two_relations_with_one_save num_orders = Order.count num_customers = Customer.count order = Order.new customer = order.billing = order.shipping = Customer.new assert order.save assert_equal customer, order.billing assert_equal customer, order.shipping order.reload assert_equal customer, order.billing assert_equal customer, order.shipping assert_equal num_orders + 1, Order.count assert_equal num_customers + 1, Customer.count end def test_store_association_in_two_relations_with_one_save_in_existing_object num_orders = Order.count num_customers = Customer.count order = Order.create customer = order.billing = order.shipping = Customer.new assert order.save assert_equal customer, order.billing assert_equal customer, order.shipping order.reload assert_equal customer, order.billing assert_equal customer, order.shipping assert_equal num_orders + 1, Order.count assert_equal num_customers + 1, Customer.count end def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values num_orders = Order.count num_customers = Customer.count order = Order.create customer = order.billing = order.shipping = Customer.new assert order.save assert_equal customer, order.billing assert_equal customer, order.shipping order.reload customer = order.billing = order.shipping = Customer.new assert order.save order.reload assert_equal customer, order.billing assert_equal customer, order.shipping assert_equal num_orders + 1, Order.count assert_equal num_customers + 2, Customer.count end def test_store_association_with_a_polymorphic_relationship num_tagging = Tagging.count tags(:misc).create_tagging(:taggable => posts(:thinking)) assert_equal num_tagging + 1, Tagging.count end def test_build_and_then_save_parent_should_not_reload_target client = Client.first apple = client.build_firm(:name => "Apple") client.save! assert_no_queries { assert_equal apple, client.firm } end def test_validation_does_not_validate_stale_association_target valid_developer = Developer.create!(:name => "Dude", :salary => 50_000) invalid_developer = Developer.new() auditlog = AuditLog.new(:message => "foo") auditlog.developer = invalid_developer auditlog.developer_id = valid_developer.id assert auditlog.valid? end end class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase def test_invalid_adding_with_nested_attributes molecule = Molecule.new valid_electron = Electron.new(name: 'electron') invalid_electron = Electron.new molecule.electrons = [valid_electron, invalid_electron] molecule.save assert_not invalid_electron.valid? assert valid_electron.valid? assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid' end def test_valid_adding_with_nested_attributes molecule = Molecule.new valid_electron = Electron.new(name: 'electron') molecule.electrons = [valid_electron] molecule.save assert valid_electron.valid? assert molecule.persisted? assert_equal 1, molecule.electrons.count end end class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase fixtures :companies, :people def test_invalid_adding firm = Firm.find(1) assert !(firm.clients_of_firm << c = Client.new) assert !c.persisted? assert !firm.valid? assert !firm.save assert !c.persisted? end def test_invalid_adding_before_save new_firm = Firm.new("name" => "A New Firm, Inc") new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")]) assert !c.persisted? assert !c.valid? assert !new_firm.valid? assert !new_firm.save assert !c.persisted? assert !new_firm.persisted? end def test_invalid_adding_with_validate_false firm = Firm.first client = Client.new firm.unvalidated_clients_of_firm << client assert firm.valid? assert !client.valid? assert firm.save assert !client.persisted? end def test_valid_adding_with_validate_false no_of_clients = Client.count firm = Firm.first client = Client.new("name" => "Apple") assert firm.valid? assert client.valid? assert !client.persisted? firm.unvalidated_clients_of_firm << client assert firm.save assert client.persisted? assert_equal no_of_clients + 1, Client.count end def test_invalid_build new_client = companies(:first_firm).clients_of_firm.build assert !new_client.persisted? assert !new_client.valid? assert_equal new_client, companies(:first_firm).clients_of_firm.last assert !companies(:first_firm).save assert !new_client.persisted? assert_equal 2, companies(:first_firm).clients_of_firm(true).size end def test_adding_before_save no_of_firms = Firm.count no_of_clients = Client.count new_firm = Firm.new("name" => "A New Firm, Inc") c = Client.new("name" => "Apple") new_firm.clients_of_firm.push Client.new("name" => "Natural Company") assert_equal 1, new_firm.clients_of_firm.size new_firm.clients_of_firm << c assert_equal 2, new_firm.clients_of_firm.size assert_equal no_of_firms, Firm.count # Firm was not saved to database. assert_equal no_of_clients, Client.count # Clients were not saved to database. assert new_firm.save assert new_firm.persisted? assert c.persisted? assert_equal new_firm, c.firm assert_equal no_of_firms + 1, Firm.count # Firm was saved to database. assert_equal no_of_clients + 2, Client.count # Clients were saved to database. assert_equal 2, new_firm.clients_of_firm.size assert_equal 2, new_firm.clients_of_firm(true).size end def test_assign_ids firm = Firm.new("name" => "Apple") firm.client_ids = [companies(:first_client).id, companies(:second_client).id] firm.save firm.reload assert_equal 2, firm.clients.length assert firm.clients.include?(companies(:second_client)) end def test_assign_ids_for_through_a_belongs_to post = Post.new(:title => "Assigning IDs works!", :body => "You heard it here first, folks!") post.person_ids = [people(:david).id, people(:michael).id] post.save post.reload assert_equal 2, post.people.length assert post.people.include?(people(:david)) end def test_build_before_save company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") } assert !company.clients_of_firm.loaded? company.name += '-changed' assert_queries(2) { assert company.save } assert new_client.persisted? assert_equal 3, company.clients_of_firm(true).size end def test_build_many_before_save company = companies(:first_firm) assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) } company.name += '-changed' assert_queries(3) { assert company.save } assert_equal 4, company.clients_of_firm(true).size end def test_build_via_block_before_save company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } } assert !company.clients_of_firm.loaded? company.name += '-changed' assert_queries(2) { assert company.save } assert new_client.persisted? assert_equal 3, company.clients_of_firm(true).size end def test_build_many_via_block_before_save company = companies(:first_firm) assert_no_queries(ignore_none: false) do company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client| client.name = "changed" end end company.name += '-changed' assert_queries(3) { assert company.save } assert_equal 4, company.clients_of_firm(true).size end def test_replace_on_new_object firm = Firm.new("name" => "New Firm") firm.clients = [companies(:second_client), Client.new("name" => "New Client")] assert firm.save firm.reload assert_equal 2, firm.clients.length assert firm.clients.include?(Client.find_by_name("New Client")) end end class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase def test_autosave_new_record_on_belongs_to_can_be_disabled_per_relationship new_account = Account.new("credit_limit" => 1000) new_firm = Firm.new("name" => "some firm") assert !new_firm.persisted? new_account.firm = new_firm new_account.save! assert new_firm.persisted? new_account = Account.new("credit_limit" => 1000) new_autosaved_firm = Firm.new("name" => "some firm") assert !new_autosaved_firm.persisted? new_account.unautosaved_firm = new_autosaved_firm new_account.save! assert !new_autosaved_firm.persisted? end def test_autosave_new_record_on_has_one_can_be_disabled_per_relationship firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) assert !account.persisted? firm.account = account firm.save! assert account.persisted? firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) firm.unautosaved_account = account assert !account.persisted? firm.unautosaved_account = account firm.save! assert !account.persisted? end def test_autosave_new_record_on_has_many_can_be_disabled_per_relationship firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) assert !account.persisted? firm.accounts << account firm.save! assert account.persisted? firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) assert !account.persisted? firm.unautosaved_accounts << account firm.save! assert !account.persisted? end def test_autosave_new_record_with_after_create_callback post = PostWithAfterCreateCallback.new(title: 'Captain Murphy', body: 'is back') post.comments.build(body: 'foo') post.save! assert_not_nil post.author_id end end class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false setup do @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end teardown do # We are running without transactional fixtures and need to cleanup. Bird.delete_all Parrot.delete_all @ship.delete @pirate.delete end # reload def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload @pirate.mark_for_destruction @pirate.ship.mark_for_destruction assert !@pirate.reload.marked_for_destruction? assert !@pirate.ship.reload.marked_for_destruction? end # has_one def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal assert !@pirate.ship.marked_for_destruction? @pirate.ship.mark_for_destruction id = @pirate.ship.id assert @pirate.ship.marked_for_destruction? assert Ship.find_by_id(id) @pirate.save assert_nil @pirate.reload.ship assert_nil Ship.find_by_id(id) end def test_should_skip_validation_on_a_child_association_if_marked_for_destruction @pirate.ship.name = '' assert !@pirate.valid? @pirate.ship.mark_for_destruction @pirate.ship.expects(:valid?).never assert_difference('Ship.count', -1) { @pirate.save! } end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice @pirate.ship.mark_for_destruction assert @pirate.save class << @pirate.ship def destroy; raise "Should not be called" end end assert @pirate.save end def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_child # Stub the save method of the @pirate.ship instance to destroy and then raise an exception class << @pirate.ship def save(*args) super destroy raise 'Oh noes!' end end @ship.pirate.catchphrase = "Changed Catchphrase" assert_raise(RuntimeError) { assert !@pirate.save } assert_not_nil @pirate.reload.ship end def test_should_save_changed_has_one_changed_object_if_child_is_saved @pirate.ship.name = "NewName" assert @pirate.save assert_equal "NewName", @pirate.ship.reload.name end def test_should_not_save_changed_has_one_unchanged_object_if_child_is_saved @pirate.ship.expects(:save).never assert @pirate.save end # belongs_to def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal assert !@ship.pirate.marked_for_destruction? @ship.pirate.mark_for_destruction id = @ship.pirate.id assert @ship.pirate.marked_for_destruction? assert Pirate.find_by_id(id) @ship.save assert_nil @ship.reload.pirate assert_nil Pirate.find_by_id(id) end def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction @ship.pirate.catchphrase = '' assert !@ship.valid? @ship.pirate.mark_for_destruction @ship.pirate.expects(:valid?).never assert_difference('Pirate.count', -1) { @ship.save! } end def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice @ship.pirate.mark_for_destruction assert @ship.save class << @ship.pirate def destroy; raise "Should not be called" end end assert @ship.save end def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent # Stub the save method of the @ship.pirate instance to destroy and then raise an exception class << @ship.pirate def save(*args) super destroy raise 'Oh noes!' end end @ship.pirate.catchphrase = "Changed Catchphrase" assert_raise(RuntimeError) { assert !@ship.save } assert_not_nil @ship.reload.pirate end def test_should_save_changed_child_objects_if_parent_is_saved @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?") @parrot = @pirate.parrots.create!(:name => 'Posideons Killer') @parrot.name = "NewName" @ship.save assert_equal 'NewName', @parrot.reload.name end def test_should_destroy_has_many_as_part_of_the_save_transaction_if_they_were_marked_for_destruction 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") } assert !@pirate.birds.any? { |child| child.marked_for_destruction? } @pirate.birds.each { |child| child.mark_for_destruction } klass = @pirate.birds.first.class ids = @pirate.birds.map(&:id) assert @pirate.birds.all? { |child| child.marked_for_destruction? } ids.each { |id| assert klass.find_by_id(id) } @pirate.save assert @pirate.reload.birds.empty? ids.each { |id| assert_nil klass.find_by_id(id) } end def test_should_not_resave_destroyed_association @pirate.birds.create!(name: :parrot) @pirate.birds.first.destroy @pirate.save! assert @pirate.reload.birds.empty? end def test_should_skip_validation_on_has_many_if_marked_for_destruction 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") } @pirate.birds.each { |bird| bird.name = '' } assert !@pirate.valid? @pirate.birds.each do |bird| bird.mark_for_destruction bird.expects(:valid?).never end assert_difference("Bird.count", -2) { @pirate.save! } end def test_should_skip_validation_on_has_many_if_destroyed @pirate.birds.create!(:name => "birds_1") @pirate.birds.each { |bird| bird.name = '' } assert !@pirate.valid? @pirate.birds.each { |bird| bird.destroy } assert @pirate.valid? end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many @pirate.birds.create!(:name => "birds_1") @pirate.birds.each { |bird| bird.mark_for_destruction } assert @pirate.save @pirate.birds.each { |bird| bird.expects(:destroy).never } assert @pirate.save end def test_should_rollback_destructions_if_an_exception_occurred_while_saving_has_many 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") } before = @pirate.birds.map { |c| c.mark_for_destruction ; c } # Stub the destroy method of the second child to raise an exception class << before.last def destroy(*args) super raise 'Oh noes!' end end assert_raise(RuntimeError) { assert !@pirate.save } assert_equal before, @pirate.reload.birds end def test_when_new_record_a_child_marked_for_destruction_should_not_affect_other_records_from_saving @pirate = @ship.build_pirate(:catchphrase => "Arr' now I shall keep me eye on you matey!") # new record 3.times { |i| @pirate.birds.build(:name => "birds_#{i}") } @pirate.birds[1].mark_for_destruction @pirate.save! assert_equal 2, @pirate.birds.reload.length end def test_should_save_new_record_that_has_same_value_as_existing_record_marked_for_destruction_on_field_that_has_unique_index Bird.connection.add_index :birds, :name, unique: true 3.times { |i| @pirate.birds.create(name: "unique_birds_#{i}") } @pirate.birds[0].mark_for_destruction @pirate.birds.build(name: @pirate.birds[0].name) @pirate.save! assert_equal 3, @pirate.birds.reload.length ensure Bird.connection.remove_index :birds, column: :name end # Add and remove callbacks tests for association collections. %w{ method proc }.each do |callback_type| define_method("test_should_run_add_callback_#{callback_type}s_for_has_many") do association_name_with_callbacks = "birds_with_#{callback_type}_callbacks" pirate = Pirate.new(:catchphrase => "Arr") pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed") expected = [ "before_adding_#{callback_type}_bird_", "after_adding_#{callback_type}_bird_" ] assert_equal expected, pirate.ship_log end define_method("test_should_run_remove_callback_#{callback_type}s_for_has_many") do association_name_with_callbacks = "birds_with_#{callback_type}_callbacks" @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed") @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction } child_id = @pirate.send(association_name_with_callbacks).first.id @pirate.ship_log.clear @pirate.save expected = [ "before_removing_#{callback_type}_bird_#{child_id}", "after_removing_#{callback_type}_bird_#{child_id}" ] assert_equal expected, @pirate.ship_log end end def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") } assert !@pirate.parrots.any? { |parrot| parrot.marked_for_destruction? } @pirate.parrots.each { |parrot| parrot.mark_for_destruction } assert_no_difference "Parrot.count" do @pirate.save end assert @pirate.reload.parrots.empty? join_records = Pirate.connection.select_all("SELECT * FROM parrots_pirates WHERE pirate_id = #{@pirate.id}") assert join_records.empty? end def test_should_skip_validation_on_habtm_if_marked_for_destruction 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") } @pirate.parrots.each { |parrot| parrot.name = '' } assert !@pirate.valid? @pirate.parrots.each do |parrot| parrot.mark_for_destruction parrot.expects(:valid?).never end @pirate.save! assert @pirate.reload.parrots.empty? end def test_should_skip_validation_on_habtm_if_destroyed @pirate.parrots.create!(:name => "parrots_1") @pirate.parrots.each { |parrot| parrot.name = '' } assert !@pirate.valid? @pirate.parrots.each { |parrot| parrot.destroy } assert @pirate.valid? end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm @pirate.parrots.create!(:name => "parrots_1") @pirate.parrots.each { |parrot| parrot.mark_for_destruction } assert @pirate.save Pirate.transaction do assert_queries(0) do assert @pirate.save end end end def test_should_rollback_destructions_if_an_exception_occurred_while_saving_habtm 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") } before = @pirate.parrots.map { |c| c.mark_for_destruction ; c } class << @pirate.association(:parrots) def destroy(*args) super raise 'Oh noes!' end end assert_raise(RuntimeError) { assert !@pirate.save } assert_equal before, @pirate.reload.parrots end # Add and remove callbacks tests for association collections. %w{ method proc }.each do |callback_type| define_method("test_should_run_add_callback_#{callback_type}s_for_habtm") do association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks" pirate = Pirate.new(:catchphrase => "Arr") pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed") expected = [ "before_adding_#{callback_type}_parrot_", "after_adding_#{callback_type}_parrot_" ] assert_equal expected, pirate.ship_log end define_method("test_should_run_remove_callback_#{callback_type}s_for_habtm") do association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks" @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed") @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction } child_id = @pirate.send(association_name_with_callbacks).first.id @pirate.ship_log.clear @pirate.save expected = [ "before_removing_#{callback_type}_parrot_#{child_id}", "after_removing_#{callback_type}_parrot_#{child_id}" ] assert_equal expected, @pirate.ship_log end end end class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end def test_should_still_work_without_an_associated_model @ship.destroy @pirate.reload.catchphrase = "Arr" @pirate.save assert_equal 'Arr', @pirate.reload.catchphrase end def test_should_automatically_save_the_associated_model @pirate.ship.name = 'The Vile Insanity' @pirate.save assert_equal 'The Vile Insanity', @pirate.reload.ship.name end def test_changed_for_autosave_should_handle_cycles @ship.pirate = @pirate assert_queries(0) { @ship.save! } @parrot = @pirate.parrots.create(name: "some_name") @parrot.name="changed_name" assert_queries(1) { @ship.save! } assert_queries(0) { @ship.save! } end def test_should_automatically_save_bang_the_associated_model @pirate.ship.name = 'The Vile Insanity' @pirate.save! assert_equal 'The Vile Insanity', @pirate.reload.ship.name end def test_should_automatically_validate_the_associated_model @pirate.ship.name = '' assert @pirate.invalid? assert @pirate.errors[:"ship.name"].any? end def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid @pirate.ship.name = nil @pirate.catchphrase = nil assert @pirate.invalid? assert @pirate.errors[:"ship.name"].any? assert @pirate.errors[:catchphrase].any? end def test_should_not_ignore_different_error_messages_on_the_same_attribute old_validators = Ship._validators.deep_dup old_callbacks = Ship._validate_callbacks.deep_dup Ship.validates_format_of :name, :with => /\w/ @pirate.ship.name = "" @pirate.catchphrase = nil assert @pirate.invalid? assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"] ensure Ship._validators = old_validators if old_validators Ship._validate_callbacks = old_callbacks if old_callbacks end def test_should_still_allow_to_bypass_validations_on_the_associated_model @pirate.catchphrase = '' @pirate.ship.name = '' @pirate.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name] else assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name] end end def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth 2.times { |i| @pirate.ship.parts.create!(:name => "part #{i}") } @pirate.catchphrase = '' @pirate.ship.name = '' @pirate.ship.parts.each { |part| part.name = '' } @pirate.save(:validate => false) values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)] # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil, nil, nil], values else assert_equal ['', '', '', ''], values end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @pirate.ship.name = '' assert_raise(ActiveRecord::RecordInvalid) do @pirate.save! end end def test_should_not_save_and_return_false_if_a_callback_cancelled_saving pirate = Pirate.new(:catchphrase => 'Arr') ship = pirate.build_ship(:name => 'The Vile Insanity') ship.cancel_save_from_callback = true assert_no_difference 'Pirate.count' do assert_no_difference 'Ship.count' do assert !pirate.save end end end def test_should_rollback_any_changes_if_an_exception_occurred_while_saving before = [@pirate.catchphrase, @pirate.ship.name] @pirate.catchphrase = 'Arr' @pirate.ship.name = 'The Vile Insanity' # Stub the save method of the @pirate.ship instance to raise an exception class << @pirate.ship def save(*args) super raise 'Oh noes!' end end assert_raise(RuntimeError) { assert !@pirate.save } assert_equal before, [@pirate.reload.catchphrase, @pirate.ship.name] end def test_should_not_load_the_associated_model assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! } end def test_mark_for_destruction_is_ignored_without_autosave_true ship = ShipWithoutNestedAttributes.new(name: "The Black Flag") ship.parts.build.mark_for_destruction assert_not ship.valid? end end class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super organization = Organization.create @member = Member.create MemberDetail.create(organization: organization, member: @member) end def test_should_not_has_one_through_model class << @member.organization def save(*args) super raise 'Oh noes!' end end assert_nothing_raised { @member.save } end end class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @ship = Ship.create(:name => 'Nights Dirty Lightning') @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?") end def test_should_still_work_without_an_associated_model @pirate.destroy @ship.reload.name = "The Vile Insanity" @ship.save assert_equal 'The Vile Insanity', @ship.reload.name end def test_should_automatically_save_the_associated_model @ship.pirate.catchphrase = 'Arr' @ship.save assert_equal 'Arr', @ship.reload.pirate.catchphrase end def test_should_automatically_save_bang_the_associated_model @ship.pirate.catchphrase = 'Arr' @ship.save! assert_equal 'Arr', @ship.reload.pirate.catchphrase end def test_should_automatically_validate_the_associated_model @ship.pirate.catchphrase = '' assert @ship.invalid? assert @ship.errors[:"pirate.catchphrase"].any? end def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid @ship.name = nil @ship.pirate.catchphrase = nil assert @ship.invalid? assert @ship.errors[:name].any? assert @ship.errors[:"pirate.catchphrase"].any? end def test_should_still_allow_to_bypass_validations_on_the_associated_model @ship.pirate.catchphrase = '' @ship.name = '' @ship.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase] else assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @ship.pirate.catchphrase = '' assert_raise(ActiveRecord::RecordInvalid) do @ship.save! end end def test_should_not_save_and_return_false_if_a_callback_cancelled_saving ship = Ship.new(:name => 'The Vile Insanity') pirate = ship.build_pirate(:catchphrase => 'Arr') pirate.cancel_save_from_callback = true assert_no_difference 'Ship.count' do assert_no_difference 'Pirate.count' do assert !ship.save end end end def test_should_rollback_any_changes_if_an_exception_occurred_while_saving before = [@ship.pirate.catchphrase, @ship.name] @ship.pirate.catchphrase = 'Arr' @ship.name = 'The Vile Insanity' # Stub the save method of the @ship.pirate instance to raise an exception class << @ship.pirate def save(*args) super raise 'Oh noes!' end end assert_raise(RuntimeError) { assert !@ship.save } assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name] end def test_should_not_load_the_associated_model assert_queries(1) { @ship.name = 'The Vile Insanity'; @ship.save! } end end module AutosaveAssociationOnACollectionAssociationTests def test_should_automatically_save_the_associated_models new_names = ['Grace OMalley', 'Privateers Greed'] @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } @pirate.save assert_equal new_names, @pirate.reload.send(@association_name).map(&:name) end def test_should_automatically_save_bang_the_associated_models new_names = ['Grace OMalley', 'Privateers Greed'] @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } @pirate.save! assert_equal new_names, @pirate.reload.send(@association_name).map(&:name) end def test_should_automatically_validate_the_associated_models @pirate.send(@association_name).each { |child| child.name = '' } assert !@pirate.valid? assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] assert @pirate.errors[@association_name].empty? end def test_should_not_use_default_invalid_error_on_associated_models @pirate.send(@association_name).build(:name => '') assert !@pirate.valid? assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] assert @pirate.errors[@association_name].empty? end def test_should_default_invalid_error_from_i18n I18n.backend.store_translations(:en, activerecord: {errors: { models: { @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } } }}) @pirate.send(@association_name).build(name: '') assert !@pirate.valid? assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"] assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages assert @pirate.errors[@association_name].empty? ensure I18n.backend = I18n::Backend::Simple.new end def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid @pirate.send(@association_name).each { |child| child.name = '' } @pirate.catchphrase = nil assert !@pirate.valid? assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] assert @pirate.errors[:catchphrase].any? end def test_should_allow_to_bypass_validations_on_the_associated_models_on_update @pirate.catchphrase = '' @pirate.send(@association_name).each { |child| child.name = '' } assert @pirate.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil, nil], [ @pirate.reload.catchphrase, @pirate.send(@association_name).first.name, @pirate.send(@association_name).last.name ] else assert_equal ['', '', ''], [ @pirate.reload.catchphrase, @pirate.send(@association_name).first.name, @pirate.send(@association_name).last.name ] end end def test_should_validation_the_associated_models_on_create assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do 2.times { @pirate.send(@association_name).build } @pirate.save end end def test_should_allow_to_bypass_validations_on_the_associated_models_on_create assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", 2) do 2.times { @pirate.send(@association_name).build } @pirate.save(:validate => false) end end def test_should_not_save_and_return_false_if_a_callback_cancelled_saving_in_either_create_or_update @pirate.catchphrase = 'Changed' @child_1.name = 'Changed' @child_1.cancel_save_from_callback = true assert !@pirate.save assert_equal "Don' botharrr talkin' like one, savvy?", @pirate.reload.catchphrase assert_equal "Posideons Killer", @child_1.reload.name new_pirate = Pirate.new(:catchphrase => 'Arr') new_child = new_pirate.send(@association_name).build(:name => 'Grace OMalley') new_child.cancel_save_from_callback = true assert_no_difference 'Pirate.count' do assert_no_difference "#{new_child.class.name}.count" do assert !new_pirate.save end end end def test_should_rollback_any_changes_if_an_exception_occurred_while_saving before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)] new_names = ['Grace OMalley', 'Privateers Greed'] @pirate.catchphrase = 'Arr' @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } # Stub the save method of the first child instance to raise an exception class << @pirate.send(@association_name).first def save(*args) super raise 'Oh noes!' end end assert_raise(RuntimeError) { assert !@pirate.save } assert_equal before, [@pirate.reload.catchphrase, *@pirate.send(@association_name).map(&:name)] end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @pirate.send(@association_name).each { |child| child.name = '' } assert_raise(ActiveRecord::RecordInvalid) do @pirate.save! end end def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! } @pirate.send(@association_name).load_target assert_queries(3) do @pirate.catchphrase = 'Yarr' new_names = ['Grace OMalley', 'Privateers Greed'] @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } @pirate.save! end end end class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @association_name = :birds @associated_model_name = :bird @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @child_1 = @pirate.birds.create(:name => 'Posideons Killer') @child_2 = @pirate.birds.create(:name => 'Killer bandita Dionne') end include AutosaveAssociationOnACollectionAssociationTests end class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @association_name = :autosaved_parrots @associated_model_name = :parrot @habtm = true @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") @child_1 = @pirate.parrots.create(name: 'Posideons Killer') @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') end include AutosaveAssociationOnACollectionAssociationTests end class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @association_name = :parrots @associated_model_name = :parrot @habtm = true @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") @child_1 = @pirate.parrots.create(name: 'Posideons Killer') @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') end include AutosaveAssociationOnACollectionAssociationTests end class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @pirate.birds.create(:name => 'cookoo') end test "should automatically validate associations" do assert @pirate.valid? @pirate.birds.each { |bird| bird.name = '' } assert !@pirate.valid? end end class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @pirate.create_ship(:name => 'titanic') super end test "should automatically validate associations with :validate => true" do assert @pirate.valid? @pirate.ship.name = '' assert !@pirate.valid? end test "should not automatically add validate associations without :validate => true" do assert @pirate.valid? @pirate.non_validated_ship.name = '' assert @pirate.valid? end end class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") end test "should automatically validate associations with :validate => true" do assert @pirate.valid? @pirate.parrot = Parrot.new(:name => '') assert !@pirate.valid? end test "should not automatically validate associations without :validate => true" do assert @pirate.valid? @pirate.non_validated_parrot = Parrot.new(:name => '') assert @pirate.valid? end end class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") end test "should automatically validate associations with :validate => true" do assert @pirate.valid? @pirate.parrots = [ Parrot.new(:name => 'popuga') ] @pirate.parrots.each { |parrot| parrot.name = '' } assert !@pirate.valid? end test "should not automatically validate associations without :validate => true" do assert @pirate.valid? @pirate.non_validated_parrots = [ Parrot.new(:name => 'popuga') ] @pirate.non_validated_parrots.each { |parrot| parrot.name = '' } assert @pirate.valid? end end class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup super @pirate = Pirate.new end test "should generate validation methods for has_many associations" do assert_respond_to @pirate, :validate_associated_records_for_birds end test "should generate validation methods for has_one associations with :validate => true" do assert_respond_to @pirate, :validate_associated_records_for_ship end test "should not generate validation methods for has_one associations without :validate => true" do assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_ship) end test "should generate validation methods for belongs_to associations with :validate => true" do assert_respond_to @pirate, :validate_associated_records_for_parrot end test "should not generate validation methods for belongs_to associations without :validate => true" do assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrot) end test "should generate validation methods for HABTM associations with :validate => true" do assert_respond_to @pirate, :validate_associated_records_for_parrots end end class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase def test_autosave_with_touch_should_not_raise_system_stack_error invoice = Invoice.create assert_nothing_raised { invoice.line_items.create(:amount => 10) } end end rails-4.2.6/activerecord/test/cases/base_test.rb000066400000000000000000001472321266740050600217010ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'active_support/concurrency/latch' require 'models/post' require 'models/author' require 'models/topic' require 'models/reply' require 'models/category' require 'models/company' require 'models/customer' require 'models/developer' require 'models/computer' require 'models/project' require 'models/default' require 'models/auto_id' require 'models/boolean' require 'models/column_name' require 'models/subscriber' require 'models/keyboard' require 'models/comment' require 'models/minimalistic' require 'models/warehouse_thing' require 'models/parrot' require 'models/person' require 'models/edge' require 'models/joke' require 'models/bird' require 'models/car' require 'models/bulb' require 'rexml/document' class FirstAbstractClass < ActiveRecord::Base self.abstract_class = true end class SecondAbstractClass < FirstAbstractClass self.abstract_class = true end class Photo < SecondAbstractClass; end class Category < ActiveRecord::Base; end class Categorization < ActiveRecord::Base; end class Smarts < ActiveRecord::Base; end class CreditCard < ActiveRecord::Base class PinNumber < ActiveRecord::Base class CvvCode < ActiveRecord::Base; end class SubCvvCode < CvvCode; end end class SubPinNumber < PinNumber; end class Brand < Category; end end class MasterCreditCard < ActiveRecord::Base; end class Post < ActiveRecord::Base; end class Computer < ActiveRecord::Base; end class NonExistentTable < ActiveRecord::Base; end class TestOracleDefault < ActiveRecord::Base; end class ReadonlyTitlePost < Post attr_readonly :title end class Weird < ActiveRecord::Base; end class Boolean < ActiveRecord::Base def has_fun super end end class LintTest < ActiveRecord::TestCase include ActiveModel::Lint::Tests class LintModel < ActiveRecord::Base; end def setup @model = LintModel.new end end class BasicsTest < ActiveRecord::TestCase fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts def test_column_names_are_escaped conn = ActiveRecord::Base.connection classname = conn.class.name[/[^:]*$/] badchar = { 'SQLite3Adapter' => '"', 'MysqlAdapter' => '`', 'Mysql2Adapter' => '`', 'PostgreSQLAdapter' => '"', 'OracleAdapter' => '"', }.fetch(classname) { raise "need a bad char for #{classname}" } quoted = conn.quote_column_name "foo#{badchar}bar" if current_adapter?(:OracleAdapter) # Oracle does not allow double quotes in table and column names at all # therefore quoting removes them assert_equal("#{badchar}foobar#{badchar}", quoted) else assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted) end end def test_columns_should_obey_set_primary_key pk = Subscriber.columns_hash[Subscriber.primary_key] assert_equal 'nick', pk.name, 'nick should be primary key' end def test_primary_key_with_no_id assert_nil Edge.primary_key end unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter) def test_limit_with_comma assert Topic.limit("1,2").to_a end end def test_limit_without_comma assert_equal 1, Topic.limit("1").to_a.length assert_equal 1, Topic.limit(1).to_a.length end def test_limit_should_take_value_from_latest_limit assert_equal 1, Topic.limit(2).limit(1).to_a.length end def test_invalid_limit assert_raises(ArgumentError) do Topic.limit("asdfadf").to_a end end def test_limit_should_sanitize_sql_injection_for_limit_without_commas assert_raises(ArgumentError) do Topic.limit("1 select * from schema").to_a end end def test_limit_should_sanitize_sql_injection_for_limit_with_commas assert_raises(ArgumentError) do Topic.limit("1, 7 procedure help()").to_a end end unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_limit_should_allow_sql_literal assert_equal 1, Topic.limit(Arel.sql('2-1')).to_a.length end end def test_select_symbol topic_ids = Topic.select(:id).map(&:id).sort assert_equal Topic.pluck(:id).sort, topic_ids end def test_table_exists assert !NonExistentTable.table_exists? assert Topic.table_exists? end def test_preserving_date_objects # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) assert_kind_of( Date, Topic.find(1).last_read, "The last_read attribute should be of the Date class" ) end def test_previously_changed topic = Topic.first topic.title = '<3<3<3' assert_equal({}, topic.previous_changes) topic.save! expected = ["The First Topic", "<3<3<3"] assert_equal(expected, topic.previous_changes['title']) end def test_previously_changed_dup topic = Topic.first topic.title = '<3<3<3' topic.save! t2 = topic.dup assert_equal(topic.previous_changes, t2.previous_changes) topic.title = "lolwut" topic.save! assert_not_equal(topic.previous_changes, t2.previous_changes) end def test_preserving_time_objects assert_kind_of( Time, Topic.find(1).bonus_time, "The bonus_time attribute should be of the Time class" ) assert_kind_of( Time, Topic.find(1).written_on, "The written_on attribute should be of the Time class" ) # For adapters which support microsecond resolution. if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) || mysql_56? assert_equal 11, Topic.find(1).written_on.sec assert_equal 223300, Topic.find(1).written_on.usec assert_equal 9900, Topic.find(2).written_on.usec assert_equal 129346, Topic.find(3).written_on.usec end end def test_preserving_time_objects_with_local_time_conversion_to_default_timezone_utc with_env_tz 'America/New_York' do with_timezone_config default: :utc do time = Time.local(2000) topic = Topic.create('written_on' => time) saved_time = Topic.find(topic.id).reload.written_on assert_equal time, saved_time assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a assert_equal [0, 0, 5, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a end end end def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc with_env_tz 'America/New_York' do with_timezone_config default: :utc do Time.use_zone 'Central Time (US & Canada)' do time = Time.zone.local(2000) topic = Topic.create('written_on' => time) saved_time = Topic.find(topic.id).reload.written_on assert_equal time, saved_time assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a assert_equal [0, 0, 6, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a end end end end def test_preserving_time_objects_with_utc_time_conversion_to_default_timezone_local with_env_tz 'America/New_York' do with_timezone_config default: :local do time = Time.utc(2000) topic = Topic.create('written_on' => time) saved_time = Topic.find(topic.id).reload.written_on assert_equal time, saved_time assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a end end end def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local with_env_tz 'America/New_York' do with_timezone_config default: :local do Time.use_zone 'Central Time (US & Canada)' do time = Time.zone.local(2000) topic = Topic.create('written_on' => time) saved_time = Topic.find(topic.id).reload.written_on assert_equal time, saved_time assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a assert_equal [0, 0, 1, 1, 1, 2000, 6, 1, false, "EST"], saved_time.to_a end end end end def test_custom_mutator topic = Topic.find(1) # This mutator is protected in the class definition topic.send(:approved=, true) assert topic.instance_variable_get("@custom_approved") end def test_initialize_with_attributes topic = Topic.new({ "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23" }) assert_equal("initialized from attributes", topic.title) end def test_initialize_with_invalid_attribute Topic.new({ "title" => "test", "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"}) rescue ActiveRecord::MultiparameterAssignmentErrors => ex assert_equal(1, ex.errors.size) assert_equal("last_read", ex.errors[0].attribute) end def test_create_after_initialize_without_block cb = CustomBulb.create(:name => 'Dude') assert_equal('Dude', cb.name) assert_equal(true, cb.frickinawesome) end def test_create_after_initialize_with_block cb = CustomBulb.create {|c| c.name = 'Dude' } assert_equal('Dude', cb.name) assert_equal(true, cb.frickinawesome) end def test_create_after_initialize_with_array_param cbs = CustomBulb.create([{ name: 'Dude' }, { name: 'Bob' }]) assert_equal 'Dude', cbs[0].name assert_equal 'Bob', cbs[1].name assert cbs[0].frickinawesome assert !cbs[1].frickinawesome end def test_load topics = Topic.all.merge!(:order => 'id').to_a assert_equal(5, topics.size) assert_equal(topics(:first).title, topics.first.title) end def test_load_with_condition topics = Topic.all.merge!(:where => "author_name = 'Mary'").to_a assert_equal(1, topics.size) assert_equal(topics(:second).title, topics.first.title) end GUESSED_CLASSES = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard] def test_table_name_guesses assert_equal "topics", Topic.table_name assert_equal "categories", Category.table_name assert_equal "smarts", Smarts.table_name assert_equal "credit_cards", CreditCard.table_name assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name assert_equal "credit_card_pin_number_cvv_codes", CreditCard::PinNumber::CvvCode.table_name assert_equal "credit_card_pin_numbers", CreditCard::SubPinNumber.table_name assert_equal "categories", CreditCard::Brand.table_name assert_equal "master_credit_cards", MasterCreditCard.table_name ensure GUESSED_CLASSES.each(&:reset_table_name) end def test_singular_table_name_guesses ActiveRecord::Base.pluralize_table_names = false GUESSED_CLASSES.each(&:reset_table_name) assert_equal "category", Category.table_name assert_equal "smarts", Smarts.table_name assert_equal "credit_card", CreditCard.table_name assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name assert_equal "credit_card_pin_number_cvv_code", CreditCard::PinNumber::CvvCode.table_name assert_equal "credit_card_pin_number", CreditCard::SubPinNumber.table_name assert_equal "category", CreditCard::Brand.table_name assert_equal "master_credit_card", MasterCreditCard.table_name ensure ActiveRecord::Base.pluralize_table_names = true GUESSED_CLASSES.each(&:reset_table_name) end def test_table_name_guesses_with_prefixes_and_suffixes ActiveRecord::Base.table_name_prefix = "test_" Category.reset_table_name assert_equal "test_categories", Category.table_name ActiveRecord::Base.table_name_suffix = "_test" Category.reset_table_name assert_equal "test_categories_test", Category.table_name ActiveRecord::Base.table_name_prefix = "" Category.reset_table_name assert_equal "categories_test", Category.table_name ActiveRecord::Base.table_name_suffix = "" Category.reset_table_name assert_equal "categories", Category.table_name ensure ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" GUESSED_CLASSES.each(&:reset_table_name) end def test_singular_table_name_guesses_with_prefixes_and_suffixes ActiveRecord::Base.pluralize_table_names = false ActiveRecord::Base.table_name_prefix = "test_" Category.reset_table_name assert_equal "test_category", Category.table_name ActiveRecord::Base.table_name_suffix = "_test" Category.reset_table_name assert_equal "test_category_test", Category.table_name ActiveRecord::Base.table_name_prefix = "" Category.reset_table_name assert_equal "category_test", Category.table_name ActiveRecord::Base.table_name_suffix = "" Category.reset_table_name assert_equal "category", Category.table_name ensure ActiveRecord::Base.pluralize_table_names = true ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" GUESSED_CLASSES.each(&:reset_table_name) end def test_table_name_guesses_with_inherited_prefixes_and_suffixes GUESSED_CLASSES.each(&:reset_table_name) CreditCard.table_name_prefix = "test_" CreditCard.reset_table_name Category.reset_table_name assert_equal "test_credit_cards", CreditCard.table_name assert_equal "categories", Category.table_name CreditCard.table_name_suffix = "_test" CreditCard.reset_table_name Category.reset_table_name assert_equal "test_credit_cards_test", CreditCard.table_name assert_equal "categories", Category.table_name CreditCard.table_name_prefix = "" CreditCard.reset_table_name Category.reset_table_name assert_equal "credit_cards_test", CreditCard.table_name assert_equal "categories", Category.table_name CreditCard.table_name_suffix = "" CreditCard.reset_table_name Category.reset_table_name assert_equal "credit_cards", CreditCard.table_name assert_equal "categories", Category.table_name ensure CreditCard.table_name_prefix = "" CreditCard.table_name_suffix = "" GUESSED_CLASSES.each(&:reset_table_name) end def test_singular_table_name_guesses_for_individual_table Post.pluralize_table_names = false Post.reset_table_name assert_equal "post", Post.table_name assert_equal "categories", Category.table_name ensure Post.pluralize_table_names = true Post.reset_table_name end if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_update_all_with_order_and_limit assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!') end end def test_null_fields assert_nil Topic.find(1).parent_id assert_nil Topic.create("title" => "Hey you").parent_id end def test_default_values topic = Topic.new assert topic.approved? assert_nil topic.written_on assert_nil topic.bonus_time assert_nil topic.last_read topic.save topic = Topic.find(topic.id) assert topic.approved? assert_nil topic.last_read # Oracle has some funky default handling, so it requires a bit of # extra testing. See ticket #2788. if current_adapter?(:OracleAdapter) test = TestOracleDefault.new assert_equal "X", test.test_char assert_equal "hello", test.test_string assert_equal 3, test.test_int end end # Oracle does not have a TIME datatype. unless current_adapter?(:OracleAdapter) def test_utc_as_time_zone with_timezone_config default: :utc do attributes = { "bonus_time" => "5:42:00AM" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time end end def test_utc_as_time_zone_and_new with_timezone_config default: :utc do attributes = { "bonus_time(1i)"=>"2000", "bonus_time(2i)"=>"1", "bonus_time(3i)"=>"1", "bonus_time(4i)"=>"10", "bonus_time(5i)"=>"35", "bonus_time(6i)"=>"50" } topic = Topic.new(attributes) assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time end end end def test_default_values_on_empty_strings topic = Topic.new topic.approved = nil topic.last_read = nil topic.save topic = Topic.find(topic.id) assert_nil topic.last_read assert_nil topic.approved end def test_equality assert_equal Topic.find(1), Topic.find(2).topic end def test_find_by_slug assert_equal Topic.find('1-meowmeow'), Topic.find(1) end def test_find_by_slug_with_array assert_equal Topic.find(['1-meowmeow', '2-hello']), Topic.find([1, 2]) end def test_find_by_slug_with_range assert_equal Topic.where(id: '1-meowmeow'..'2-hello'), Topic.where(id: 1..2) end def test_equality_of_new_records assert_not_equal Topic.new, Topic.new assert_equal false, Topic.new == Topic.new end def test_equality_of_destroyed_records topic_1 = Topic.new(:title => 'test_1') topic_1.save topic_2 = Topic.find(topic_1.id) topic_1.destroy assert_equal topic_1, topic_2 assert_equal topic_2, topic_1 end def test_equality_with_blank_ids one = Subscriber.new(:id => '') two = Subscriber.new(:id => '') assert_equal one, two end def test_equality_of_relation_and_collection_proxy car = Car.create! car.bulbs.build car.save assert car.bulbs == Bulb.where(car_id: car.id), 'CollectionProxy should be comparable with Relation' assert Bulb.where(car_id: car.id) == car.bulbs, 'Relation should be comparable with CollectionProxy' end def test_equality_of_relation_and_array car = Car.create! car.bulbs.build car.save assert Bulb.where(car_id: car.id) == car.bulbs.to_a, 'Relation should be comparable with Array' end def test_equality_of_relation_and_association_relation car = Car.create! car.bulbs.build car.save assert_equal Bulb.where(car_id: car.id), car.bulbs.includes(:car), 'Relation should be comparable with AssociationRelation' assert_equal car.bulbs.includes(:car), Bulb.where(car_id: car.id), 'AssociationRelation should be comparable with Relation' end def test_equality_of_collection_proxy_and_association_relation car = Car.create! car.bulbs.build car.save assert_equal car.bulbs, car.bulbs.includes(:car), 'CollectionProxy should be comparable with AssociationRelation' assert_equal car.bulbs.includes(:car), car.bulbs, 'AssociationRelation should be comparable with CollectionProxy' end def test_hashing assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end def test_successful_comparison_of_like_class_records topic_1 = Topic.create! topic_2 = Topic.create! assert_equal [topic_2, topic_1].sort, [topic_1, topic_2] end def test_failed_comparison_of_unlike_class_records assert_raises ArgumentError do [ topics(:first), posts(:welcome) ].sort end end def test_create_without_prepared_statement topic = Topic.connection.unprepared_statement do Topic.create(:title => 'foo') end assert_equal topic, Topic.find(topic.id) end def test_destroy_without_prepared_statement topic = Topic.create(title: 'foo') Topic.connection.unprepared_statement do Topic.find(topic.id).destroy end assert_equal nil, Topic.find_by_id(topic.id) end def test_comparison_with_different_objects topic = Topic.create category = Category.create(:name => "comparison") assert_nil topic <=> category end def test_comparison_with_different_objects_in_array topic = Topic.create assert_raises(ArgumentError) do [1, topic].sort end end def test_readonly_attributes assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable") post.reload assert_equal "cannot change this", post.title post.update(title: "try to change", body: "changed") post.reload assert_equal "cannot change this", post.title assert_equal "changed", post.body end def test_unicode_column_name Weird.reset_column_information weird = Weird.create(:ãªã¾ãˆ => 'ãŸã“焼ãä»®é¢') assert_equal 'ãŸã“焼ãä»®é¢', weird.ãªã¾ãˆ end unless current_adapter?(:PostgreSQLAdapter) def test_respect_internal_encoding old_default_internal = Encoding.default_internal silence_warnings { Encoding.default_internal = "EUC-JP" } Weird.reset_column_information assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq ensure silence_warnings { Encoding.default_internal = old_default_internal } Weird.reset_column_information end end def test_non_valid_identifier_column_name weird = Weird.create('a$b' => 'value') weird.reload assert_equal 'value', weird.send('a$b') assert_equal 'value', weird.read_attribute('a$b') weird.update_columns('a$b' => 'value2') weird.reload assert_equal 'value2', weird.send('a$b') assert_equal 'value2', weird.read_attribute('a$b') end def test_group_weirds_by_from Weird.create('a$b' => 'value', :from => 'aaron') count = Weird.group(Weird.arel_table[:from]).count assert_equal 1, count['aaron'] end def test_attributes_on_dummy_time # Oracle does not have a TIME datatype. return true if current_adapter?(:OracleAdapter) with_timezone_config default: :local do attributes = { "bonus_time" => "5:42:00AM" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time end end def test_attributes_on_dummy_time_with_invalid_time # Oracle does not have a TIME datatype. return true if current_adapter?(:OracleAdapter) attributes = { "bonus_time" => "not a time" } topic = Topic.find(1) topic.attributes = attributes assert_nil topic.bonus_time end def test_boolean b_nil = Boolean.create({ "value" => nil }) nil_id = b_nil.id b_false = Boolean.create({ "value" => false }) false_id = b_false.id b_true = Boolean.create({ "value" => true }) true_id = b_true.id b_nil = Boolean.find(nil_id) assert_nil b_nil.value b_false = Boolean.find(false_id) assert !b_false.value? b_true = Boolean.find(true_id) assert b_true.value? end def test_boolean_without_questionmark b_true = Boolean.create({ "value" => true }) true_id = b_true.id subclass = Class.new(Boolean).find true_id superclass = Boolean.find true_id assert_equal superclass.read_attribute(:has_fun), subclass.read_attribute(:has_fun) end def test_boolean_cast_from_string b_blank = Boolean.create({ "value" => "" }) blank_id = b_blank.id b_false = Boolean.create({ "value" => "0" }) false_id = b_false.id b_true = Boolean.create({ "value" => "1" }) true_id = b_true.id b_blank = Boolean.find(blank_id) assert_nil b_blank.value b_false = Boolean.find(false_id) assert !b_false.value? b_true = Boolean.find(true_id) assert b_true.value? end def test_new_record_returns_boolean assert_equal false, Topic.new.persisted? assert_equal true, Topic.find(1).persisted? end def test_dup topic = Topic.find(1) duped_topic = nil assert_nothing_raised { duped_topic = topic.dup } assert_equal topic.title, duped_topic.title assert !duped_topic.persisted? # test if the attributes have been duped topic.title = "a" duped_topic.title = "b" assert_equal "a", topic.title assert_equal "b", duped_topic.title # test if the attribute values have been duped duped_topic = topic.dup duped_topic.title.replace "c" assert_equal "a", topic.title # test if attributes set as part of after_initialize are duped correctly assert_equal topic.author_email_address, duped_topic.author_email_address # test if saved clone object differs from original duped_topic.save assert duped_topic.persisted? assert_not_equal duped_topic.id, topic.id duped_topic.reload assert_equal("c", duped_topic.title) end DeveloperSalary = Struct.new(:amount) def test_dup_with_aggregate_of_same_name_as_attribute developer_with_aggregate = Class.new(ActiveRecord::Base) do self.table_name = 'developers' composed_of :salary, :class_name => 'BasicsTest::DeveloperSalary', :mapping => [%w(salary amount)] end dev = developer_with_aggregate.find(1) assert_kind_of DeveloperSalary, dev.salary dup = nil assert_nothing_raised { dup = dev.dup } assert_kind_of DeveloperSalary, dup.salary assert_equal dev.salary.amount, dup.salary.amount assert !dup.persisted? # test if the attributes have been dupd original_amount = dup.salary.amount dev.salary.amount = 1 assert_equal original_amount, dup.salary.amount assert dup.save assert dup.persisted? assert_not_equal dup.id, dev.id end def test_dup_does_not_copy_associations author = authors(:david) assert_not_equal [], author.posts author.send(:clear_association_cache) author_dup = author.dup assert_equal [], author_dup.posts end def test_clone_preserves_subtype clone = nil assert_nothing_raised { clone = Company.find(3).clone } assert_kind_of Client, clone end def test_clone_of_new_object_with_defaults developer = Developer.new assert !developer.name_changed? assert !developer.salary_changed? cloned_developer = developer.clone assert !cloned_developer.name_changed? assert !cloned_developer.salary_changed? end def test_clone_of_new_object_marks_attributes_as_dirty developer = Developer.new :name => 'Bjorn', :salary => 100000 assert developer.name_changed? assert developer.salary_changed? cloned_developer = developer.clone assert cloned_developer.name_changed? assert cloned_developer.salary_changed? end def test_clone_of_new_object_marks_as_dirty_only_changed_attributes developer = Developer.new :name => 'Bjorn' assert developer.name_changed? # obviously assert !developer.salary_changed? # attribute has non-nil default value, so treated as not changed cloned_developer = developer.clone assert cloned_developer.name_changed? assert !cloned_developer.salary_changed? # ... and cloned instance should behave same end def test_dup_of_saved_object_marks_attributes_as_dirty developer = Developer.create! :name => 'Bjorn', :salary => 100000 assert !developer.name_changed? assert !developer.salary_changed? cloned_developer = developer.dup assert cloned_developer.name_changed? # both attributes differ from defaults assert cloned_developer.salary_changed? end def test_dup_of_saved_object_marks_as_dirty_only_changed_attributes developer = Developer.create! :name => 'Bjorn' assert !developer.name_changed? # both attributes of saved object should be treated as not changed assert !developer.salary_changed? cloned_developer = developer.dup assert cloned_developer.name_changed? # ... but on cloned object should be assert !cloned_developer.salary_changed? # ... BUT salary has non-nil default which should be treated as not changed on cloned instance end def test_bignum company = Company.find(1) company.rating = 2147483647 company.save company = Company.find(1) assert_equal 2147483647, company.rating end # TODO: extend defaults tests to other databases! if current_adapter?(:PostgreSQLAdapter) def test_default with_timezone_config default: :local do default = Default.new # fixed dates / times assert_equal Date.new(2004, 1, 1), default.fixed_date assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time # char types assert_equal 'Y', default.char1 assert_equal 'a varchar field', default.char2 assert_equal 'a text field', default.char3 end end class Geometric < ActiveRecord::Base; end def test_geometric_content # accepted format notes: # ()'s aren't required # values can be a mix of float or integer g = Geometric.new( :a_point => '(5.0, 6.1)', #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql :a_line_segment => '(2.0, 3), (5.5, 7.0)', :a_box => '2.0, 3, 5.5, 7.0', :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', :a_circle => '<(5.3, 10.4), 2>' ) assert g.save # Reload and check that we have all the geometric attributes. h = Geometric.find(g.id) assert_equal [5.0, 6.1], h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon assert_equal '<(5.3,10.4),2>', h.a_circle # use a geometric function to test for an open path objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id] assert_equal true, objs[0].isopen # test alternate formats when defining the geometric types g = Geometric.new( :a_point => '5.0, 6.1', #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql :a_line_segment => '((2.0, 3), (5.5, 7.0))', :a_box => '(2.0, 3), (5.5, 7.0)', :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0', :a_circle => '((5.3, 10.4), 2)' ) assert g.save # Reload and check that we have all the geometric attributes. h = Geometric.find(g.id) assert_equal [5.0, 6.1], h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon assert_equal '<(5.3,10.4),2>', h.a_circle # use a geometric function to test for an closed path objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id] assert_equal true, objs[0].isclosed # test native ruby formats when defining the geometric types g = Geometric.new( :a_point => [5.0, 6.1], #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql :a_line_segment => '((2.0, 3), (5.5, 7.0))', :a_box => '(2.0, 3), (5.5, 7.0)', :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0', :a_circle => '((5.3, 10.4), 2)' ) assert g.save # Reload and check that we have all the geometric attributes. h = Geometric.find(g.id) assert_equal [5.0, 6.1], h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon assert_equal '<(5.3,10.4),2>', h.a_circle end end class NumericData < ActiveRecord::Base self.table_name = 'numeric_data' attribute :my_house_population, Type::Integer.new attribute :atoms_in_universe, Type::Integer.new end def test_big_decimal_conditions m = NumericData.new( :bank_balance => 1586.43, :big_bank_balance => BigDecimal("1000234000567.95"), :world_population => 6000000000, :my_house_population => 3 ) assert m.save assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count end def test_numeric_fields m = NumericData.new( :bank_balance => 1586.43, :big_bank_balance => BigDecimal("1000234000567.95"), :world_population => 6000000000, :my_house_population => 3 ) assert m.save m1 = NumericData.find(m.id) assert_not_nil m1 # As with migration_test.rb, we should make world_population >= 2**62 # to cover 64-bit platforms and test it is a Bignum, but the main thing # is that it's an Integer. assert_kind_of Integer, m1.world_population assert_equal 6000000000, m1.world_population assert_kind_of Fixnum, m1.my_house_population assert_equal 3, m1.my_house_population assert_kind_of BigDecimal, m1.bank_balance assert_equal BigDecimal("1586.43"), m1.bank_balance assert_kind_of BigDecimal, m1.big_bank_balance assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance end def test_numeric_fields_with_scale m = NumericData.new( :bank_balance => 1586.43122334, :big_bank_balance => BigDecimal("234000567.952344"), :world_population => 6000000000, :my_house_population => 3 ) assert m.save m1 = NumericData.find(m.id) assert_not_nil m1 # As with migration_test.rb, we should make world_population >= 2**62 # to cover 64-bit platforms and test it is a Bignum, but the main thing # is that it's an Integer. assert_kind_of Integer, m1.world_population assert_equal 6000000000, m1.world_population assert_kind_of Fixnum, m1.my_house_population assert_equal 3, m1.my_house_population assert_kind_of BigDecimal, m1.bank_balance assert_equal BigDecimal("1586.43"), m1.bank_balance assert_kind_of BigDecimal, m1.big_bank_balance assert_equal BigDecimal("234000567.95"), m1.big_bank_balance end def test_auto_id auto = AutoId.new auto.save assert(auto.id > 0) end def test_sql_injection_via_find assert_raise(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do Topic.find("123456 OR id > 0") end end def test_column_name_properly_quoted col_record = ColumnName.new col_record.references = 40 assert col_record.save col_record.references = 41 assert col_record.save assert_not_nil c2 = ColumnName.find(col_record.id) assert_equal(41, c2.references) end def test_quoting_arrays replies = Reply.all.merge!(:where => [ "id IN (?)", topics(:first).replies.collect(&:id) ]).to_a assert_equal topics(:first).replies.size, replies.size replies = Reply.all.merge!(:where => [ "id IN (?)", [] ]).to_a assert_equal 0, replies.size end def test_quote author_name = "\\ \001 ' \n \\n \"" topic = Topic.create('author_name' => author_name) assert_equal author_name, Topic.find(topic.id).author_name end def test_toggle_attribute assert !topics(:first).approved? topics(:first).toggle!(:approved) assert topics(:first).approved? topic = topics(:first) topic.toggle(:approved) assert !topic.approved? topic.reload assert topic.approved? end def test_reload t1 = Topic.find(1) t2 = Topic.find(1) t1.title = "something else" t1.save t2.reload assert_equal t1.title, t2.title end def test_reload_with_exclusive_scope dev = DeveloperCalledDavid.first dev.update!(name: "NotDavid" ) assert_equal dev, dev.reload end def test_switching_between_table_name assert_difference("GoodJoke.count") do Joke.table_name = "cold_jokes" Joke.create Joke.table_name = "funny_jokes" Joke.create end end def test_clear_cash_when_setting_table_name Joke.table_name = "cold_jokes" before_columns = Joke.columns before_seq = Joke.sequence_name Joke.table_name = "funny_jokes" after_columns = Joke.columns after_seq = Joke.sequence_name assert_not_equal before_columns, after_columns assert_not_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil? end def test_dont_clear_sequence_name_when_setting_explicitly Joke.sequence_name = "black_jokes_seq" Joke.table_name = "cold_jokes" before_seq = Joke.sequence_name Joke.table_name = "funny_jokes" after_seq = Joke.sequence_name assert_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil? ensure Joke.reset_sequence_name end def test_dont_clear_inheritance_column_when_setting_explicitly Joke.inheritance_column = "my_type" before_inherit = Joke.inheritance_column Joke.reset_column_information after_inherit = Joke.inheritance_column assert_equal before_inherit, after_inherit unless before_inherit.blank? && after_inherit.blank? end def test_set_table_name_symbol_converted_to_string Joke.table_name = :cold_jokes assert_equal 'cold_jokes', Joke.table_name end def test_quoted_table_name_after_set_table_name klass = Class.new(ActiveRecord::Base) klass.table_name = "foo" assert_equal "foo", klass.table_name assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name klass.table_name = "bar" assert_equal "bar", klass.table_name assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name end def test_set_table_name_with_inheritance k = Class.new( ActiveRecord::Base ) def k.name; "Foo"; end def k.table_name; super + "ks"; end assert_equal "foosks", k.table_name end def test_sequence_name_with_abstract_class ak = Class.new(ActiveRecord::Base) ak.abstract_class = true k = Class.new(ak) k.table_name = "projects" orig_name = k.sequence_name skip "sequences not supported by db" unless orig_name assert_equal k.reset_sequence_name, orig_name end def test_count_with_join res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'" res2 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count assert_equal res, res2 res3 = nil assert_nothing_raised do res3 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count end assert_equal res, res3 res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" res5 = nil assert_nothing_raised do res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count end assert_equal res4, res5 res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" res7 = nil assert_nothing_raised do res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").distinct.count end assert_equal res6, res7 end def test_no_limit_offset assert_nothing_raised do Developer.all.merge!(:offset => 2).to_a end end def test_find_last last = Developer.last assert_equal last, Developer.all.merge!(:order => 'id desc').first end def test_last assert_equal Developer.all.merge!(:order => 'id desc').first, Developer.last end def test_all developers = Developer.all assert_kind_of ActiveRecord::Relation, developers assert_equal Developer.all, developers end def test_all_with_conditions assert_equal Developer.all.merge!(:order => 'id desc').to_a, Developer.order('id desc').to_a end def test_find_ordered_last last = Developer.all.merge!(:order => 'developers.salary ASC').last assert_equal last, Developer.all.merge!(:order => 'developers.salary ASC').to_a.last end def test_find_reverse_ordered_last last = Developer.all.merge!(:order => 'developers.salary DESC').last assert_equal last, Developer.all.merge!(:order => 'developers.salary DESC').to_a.last end def test_find_multiple_ordered_last last = Developer.all.merge!(:order => 'developers.name, developers.salary DESC').last assert_equal last, Developer.all.merge!(:order => 'developers.name, developers.salary DESC').to_a.last end def test_find_keeps_multiple_order_values combined = Developer.all.merge!(:order => 'developers.name, developers.salary').to_a assert_equal combined, Developer.all.merge!(:order => ['developers.name', 'developers.salary']).to_a end def test_find_keeps_multiple_group_values combined = Developer.all.merge!(:group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at, developers.created_on, developers.updated_on').to_a assert_equal combined, Developer.all.merge!(:group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at', 'developers.created_on', 'developers.updated_on']).to_a end def test_find_symbol_ordered_last last = Developer.all.merge!(:order => :salary).last assert_equal last, Developer.all.merge!(:order => :salary).to_a.last end def test_abstract_class assert !ActiveRecord::Base.abstract_class? assert LoosePerson.abstract_class? assert !LooseDescendant.abstract_class? end def test_abstract_class_table_name assert_nil AbstractCompany.table_name end def test_descends_from_active_record assert !ActiveRecord::Base.descends_from_active_record? # Abstract subclass of AR::Base. assert LoosePerson.descends_from_active_record? # Concrete subclass of an abstract class. assert LooseDescendant.descends_from_active_record? # Concrete subclass of AR::Base. assert TightPerson.descends_from_active_record? # Concrete subclass of a concrete class but has no type column. assert TightDescendant.descends_from_active_record? # Concrete subclass of AR::Base. assert Post.descends_from_active_record? # Abstract subclass of a concrete class which has a type column. # This is pathological, as you'll never have Sub < Abstract < Concrete. assert !StiPost.descends_from_active_record? # Concrete subclasses an abstract class which has a type column. assert !SubStiPost.descends_from_active_record? end def test_find_on_abstract_base_class_doesnt_use_type_condition old_class = LooseDescendant Object.send :remove_const, :LooseDescendant descendant = old_class.create! :first_name => 'bob' assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}" ensure unless Object.const_defined?(:LooseDescendant) Object.const_set :LooseDescendant, old_class end end def test_assert_queries query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' } assert_queries(2) { 2.times { query.call } } assert_queries 1, &query assert_no_queries { assert true } end def test_benchmark_with_log_level original_logger = ActiveRecord::Base.logger log = StringIO.new ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::WARN ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count } ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count } ActiveRecord::Base.benchmark("Error Topic Count", :level => :error) { Topic.count } assert_no_match(/Debug Topic Count/, log.string) assert_match(/Warn Topic Count/, log.string) assert_match(/Error Topic Count/, log.string) ensure ActiveRecord::Base.logger = original_logger end def test_benchmark_with_use_silence original_logger = ActiveRecord::Base.logger log = StringIO.new ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" } assert_match(/Quiet/, log.string) ensure ActiveRecord::Base.logger = original_logger end def test_compute_type_success assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author') end def test_compute_type_nonexistent_constant e = assert_raises NameError do ActiveRecord::Base.send :compute_type, 'NonexistentModel' end assert_equal 'uninitialized constant ActiveRecord::Base::NonexistentModel', e.message assert_equal 'ActiveRecord::Base::NonexistentModel', e.name end def test_compute_type_no_method_error ActiveSupport::Dependencies.stubs(:safe_constantize).raises(NoMethodError) assert_raises NoMethodError do ActiveRecord::Base.send :compute_type, 'InvalidModel' end end def test_compute_type_on_undefined_method error = nil begin Class.new(Author) do alias_method :foo, :bar end rescue => e error = e end ActiveSupport::Dependencies.stubs(:safe_constantize).raises(e) exception = assert_raises NameError do ActiveRecord::Base.send :compute_type, 'InvalidModel' end assert_equal error.message, exception.message end def test_compute_type_argument_error ActiveSupport::Dependencies.stubs(:safe_constantize).raises(ArgumentError) assert_raises ArgumentError do ActiveRecord::Base.send :compute_type, 'InvalidModel' end end def test_clear_cache! # preheat cache c1 = Post.connection.schema_cache.columns('posts') ActiveRecord::Base.clear_cache! c2 = Post.connection.schema_cache.columns('posts') c1.each_with_index do |v, i| assert_not_same v, c2[i] end assert_equal c1, c2 end def test_current_scope_is_reset Object.const_set :UnloadablePost, Class.new(ActiveRecord::Base) UnloadablePost.send(:current_scope=, UnloadablePost.all) UnloadablePost.unloadable assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost") ActiveSupport::Dependencies.remove_unloadable_constants! assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost") ensure Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost) end def test_marshal_round_trip expected = posts(:welcome) marshalled = Marshal.dump(expected) actual = Marshal.load(marshalled) assert_equal expected.attributes, actual.attributes end def test_marshal_new_record_round_trip marshalled = Marshal.dump(Post.new) post = Marshal.load(marshalled) assert post.new_record?, "should be a new record" end def test_marshalling_with_associations post = Post.new post.comments.build marshalled = Marshal.dump(post) post = Marshal.load(marshalled) assert_equal 1, post.comments.length end if Process.respond_to?(:fork) && !in_memory_db? def test_marshal_between_processes # Define a new model to ensure there are no caches if self.class.const_defined?("Post", false) flunk "there should be no post constant" end self.class.const_set("Post", Class.new(ActiveRecord::Base) { has_many :comments }) rd, wr = IO.pipe rd.binmode wr.binmode ActiveRecord::Base.connection_handler.clear_all_connections! fork do rd.close post = Post.new post.comments.build wr.write Marshal.dump(post) wr.close end wr.close assert Marshal.load rd.read rd.close end end def test_marshalling_new_record_round_trip_with_associations post = Post.new post.comments.build post = Marshal.load(Marshal.dump(post)) assert post.new_record?, "should be a new record" end def test_attribute_names assert_equal ["id", "type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"], Company.attribute_names end def test_attribute_names_on_table_not_exists assert_equal [], NonExistentTable.attribute_names end def test_attribute_names_on_abstract_class assert_equal [], AbstractCompany.attribute_names end def test_touch_should_raise_error_on_a_new_object company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals") assert_raises(ActiveRecord::ActiveRecordError) do company.touch :updated_at end end def test_uniq_delegates_to_scoped scope = stub Bird.stubs(:all).returns(mock(:uniq => scope)) assert_equal scope, Bird.uniq end def test_distinct_delegates_to_scoped scope = stub Bird.stubs(:all).returns(mock(:distinct => scope)) assert_equal scope, Bird.distinct end def test_table_name_with_2_abstract_subclasses assert_equal "photos", Photo.table_name end def test_column_types_typecast topic = Topic.first assert_not_equal 't.lo', topic.author_name attrs = topic.attributes.dup attrs.delete 'id' typecast = Class.new(ActiveRecord::Type::Value) { def type_cast value "t.lo" end } types = { 'author_name' => typecast.new } topic = Topic.instantiate(attrs, types) assert_equal 't.lo', topic.author_name end def test_typecasting_aliases assert_equal 10, Topic.select('10 as tenderlove').first.tenderlove end def test_slice company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals") hash = company.slice(:name, :rating, "arbitrary_method") assert_equal hash[:name], company.name assert_equal hash['name'], company.name assert_equal hash[:rating], company.rating assert_equal hash['arbitrary_method'], company.arbitrary_method assert_equal hash[:arbitrary_method], company.arbitrary_method assert_nil hash[:firm_name] assert_nil hash['firm_name'] end def test_default_values_are_deeply_dupped company = Company.new company.description << "foo" assert_equal "", Company.new.description end test "scoped can take a values hash" do klass = Class.new(ActiveRecord::Base) assert_equal ['foo'], klass.all.merge!(select: 'foo').select_values end test "connection_handler can be overridden" do klass = Class.new(ActiveRecord::Base) orig_handler = klass.connection_handler new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new thread_connection_handler = nil t = Thread.new do klass.connection_handler = new_handler thread_connection_handler = klass.connection_handler end t.join assert_equal klass.connection_handler, orig_handler assert_equal thread_connection_handler, new_handler end test "new threads get default the default connection handler" do klass = Class.new(ActiveRecord::Base) orig_handler = klass.connection_handler handler = nil t = Thread.new do handler = klass.connection_handler end t.join assert_equal handler, orig_handler assert_equal klass.connection_handler, orig_handler assert_equal klass.default_connection_handler, orig_handler end test "changing a connection handler in a main thread does not poison the other threads" do klass = Class.new(ActiveRecord::Base) orig_handler = klass.connection_handler new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new after_handler = nil latch1 = ActiveSupport::Concurrency::Latch.new latch2 = ActiveSupport::Concurrency::Latch.new t = Thread.new do klass.connection_handler = new_handler latch1.release latch2.await after_handler = klass.connection_handler end latch1.await klass.connection_handler = orig_handler latch2.release t.join assert_equal after_handler, new_handler assert_equal orig_handler, klass.connection_handler end # Note: This is a performance optimization for Array#uniq and Hash#[] with # AR::Base objects. If the future has made this irrelevant, feel free to # delete this. test "records without an id have unique hashes" do assert_not_equal Post.new.hash, Post.new.hash end test "resetting column information doesn't remove attribute methods" do topic = topics(:first) assert_not topic.id_changed? Topic.reset_column_information assert_not topic.id_changed? end end rails-4.2.6/activerecord/test/cases/batches_test.rb000066400000000000000000000144261266740050600223760ustar00rootroot00000000000000require 'cases/helper' require 'models/post' require 'models/subscriber' class EachTest < ActiveRecord::TestCase fixtures :posts, :subscribers def setup @posts = Post.order("id asc") @total = Post.count Post.count('id') # preheat arel's table cache end def test_each_should_execute_one_query_per_batch assert_queries(@total + 1) do Post.find_each(:batch_size => 1) do |post| assert_kind_of Post, post end end end def test_each_should_not_return_query_chain_and_execute_only_one_query assert_queries(1) do result = Post.find_each(:batch_size => 100000){ } assert_nil result end end def test_each_should_return_an_enumerator_if_no_block_is_present assert_queries(1) do Post.find_each(:batch_size => 100000).with_index do |post, index| assert_kind_of Post, post assert_kind_of Integer, index end end end if Enumerator.method_defined? :size def test_each_should_return_a_sized_enumerator assert_equal 11, Post.find_each(:batch_size => 1).size assert_equal 5, Post.find_each(:batch_size => 2, :start => 7).size assert_equal 11, Post.find_each(:batch_size => 10_000).size end end def test_each_enumerator_should_execute_one_query_per_batch assert_queries(@total + 1) do Post.find_each(:batch_size => 1).with_index do |post, index| assert_kind_of Post, post assert_kind_of Integer, index end end end def test_each_should_raise_if_select_is_set_without_id assert_raise(RuntimeError) do Post.select(:title).find_each(batch_size: 1) { |post| flunk "should not call this block" } end end def test_each_should_execute_if_id_is_in_select assert_queries(6) do Post.select("id, title, type").find_each(:batch_size => 2) do |post| assert_kind_of Post, post end end end def test_warn_if_limit_scope_is_set ActiveRecord::Base.logger.expects(:warn) Post.limit(1).find_each { |post| post } end def test_warn_if_order_scope_is_set ActiveRecord::Base.logger.expects(:warn) Post.order("title").find_each { |post| post } end def test_logger_not_required previous_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = nil assert_nothing_raised do Post.limit(1).find_each { |post| post } end ensure ActiveRecord::Base.logger = previous_logger end def test_find_in_batches_should_return_batches assert_queries(@total + 1) do Post.find_in_batches(:batch_size => 1) do |batch| assert_kind_of Array, batch assert_kind_of Post, batch.first end end end def test_find_in_batches_should_start_from_the_start_option assert_queries(@total) do Post.find_in_batches(:batch_size => 1, :start => 2) do |batch| assert_kind_of Array, batch assert_kind_of Post, batch.first end end end def test_find_in_batches_shouldnt_execute_query_unless_needed assert_queries(2) do Post.find_in_batches(:batch_size => @total) {|batch| assert_kind_of Array, batch } end assert_queries(1) do Post.find_in_batches(:batch_size => @total + 1) {|batch| assert_kind_of Array, batch } end end def test_find_in_batches_should_quote_batch_order c = Post.connection assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do Post.find_in_batches(:batch_size => 1) do |batch| assert_kind_of Array, batch assert_kind_of Post, batch.first end end end def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified not_a_post = "not a post" not_a_post.stubs(:id).raises(StandardError, "not_a_post had #id called on it") assert_nothing_raised do Post.find_in_batches(:batch_size => 1) do |batch| assert_kind_of Array, batch assert_kind_of Post, batch.first batch.map! { not_a_post } end end end def test_find_in_batches_should_ignore_the_order_default_scope # First post is with title scope first_post = PostWithDefaultScope.first posts = [] PostWithDefaultScope.find_in_batches do |batch| posts.concat(batch) end # posts.first will be ordered using id only. Title order scope should not apply here assert_not_equal first_post, posts.first assert_equal posts(:welcome), posts.first end def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort posts = [] SpecialPostWithDefaultScope.find_in_batches do |batch| posts.concat(batch) end assert_equal special_posts_ids, posts.map(&:id) end def test_find_in_batches_should_not_modify_passed_options assert_nothing_raised do Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){} end end def test_find_in_batches_should_use_any_column_as_primary_key nick_order_subscribers = Subscriber.order('nick asc') start_nick = nick_order_subscribers.second.nick subscribers = [] Subscriber.find_in_batches(:batch_size => 1, :start => start_nick) do |batch| subscribers.concat(batch) end assert_equal nick_order_subscribers[1..-1].map(&:id), subscribers.map(&:id) end def test_find_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified assert_queries(Subscriber.count + 1) do Subscriber.find_each(:batch_size => 1) do |subscriber| assert_kind_of Subscriber, subscriber end end end def test_find_in_batches_should_return_an_enumerator enum = nil assert_queries(0) do enum = Post.find_in_batches(:batch_size => 1) end assert_queries(4) do enum.first(4) do |batch| assert_kind_of Array, batch assert_kind_of Post, batch.first end end end if Enumerator.method_defined? :size def test_find_in_batches_should_return_a_sized_enumerator assert_equal 11, Post.find_in_batches(:batch_size => 1).size assert_equal 6, Post.find_in_batches(:batch_size => 2).size assert_equal 4, Post.find_in_batches(:batch_size => 2, :start => 4).size assert_equal 4, Post.find_in_batches(:batch_size => 3).size assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size end end end rails-4.2.6/activerecord/test/cases/binary_test.rb000066400000000000000000000025221266740050600222430ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" # Without using prepared statements, it makes no sense to test # BLOB data with DB2, because the length of a statement # is limited to 32KB. unless current_adapter?(:DB2Adapter) require 'models/binary' class BinaryTest < ActiveRecord::TestCase FIXTURES = %w(flowers.jpg example.log test.txt) def test_mixed_encoding str = "\x80" str.force_encoding('ASCII-8BIT') binary = Binary.new :name => 'ã„ãŸã ãã¾ã™ï¼', :data => str binary.save! binary.reload assert_equal str, binary.data name = binary.name # MySQL adapter doesn't properly encode things, so we have to do it if current_adapter?(:MysqlAdapter) name.force_encoding(Encoding::UTF_8) end assert_equal 'ã„ãŸã ãã¾ã™ï¼', name end def test_load_save Binary.delete_all FIXTURES.each do |filename| data = File.read(ASSETS_ROOT + "/#{filename}") data.force_encoding('ASCII-8BIT') data.freeze bin = Binary.new(:data => data) assert_equal data, bin.data, 'Newly assigned data differs from original' bin.save! assert_equal data, bin.data, 'Data differs from original after save' assert_equal data, bin.reload.data, 'Reloaded data differs from original' end end end end rails-4.2.6/activerecord/test/cases/bind_parameter_test.rb000066400000000000000000000051571266740050600237420ustar00rootroot00000000000000require 'cases/helper' require 'models/topic' require 'models/author' require 'models/post' module ActiveRecord class BindParameterTest < ActiveRecord::TestCase fixtures :topics, :authors, :posts class LogListener attr_accessor :calls def initialize @calls = [] end def call(*args) calls << args end end def setup super @connection = ActiveRecord::Base.connection @subscriber = LogListener.new @pk = Topic.columns_hash[Topic.primary_key] @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) end teardown do ActiveSupport::Notifications.unsubscribe(@subscription) end if ActiveRecord::Base.connection.supports_statement_cache? def test_bind_from_join_in_subquery subquery = Author.joins(:thinking_posts).where(name: 'David') scope = Author.from(subquery, 'authors').where(id: 1) assert_equal 1, scope.count end def test_binds_are_logged sub = @connection.substitute_at(@pk) binds = [[@pk, 1]] sql = "select * from topics where id = #{sub.to_sql}" @connection.exec_query(sql, 'SQL', binds) message = @subscriber.calls.find { |args| args[4][:sql] == sql } assert_equal binds, message[4][:binds] end def test_binds_are_logged_after_type_cast sub = @connection.substitute_at(@pk) binds = [[@pk, "3"]] sql = "select * from topics where id = #{sub.to_sql}" @connection.exec_query(sql, 'SQL', binds) message = @subscriber.calls.find { |args| args[4][:sql] == sql } assert_equal [[@pk, 3]], message[4][:binds] end def test_find_one_uses_binds Topic.find(1) binds = [[@pk, 1]] message = @subscriber.calls.find { |args| args[4][:binds] == binds } assert message, 'expected a message with binds' end def test_logs_bind_vars payload = { :name => 'SQL', :sql => 'select * from topics where id = ?', :binds => [[@pk, 10]] } event = ActiveSupport::Notifications::Event.new( 'foo', Time.now, Time.now, 123, payload) logger = Class.new(ActiveRecord::LogSubscriber) { attr_reader :debugs def initialize super @debugs = [] end def debug str @debugs << str end }.new logger.sql event assert_match([[@pk.name, 10]].inspect, logger.debugs.first) end end end end rails-4.2.6/activerecord/test/cases/calculations_test.rb000066400000000000000000000541341266740050600234460ustar00rootroot00000000000000require "cases/helper" require 'models/club' require 'models/company' require "models/contract" require 'models/edge' require 'models/organization' require 'models/possession' require 'models/topic' require 'models/reply' require 'models/minivan' require 'models/speedometer' require 'models/ship_part' require 'models/treasure' require 'models/developer' require 'models/comment' require 'models/rating' require 'models/post' class NumericData < ActiveRecord::Base self.table_name = 'numeric_data' attribute :world_population, Type::Integer.new attribute :my_house_population, Type::Integer.new attribute :atoms_in_universe, Type::Integer.new end class CalculationsTest < ActiveRecord::TestCase fixtures :companies, :accounts, :topics, :speedometers, :minivans def test_should_sum_field assert_equal 318, Account.sum(:credit_limit) end def test_should_average_field value = Account.average(:credit_limit) assert_equal 53.0, value end def test_should_resolve_aliased_attributes assert_equal 318, Account.sum(:available_credit) end def test_should_return_decimal_average_of_integer_field value = Account.average(:id) assert_equal 3.5, value end def test_should_return_integer_average_if_db_returns_such ShipPart.delete_all ShipPart.create!(:id => 3, :name => 'foo') value = ShipPart.average(:id) assert_equal 3, value end def test_should_return_nil_as_average assert_nil NumericData.average(:bank_balance) end def test_should_get_maximum_of_field assert_equal 60, Account.maximum(:credit_limit) end def test_should_get_maximum_of_field_with_include assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).includes(:firm).maximum(:credit_limit) end def test_should_get_minimum_of_field assert_equal 50, Account.minimum(:credit_limit) end def test_should_group_by_field c = Account.group(:firm_id).sum(:credit_limit) [1,6,2].each do |firm_id| assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}" end end def test_should_group_by_arel_attribute c = Account.group(Account.arel_table[:firm_id]).sum(:credit_limit) [1,6,2].each do |firm_id| assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}" end end def test_should_group_by_multiple_fields c = Account.group('firm_id', :credit_limit).count(:all) [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) } end def test_should_group_by_multiple_fields_having_functions c = Topic.group(:author_name, 'COALESCE(type, title)').count(:all) assert_equal 1, c[["Carl", "The Third Topic of the day"]] assert_equal 1, c[["Mary", "Reply"]] assert_equal 1, c[["David", "The First Topic"]] assert_equal 1, c[["Carl", "Reply"]] end def test_should_group_by_summed_field c = Account.group(:firm_id).sum(:credit_limit) assert_equal 50, c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_order_by_grouped_field c = Account.group(:firm_id).order("firm_id").sum(:credit_limit) assert_equal [1, 2, 6, 9], c.keys.compact end def test_should_order_by_calculation c = Account.group(:firm_id).order("sum_credit_limit desc, firm_id").sum(:credit_limit) assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] } assert_equal [6, 2, 9, 1], c.keys.compact end def test_should_limit_calculation c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").limit(2).sum(:credit_limit) assert_equal [1, 2], c.keys.compact end def test_should_limit_calculation_with_offset c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id"). limit(2).offset(1).sum(:credit_limit) assert_equal [2, 6], c.keys.compact end def test_limit_should_apply_before_count accounts = Account.limit(3).where('firm_id IS NOT NULL') assert_equal 3, accounts.count(:firm_id) assert_equal 3, accounts.select(:firm_id).count end def test_count_should_shortcut_with_limit_zero accounts = Account.limit(0) assert_no_queries { assert_equal 0, accounts.count } end def test_limit_is_kept return if current_adapter?(:OracleAdapter) queries = assert_sql { Account.limit(1).count } assert_equal 1, queries.length assert_match(/LIMIT/, queries.first) end def test_offset_is_kept return if current_adapter?(:OracleAdapter) queries = assert_sql { Account.offset(1).count } assert_equal 1, queries.length assert_match(/OFFSET/, queries.first) end def test_limit_with_offset_is_kept return if current_adapter?(:OracleAdapter) queries = assert_sql { Account.limit(1).offset(1).count } assert_equal 1, queries.length assert_match(/LIMIT/, queries.first) assert_match(/OFFSET/, queries.first) end def test_no_limit_no_offset queries = assert_sql { Account.count } assert_equal 1, queries.length assert_no_match(/LIMIT/, queries.first) assert_no_match(/OFFSET/, queries.first) end def test_count_on_invalid_columns_raises e = assert_raises(ActiveRecord::StatementInvalid) { Account.select("credit_limit, firm_name").count } assert_match %r{accounts}i, e.message assert_match "credit_limit, firm_name", e.message end def test_should_group_by_summed_field_having_condition c = Account.group(:firm_id).having('sum(credit_limit) > 50').sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_group_by_summed_field_having_condition_from_select c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("MIN(credit_limit) > 50").sum(:credit_limit) assert_nil c[1] assert_equal 60, c[2] assert_equal 53, c[9] end def test_should_group_by_summed_association c = Account.group(:firm).sum(:credit_limit) assert_equal 50, c[companies(:first_firm)] assert_equal 105, c[companies(:rails_core)] assert_equal 60, c[companies(:first_client)] end def test_should_sum_field_with_conditions assert_equal 105, Account.where('firm_id = 6').sum(:credit_limit) end def test_should_return_zero_if_sum_conditions_return_nothing assert_equal 0, Account.where('1 = 2').sum(:credit_limit) assert_equal 0, companies(:rails_core).companies.where('1 = 2').sum(:id) end def test_sum_should_return_valid_values_for_decimals NumericData.create(:bank_balance => 19.83) assert_equal 19.83, NumericData.sum(:bank_balance) end def test_should_return_type_casted_values_with_group_and_expression assert_equal 0.5, Account.group(:firm_name).sum('0.01 * credit_limit')['37signals'] end def test_should_group_by_summed_field_with_conditions c = Account.where('firm_id > 1').group(:firm_id).sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_group_by_summed_field_with_conditions_and_having c = Account.where('firm_id > 1').group(:firm_id). having('sum(credit_limit) > 60').sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_nil c[2] end def test_should_group_by_fields_with_table_alias c = Account.group('accounts.firm_id').sum(:credit_limit) assert_equal 50, c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_calculate_with_invalid_field assert_equal 6, Account.calculate(:count, '*') assert_equal 6, Account.calculate(:count, :all) end def test_should_calculate_grouped_with_invalid_field c = Account.group('accounts.firm_id').count(:all) assert_equal 1, c[1] assert_equal 2, c[6] assert_equal 1, c[2] end def test_should_calculate_grouped_association_with_invalid_field c = Account.group(:firm).count(:all) assert_equal 1, c[companies(:first_firm)] assert_equal 2, c[companies(:rails_core)] assert_equal 1, c[companies(:first_client)] end def test_should_group_by_association_with_non_numeric_foreign_key Speedometer.create! id: 'ABC' Minivan.create! id: 'OMG', speedometer_id: 'ABC' c = Minivan.group(:speedometer).count(:all) first_key = c.keys.first assert_equal Speedometer, first_key.class assert_equal 1, c[first_key] end def test_should_calculate_grouped_association_with_foreign_key_option Account.belongs_to :another_firm, :class_name => 'Firm', :foreign_key => 'firm_id' c = Account.group(:another_firm).count(:all) assert_equal 1, c[companies(:first_firm)] assert_equal 2, c[companies(:rails_core)] assert_equal 1, c[companies(:first_client)] end def test_should_calculate_grouped_by_function c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] assert_equal 5, c['CLIENT'] assert_equal 2, c['FIRM'] end def test_should_calculate_grouped_by_function_with_table_alias c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] assert_equal 5, c['CLIENT'] assert_equal 2, c['FIRM'] end def test_should_not_overshadow_enumerable_sum assert_equal 6, [1, 2, 3].sum(&:abs) end def test_should_sum_scoped_field assert_equal 15, companies(:rails_core).companies.sum(:id) end def test_should_sum_scoped_field_with_from assert_equal Club.count, Organization.clubs.count end def test_should_sum_scoped_field_with_conditions assert_equal 8, companies(:rails_core).companies.where('id > 7').sum(:id) end def test_should_group_by_scoped_field c = companies(:rails_core).companies.group(:name).sum(:id) assert_equal 7, c['Leetsoft'] assert_equal 8, c['Jadedpixel'] end def test_should_group_by_summed_field_through_association_and_having c = companies(:rails_core).companies.group(:name).having('sum(id) > 7').sum(:id) assert_nil c['Leetsoft'] assert_equal 8, c['Jadedpixel'] end def test_should_count_selected_field_with_include assert_equal 6, Account.includes(:firm).distinct.count assert_equal 4, Account.includes(:firm).distinct.select(:credit_limit).count end def test_should_not_perform_joined_include_by_default assert_equal Account.count, Account.includes(:firm).count queries = assert_sql { Account.includes(:firm).count } assert_no_match(/join/i, queries.last) end def test_should_perform_joined_include_when_referencing_included_tables joined_count = Account.includes(:firm).where(:companies => {:name => '37signals'}).count assert_equal 1, joined_count end def test_should_count_scoped_select Account.update_all("credit_limit = NULL") assert_equal 0, Account.select("credit_limit").count end def test_should_count_scoped_select_with_options Account.update_all("credit_limit = NULL") Account.last.update_columns('credit_limit' => 49) Account.first.update_columns('credit_limit' => 51) assert_equal 1, Account.select("credit_limit").where('credit_limit >= 50').count end def test_should_count_manual_select_with_include assert_equal 6, Account.select("DISTINCT accounts.id").includes(:firm).count end def test_count_with_column_parameter assert_equal 5, Account.count(:firm_id) end def test_count_with_distinct assert_equal 4, Account.select(:credit_limit).distinct.count assert_equal 4, Account.select(:credit_limit).uniq.count end def test_count_with_aliased_attribute assert_equal 6, Account.count(:available_credit) end def test_count_with_column_and_options_parameter assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id) end def test_should_count_field_in_joined_table assert_equal 5, Account.joins(:firm).count('companies.id') assert_equal 4, Account.joins(:firm).distinct.count('companies.id') end def test_should_count_field_in_joined_table_with_group_by c = Account.group('accounts.firm_id').joins(:firm).count('companies.id') [1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) } end def test_count_with_no_parameters_isnt_deprecated assert_not_deprecated { Account.count } end def test_count_with_too_many_parameters_raises assert_raise(ArgumentError) { Account.count(1, 2, 3) } end def test_count_with_order assert_equal 6, Account.order(:credit_limit).count end def test_count_with_reverse_order assert_equal 6, Account.order(:credit_limit).reverse_order.count end def test_count_with_where_and_order assert_equal 1, Account.where(firm_name: '37signals').count assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).count assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count end def test_should_sum_expression # Oracle adapter returns floating point value 636.0 after SUM if current_adapter?(:OracleAdapter) assert_equal 636, Account.sum("2 * credit_limit") else assert_equal 636, Account.sum("2 * credit_limit").to_i end end def test_sum_expression_returns_zero_when_no_records_to_sum assert_equal 0, Account.where('1 = 2').sum("2 * credit_limit") end def test_count_with_from_option assert_equal Company.count(:all), Company.from('companies').count(:all) assert_equal Account.where("credit_limit = 50").count(:all), Account.from('accounts').where("credit_limit = 50").count(:all) assert_equal Company.where(:type => "Firm").count(:type), Company.where(:type => "Firm").from('companies').count(:type) end def test_sum_with_from_option assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit) assert_equal Account.where("credit_limit > 50").sum(:credit_limit), Account.where("credit_limit > 50").from('accounts').sum(:credit_limit) end def test_average_with_from_option assert_equal Account.average(:credit_limit), Account.from('accounts').average(:credit_limit) assert_equal Account.where("credit_limit > 50").average(:credit_limit), Account.where("credit_limit > 50").from('accounts').average(:credit_limit) end def test_minimum_with_from_option assert_equal Account.minimum(:credit_limit), Account.from('accounts').minimum(:credit_limit) assert_equal Account.where("credit_limit > 50").minimum(:credit_limit), Account.where("credit_limit > 50").from('accounts').minimum(:credit_limit) end def test_maximum_with_from_option assert_equal Account.maximum(:credit_limit), Account.from('accounts').maximum(:credit_limit) assert_equal Account.where("credit_limit > 50").maximum(:credit_limit), Account.where("credit_limit > 50").from('accounts').maximum(:credit_limit) end def test_maximum_with_not_auto_table_name_prefix_if_column_included Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) assert_equal 7, Company.includes(:contracts).maximum(:developer_id) end def test_minimum_with_not_auto_table_name_prefix_if_column_included Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) assert_equal 7, Company.includes(:contracts).minimum(:developer_id) end def test_sum_with_not_auto_table_name_prefix_if_column_included Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) assert_equal 7, Company.includes(:contracts).sum(:developer_id) end def test_from_option_with_specified_index if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2' assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all) assert_equal Edge.where('sink_id < 5').count(:all), Edge.from('edges USE INDEX(unique_edge_index)').where('sink_id < 5').count(:all) end end def test_from_option_with_table_different_than_class assert_equal Account.count(:all), Company.from('accounts').count(:all) end def test_distinct_is_honored_when_used_with_count_operation_after_group # Count the number of authors for approved topics approved_topics_count = Topic.group(:approved).count(:author_name)[true] assert_equal approved_topics_count, 4 # Count the number of distinct authors for approved Topics distinct_authors_for_approved_count = Topic.group(:approved).distinct.count(:author_name)[true] assert_equal distinct_authors_for_approved_count, 3 end def test_pluck assert_equal [1,2,3,4,5], Topic.order(:id).pluck(:id) end def test_pluck_without_column_names assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]], Company.order(:id).limit(1).pluck end def test_pluck_type_cast topic = topics(:first) relation = Topic.where(:id => topic.id) assert_equal [ topic.approved ], relation.pluck(:approved) assert_equal [ topic.last_read ], relation.pluck(:last_read) assert_equal [ topic.written_on ], relation.pluck(:written_on) end def test_pluck_and_uniq assert_equal [50, 53, 55, 60], Account.order(:credit_limit).uniq.pluck(:credit_limit) end def test_pluck_in_relation company = Company.first contract = company.contracts.create! assert_equal [contract.id], company.contracts.pluck(:id) end def test_pluck_on_aliased_attribute assert_equal 'The First Topic', Topic.order(:id).pluck(:heading).first end def test_pluck_with_serialization t = Topic.create!(:content => { :foo => :bar }) assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content) end def test_pluck_with_qualified_column_name assert_equal [1,2,3,4,5], Topic.order(:id).pluck("topics.id") end def test_pluck_auto_table_name_prefix c = Company.create!(:name => "test", :contracts => [Contract.new]) assert_equal [c.id], Company.joins(:contracts).pluck(:id) end def test_pluck_if_table_included c = Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) assert_equal [c.id], Company.includes(:contracts).where("contracts.id" => c.contracts.first).pluck(:id) end def test_pluck_not_auto_table_name_prefix_if_column_joined Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) assert_equal [7], Company.joins(:contracts).pluck(:developer_id) end def test_pluck_with_selection_clause assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT credit_limit').sort assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT accounts.credit_limit').sort assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT(credit_limit)').sort # MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless # an alias is provided. Without the alias, the column cannot be found # and properly typecast. assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit') end def test_plucks_with_ids assert_equal Company.all.map(&:id).sort, Company.ids.sort end def test_pluck_with_includes_limit_and_empty_result assert_equal [], Topic.includes(:replies).limit(0).pluck(:id) assert_equal [], Topic.includes(:replies).limit(1).where('0 = 1').pluck(:id) end def test_pluck_not_auto_table_name_prefix_if_column_included Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) ids = Company.includes(:contracts).pluck(:developer_id) assert_equal Company.count, ids.length assert_equal [7], ids.compact end def test_pluck_multiple_columns assert_equal [ [1, "The First Topic"], [2, "The Second Topic of the day"], [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"], [5, "The Fifth Topic of the day"] ], Topic.order(:id).pluck(:id, :title) assert_equal [ [1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"], [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"], [5, "The Fifth Topic of the day", "Jason"] ], Topic.order(:id).pluck(:id, :title, :author_name) end def test_pluck_with_multiple_columns_and_selection_clause assert_equal [[1, 50], [2, 50], [3, 50], [4, 60], [5, 55], [6, 53]], Account.pluck('id, credit_limit') end def test_pluck_with_multiple_columns_and_includes Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) companies_and_developers = Company.order('companies.id').includes(:contracts).pluck(:name, :developer_id) assert_equal Company.count, companies_and_developers.length assert_equal ["37signals", nil], companies_and_developers.first assert_equal ["test", 7], companies_and_developers.last end def test_pluck_with_reserved_words Possession.create!(:where => "Over There") assert_equal ["Over There"], Possession.pluck(:where) end def test_pluck_replaces_select_clause taks_relation = Topic.select(:approved, :id).order(:id) assert_equal [1,2,3,4,5], taks_relation.pluck(:id) assert_equal [false, true, true, true, true], taks_relation.pluck(:approved) end def test_pluck_columns_with_same_name expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]] actual = Topic.joins(:replies) .pluck('topics.title', 'replies_topics.title') assert_equal expected, actual end def test_calculation_with_polymorphic_relation part = ShipPart.create!(name: "has trinket") part.trinkets.create! assert_equal part.id, ShipPart.joins(:trinkets).sum(:id) end def test_pluck_joined_with_polymorphic_relation part = ShipPart.create!(name: "has trinket") part.trinkets.create! assert_equal [part.id], ShipPart.joins(:trinkets).pluck(:id) end def test_grouped_calculation_with_polymorphic_relation part = ShipPart.create!(name: "has trinket") part.trinkets.create! assert_equal({ "has trinket" => part.id }, ShipPart.joins(:trinkets).group("ship_parts.name").sum(:id)) end def test_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association assert_nothing_raised ActiveRecord::StatementInvalid do developer = Developer.create!(name: 'developer') developer.ratings.includes(comment: :post).where(posts: { id: 1 }).count end end end rails-4.2.6/activerecord/test/cases/callbacks_test.rb000066400000000000000000000430071266740050600227010ustar00rootroot00000000000000require "cases/helper" require 'models/developer' require 'models/computer' class CallbackDeveloper < ActiveRecord::Base self.table_name = 'developers' class << self def callback_string(callback_method) "history << [#{callback_method.to_sym.inspect}, :string]" end def callback_proc(callback_method) Proc.new { |model| model.history << [callback_method, :proc] } end def define_callback_method(callback_method) define_method(callback_method) do self.history << [callback_method, :method] end send(callback_method, :"#{callback_method}") end def callback_object(callback_method) klass = Class.new klass.send(:define_method, callback_method) do |model| model.history << [callback_method, :object] end klass.new end end ActiveRecord::Callbacks::CALLBACKS.each do |callback_method| next if callback_method.to_s =~ /^around_/ define_callback_method(callback_method) send(callback_method, callback_string(callback_method)) send(callback_method, callback_proc(callback_method)) send(callback_method, callback_object(callback_method)) send(callback_method) { |model| model.history << [callback_method, :block] } end def history @history ||= [] end end class CallbackDeveloperWithFalseValidation < CallbackDeveloper before_validation proc { |model| model.history << [:before_validation, :returning_false]; false } before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } end class ParentDeveloper < ActiveRecord::Base self.table_name = 'developers' attr_accessor :after_save_called before_validation {|record| record.after_save_called = true} end class ChildDeveloper < ParentDeveloper end class RecursiveCallbackDeveloper < ActiveRecord::Base self.table_name = 'developers' before_save :on_before_save after_save :on_after_save attr_reader :on_before_save_called, :on_after_save_called def on_before_save @on_before_save_called ||= 0 @on_before_save_called += 1 save unless @on_before_save_called > 1 end def on_after_save @on_after_save_called ||= 0 @on_after_save_called += 1 save unless @on_after_save_called > 1 end end class ImmutableDeveloper < ActiveRecord::Base self.table_name = 'developers' validates_inclusion_of :salary, :in => 50000..200000 before_save :cancel before_destroy :cancel def cancelled? @cancelled == true end private def cancel @cancelled = true false end end class ImmutableMethodDeveloper < ActiveRecord::Base self.table_name = 'developers' validates_inclusion_of :salary, :in => 50000..200000 def cancelled? @cancelled == true end before_save do @cancelled = true false end before_destroy do @cancelled = true false end end class OnCallbacksDeveloper < ActiveRecord::Base self.table_name = 'developers' before_validation { history << :before_validation } before_validation(:on => :create){ history << :before_validation_on_create } before_validation(:on => :update){ history << :before_validation_on_update } validate do history << :validate end after_validation { history << :after_validation } after_validation(:on => :create){ history << :after_validation_on_create } after_validation(:on => :update){ history << :after_validation_on_update } def history @history ||= [] end end class ContextualCallbacksDeveloper < ActiveRecord::Base self.table_name = 'developers' before_validation { history << :before_validation } before_validation :before_validation_on_create_and_update, :on => [ :create, :update ] validate do history << :validate end after_validation { history << :after_validation } after_validation :after_validation_on_create_and_update, :on => [ :create, :update ] def before_validation_on_create_and_update history << "before_validation_on_#{self.validation_context}".to_sym end def after_validation_on_create_and_update history << "after_validation_on_#{self.validation_context}".to_sym end def history @history ||= [] end end class CallbackCancellationDeveloper < ActiveRecord::Base self.table_name = 'developers' attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy before_save {defined?(@cancel_before_save) ? !@cancel_before_save : false} before_create { !@cancel_before_create } before_update { !@cancel_before_update } before_destroy { !@cancel_before_destroy } after_save { @after_save_called = true } after_update { @after_update_called = true } after_create { @after_create_called = true } after_destroy { @after_destroy_called = true } end class CallbacksTest < ActiveRecord::TestCase fixtures :developers def test_initialize david = CallbackDeveloper.new assert_equal [ [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], ], david.history end def test_find david = CallbackDeveloper.find(1) assert_equal [ [ :after_find, :method ], [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], ], david.history end def test_new_valid? david = CallbackDeveloper.new david.valid? assert_equal [ [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], ], david.history end def test_existing_valid? david = CallbackDeveloper.find(1) david.valid? assert_equal [ [ :after_find, :method ], [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], ], david.history end def test_create david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000) assert_equal [ [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], [ :before_save, :method ], [ :before_save, :string ], [ :before_save, :proc ], [ :before_save, :object ], [ :before_save, :block ], [ :before_create, :method ], [ :before_create, :string ], [ :before_create, :proc ], [ :before_create, :object ], [ :before_create, :block ], [ :after_create, :method ], [ :after_create, :string ], [ :after_create, :proc ], [ :after_create, :object ], [ :after_create, :block ], [ :after_save, :method ], [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], [ :after_save, :block ] ], david.history end def test_validate_on_create david = OnCallbacksDeveloper.create('name' => 'David', 'salary' => 1000000) assert_equal [ :before_validation, :before_validation_on_create, :validate, :after_validation, :after_validation_on_create ], david.history end def test_validate_on_contextual_create david = ContextualCallbacksDeveloper.create('name' => 'David', 'salary' => 1000000) assert_equal [ :before_validation, :before_validation_on_create, :validate, :after_validation, :after_validation_on_create ], david.history end def test_update david = CallbackDeveloper.find(1) david.save assert_equal [ [ :after_find, :method ], [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], [ :before_save, :method ], [ :before_save, :string ], [ :before_save, :proc ], [ :before_save, :object ], [ :before_save, :block ], [ :before_update, :method ], [ :before_update, :string ], [ :before_update, :proc ], [ :before_update, :object ], [ :before_update, :block ], [ :after_update, :method ], [ :after_update, :string ], [ :after_update, :proc ], [ :after_update, :object ], [ :after_update, :block ], [ :after_save, :method ], [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], [ :after_save, :block ] ], david.history end def test_validate_on_update david = OnCallbacksDeveloper.find(1) david.save assert_equal [ :before_validation, :before_validation_on_update, :validate, :after_validation, :after_validation_on_update ], david.history end def test_validate_on_contextual_update david = ContextualCallbacksDeveloper.find(1) david.save assert_equal [ :before_validation, :before_validation_on_update, :validate, :after_validation, :after_validation_on_update ], david.history end def test_destroy david = CallbackDeveloper.find(1) david.destroy assert_equal [ [ :after_find, :method ], [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_destroy, :method ], [ :before_destroy, :string ], [ :before_destroy, :proc ], [ :before_destroy, :object ], [ :before_destroy, :block ], [ :after_destroy, :method ], [ :after_destroy, :string ], [ :after_destroy, :proc ], [ :after_destroy, :object ], [ :after_destroy, :block ] ], david.history end def test_delete david = CallbackDeveloper.find(1) CallbackDeveloper.delete(david.id) assert_equal [ [ :after_find, :method ], [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], ], david.history end def test_before_save_returning_false david = ImmutableDeveloper.find(1) assert david.valid? assert !david.save exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } assert_equal exc.record, david assert_equal "Failed to save the record", exc.message assert_equal exc.record, david david = ImmutableDeveloper.find(1) david.salary = 10_000_000 assert !david.valid? assert !david.save assert_raise(ActiveRecord::RecordInvalid) { david.save! } someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_save = true assert someone.valid? assert !someone.save assert_save_callbacks_not_called(someone) end def test_before_create_returning_false someone = CallbackCancellationDeveloper.new someone.cancel_before_create = true assert someone.valid? assert !someone.save assert_save_callbacks_not_called(someone) end def test_before_update_returning_false someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_update = true assert someone.valid? assert !someone.save assert_save_callbacks_not_called(someone) end def test_before_destroy_returning_false david = ImmutableDeveloper.find(1) assert !david.destroy exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } assert_equal exc.record, david assert_equal "Failed to destroy the record", exc.message assert_equal exc.record, david assert_not_nil ImmutableDeveloper.find_by_id(1) someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_destroy = true assert !someone.destroy assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } assert !someone.after_destroy_called end def assert_save_callbacks_not_called(someone) assert !someone.after_save_called assert !someone.after_create_called assert !someone.after_update_called end private :assert_save_callbacks_not_called def test_callback_returning_false david = CallbackDeveloperWithFalseValidation.find(1) david.save assert_equal [ [ :after_find, :method ], [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :before_validation, :returning_false ], [ :after_rollback, :block ], [ :after_rollback, :object ], [ :after_rollback, :proc ], [ :after_rollback, :string ], [ :after_rollback, :method ], ], david.history end def test_inheritance_of_callbacks parent = ParentDeveloper.new assert !parent.after_save_called parent.save assert parent.after_save_called child = ChildDeveloper.new assert !child.after_save_called child.save assert child.after_save_called end end rails-4.2.6/activerecord/test/cases/clone_test.rb000066400000000000000000000017221266740050600220600ustar00rootroot00000000000000require "cases/helper" require 'models/topic' module ActiveRecord class CloneTest < ActiveRecord::TestCase fixtures :topics def test_persisted topic = Topic.first cloned = topic.clone assert topic.persisted?, 'topic persisted' assert cloned.persisted?, 'topic persisted' assert !cloned.new_record?, 'topic is not new' end def test_stays_frozen topic = Topic.first topic.freeze cloned = topic.clone assert cloned.persisted?, 'topic persisted' assert !cloned.new_record?, 'topic is not new' assert cloned.frozen?, 'topic should be frozen' end def test_shallow topic = Topic.first cloned = topic.clone topic.author_name = 'Aaron' assert_equal 'Aaron', cloned.author_name end def test_freezing_a_cloned_model_does_not_freeze_clone cloned = Topic.new clone = cloned.clone cloned.freeze assert_not clone.frozen? end end end rails-4.2.6/activerecord/test/cases/coders/000077500000000000000000000000001266740050600206515ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/coders/yaml_column_test.rb000066400000000000000000000032421266740050600245550ustar00rootroot00000000000000 require "cases/helper" module ActiveRecord module Coders class YAMLColumnTest < ActiveRecord::TestCase def test_initialize_takes_class coder = YAMLColumn.new(Object) assert_equal Object, coder.object_class end def test_type_mismatch_on_different_classes_on_dump coder = YAMLColumn.new(Array) assert_raises(SerializationTypeMismatch) do coder.dump("a") end end def test_type_mismatch_on_different_classes coder = YAMLColumn.new(Array) assert_raises(SerializationTypeMismatch) do coder.load "--- foo" end end def test_nil_is_ok coder = YAMLColumn.new assert_nil coder.load "--- " end def test_returns_new_with_different_class coder = YAMLColumn.new SerializationTypeMismatch assert_equal SerializationTypeMismatch, coder.load("--- ").class end def test_returns_string_unless_starts_with_dash coder = YAMLColumn.new assert_equal 'foo', coder.load("foo") end def test_load_handles_other_classes coder = YAMLColumn.new assert_equal [], coder.load([]) end def test_load_doesnt_swallow_yaml_exceptions coder = YAMLColumn.new bad_yaml = '--- {' assert_raises(Psych::SyntaxError) do coder.load(bad_yaml) end end def test_load_doesnt_handle_undefined_class_or_module coder = YAMLColumn.new missing_class_yaml = '--- !ruby/object:DoesNotExistAndShouldntEver {}\n' assert_raises(ArgumentError) do coder.load(missing_class_yaml) end end end end end rails-4.2.6/activerecord/test/cases/column_alias_test.rb000066400000000000000000000006661266740050600234340ustar00rootroot00000000000000require "cases/helper" require 'models/topic' class TestColumnAlias < ActiveRecord::TestCase fixtures :topics QUERY = if 'Oracle' == ActiveRecord::Base.connection.adapter_name 'SELECT id AS pk FROM topics WHERE ROWNUM < 2' else 'SELECT id AS pk FROM topics' end def test_column_alias records = Topic.connection.select_all(QUERY) assert_equal 'pk', records[0].keys[0] end end rails-4.2.6/activerecord/test/cases/column_definition_test.rb000066400000000000000000000116531266740050600244710ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class ColumnDefinitionTest < ActiveRecord::TestCase def setup @adapter = AbstractAdapter.new(nil) def @adapter.native_database_types {:string => "varchar"} end @viz = @adapter.schema_creation end # Avoid column definitions in create table statements like: # `title` varchar(255) DEFAULT NULL def test_should_not_include_default_clause_when_default_is_null column = Column.new("title", nil, Type::String.new(limit: 20)) column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) assert_equal "title varchar(20)", @viz.accept(column_def) end def test_should_include_default_clause_when_default_is_present column = Column.new("title", "Hello", Type::String.new(limit: 20)) column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, @viz.accept(column_def) end def test_should_specify_not_null_if_null_option_is_false column = Column.new("title", "Hello", Type::String.new(limit: 20), "varchar(20)", false) column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def) end if current_adapter?(:MysqlAdapter) def test_should_set_default_for_mysql_binary_data_types binary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "binary(1)") assert_equal "a", binary_column.default varbinary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)") assert_equal "a", varbinary_column.default end def test_should_not_set_default_for_blob_and_text_data_types assert_raise ArgumentError do MysqlAdapter::Column.new("title", "a", Type::Binary.new, "blob") end assert_raise ArgumentError do MysqlAdapter::Column.new("title", "Hello", Type::Text.new) end text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new) assert_equal nil, text_column.default not_null_text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new, "text", false) assert_equal "", not_null_text_column.default end def test_has_default_should_return_false_for_blob_and_text_data_types blob_column = MysqlAdapter::Column.new("title", nil, Type::Binary.new, "blob") assert !blob_column.has_default? text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new) assert !text_column.has_default? end end if current_adapter?(:Mysql2Adapter) def test_should_set_default_for_mysql_binary_data_types binary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "binary(1)") assert_equal "a", binary_column.default varbinary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)") assert_equal "a", varbinary_column.default end def test_should_not_set_default_for_blob_and_text_data_types assert_raise ArgumentError do Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "blob") end assert_raise ArgumentError do Mysql2Adapter::Column.new("title", "Hello", Type::Text.new) end text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new) assert_equal nil, text_column.default not_null_text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new, "text", false) assert_equal "", not_null_text_column.default end def test_has_default_should_return_false_for_blob_and_text_data_types blob_column = Mysql2Adapter::Column.new("title", nil, Type::Binary.new, "blob") assert !blob_column.has_default? text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new) assert !text_column.has_default? end end if current_adapter?(:PostgreSQLAdapter) def test_bigint_column_should_map_to_integer oid = PostgreSQLAdapter::OID::Integer.new bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint") assert_equal :integer, bigint_column.type end def test_smallint_column_should_map_to_integer oid = PostgreSQLAdapter::OID::Integer.new smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint") assert_equal :integer, smallint_column.type end end end end end rails-4.2.6/activerecord/test/cases/connection_adapters/000077500000000000000000000000001266740050600234145ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb000066400000000000000000000026741266740050600301330ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class AdapterLeasingTest < ActiveRecord::TestCase class Pool < ConnectionPool def insert_connection_for_test!(c) synchronize do @connections << c @available.add c end end end def setup @adapter = AbstractAdapter.new nil, nil end def test_in_use? assert_not @adapter.in_use?, 'adapter is not in use' assert @adapter.lease, 'lease adapter' assert @adapter.in_use?, 'adapter is in use' end def test_lease_twice assert @adapter.lease, 'should lease adapter' assert_not @adapter.lease, 'should not lease adapter' end def test_expire_mutates_in_use assert @adapter.lease, 'lease adapter' assert @adapter.in_use?, 'adapter is in use' @adapter.expire assert_not @adapter.in_use?, 'adapter is in use' end def test_close pool = Pool.new(ConnectionSpecification.new({}, nil)) pool.insert_connection_for_test! @adapter @adapter.pool = pool # Make sure the pool marks the connection in use assert_equal @adapter, pool.connection assert @adapter.in_use? # Close should put the adapter back in the pool @adapter.close assert_not @adapter.in_use? assert_equal @adapter, pool.connection end end end end rails-4.2.6/activerecord/test/cases/connection_adapters/connection_handler_test.rb000066400000000000000000000034041266740050600306350ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class ConnectionHandlerTest < ActiveRecord::TestCase def setup @klass = Class.new(Base) { def self.name; 'klass'; end } @subklass = Class.new(@klass) { def self.name; 'subklass'; end } @handler = ConnectionHandler.new @pool = @handler.establish_connection(@klass, Base.connection_pool.spec) end def test_retrieve_connection assert @handler.retrieve_connection(@klass) end def test_active_connections? assert !@handler.active_connections? assert @handler.retrieve_connection(@klass) assert @handler.active_connections? @handler.clear_active_connections! assert !@handler.active_connections? end def test_retrieve_connection_pool_with_ar_base assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base) end def test_retrieve_connection_pool assert_not_nil @handler.retrieve_connection_pool(@klass) end def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection assert_not_nil @handler.retrieve_connection_pool(@subklass) end def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove sub_pool = @handler.establish_connection(@subklass, Base.connection_pool.spec) assert_same sub_pool, @handler.retrieve_connection_pool(@subklass) @handler.remove_connection @subklass assert_same @pool, @handler.retrieve_connection_pool(@subklass) end def test_connection_pools assert_deprecated do assert_equal({ Base.connection_pool.spec => @pool }, @handler.connection_pools) end end end end end rails-4.2.6/activerecord/test/cases/connection_adapters/connection_specification_test.rb000066400000000000000000000005221266740050600320360ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class ConnectionSpecificationTest < ActiveRecord::TestCase def test_dup_deep_copy_config spec = ConnectionSpecification.new({ :a => :b }, "bar") assert_not_equal(spec.config.object_id, spec.dup.config.object_id) end end end end rails-4.2.6/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb000066400000000000000000000271201266740050600343750ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase def setup @previous_database_url = ENV.delete("DATABASE_URL") @previous_rack_env = ENV.delete("RACK_ENV") @previous_rails_env = ENV.delete("RAILS_ENV") end teardown do ENV["DATABASE_URL"] = @previous_database_url ENV["RACK_ENV"] = @previous_rack_env ENV["RAILS_ENV"] = @previous_rails_env end def resolve_config(config) ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve end def resolve_spec(spec, config) ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec) end def test_resolver_with_database_uri_and_current_env_symbol_key ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = resolve_spec(:default_env, config) expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } assert_equal expected, actual end def test_resolver_with_database_uri_and_current_env_symbol_key_and_rails_env ENV['DATABASE_URL'] = "postgres://localhost/foo" ENV['RAILS_ENV'] = "foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = resolve_spec(:foo, config) expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } assert_equal expected, actual end def test_resolver_with_database_uri_and_current_env_symbol_key_and_rack_env ENV['DATABASE_URL'] = "postgres://localhost/foo" ENV['RACK_ENV'] = "foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = resolve_spec(:foo, config) expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } assert_equal expected, actual end def test_resolver_with_database_uri_and_and_current_env_string_key ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = assert_deprecated { resolve_spec("default_env", config) } expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } assert_equal expected, actual end def test_resolver_with_database_uri_and_and_current_env_string_key_and_rails_env ENV['DATABASE_URL'] = "postgres://localhost/foo" ENV['RAILS_ENV'] = "foo" config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } } actual = assert_deprecated { resolve_spec("foo", config) } expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } assert_equal expected, actual end def test_resolver_with_database_uri_and_and_current_env_string_key_and_rack_env ENV['DATABASE_URL'] = "postgres://localhost/foo" ENV['RACK_ENV'] = "foo" config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } } actual = assert_deprecated { resolve_spec("foo", config) } expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } assert_equal expected, actual end def test_resolver_with_database_uri_and_known_key ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } actual = resolve_spec(:production, config) expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" } assert_equal expected, actual end def test_resolver_with_database_uri_and_unknown_symbol_key ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } assert_raises AdapterNotSpecified do resolve_spec(:production, config) end end def test_resolver_with_database_uri_and_unknown_string_key ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } assert_deprecated do assert_raises AdapterNotSpecified do resolve_spec("production", config) end end end def test_resolver_with_database_uri_and_supplied_url ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo" config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } } actual = resolve_spec("postgres://localhost/foo", config) expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } assert_equal expected, actual end def test_jdbc_url config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } } actual = resolve_config(config) assert_equal config, actual end def test_environment_does_not_exist_in_config_url_does_exist ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = resolve_config(config) expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } assert_equal expect_prod, actual["default_env"] end def test_url_with_hyphenated_scheme ENV['DATABASE_URL'] = "ibm-db://localhost/foo" config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } actual = resolve_spec(:default_env, config) expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" } assert_equal expected, actual end def test_string_connection config = { "default_env" => "postgres://localhost/foo" } actual = resolve_config(config) expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } } assert_equal expected, actual end def test_url_sub_key config = { "default_env" => { "url" => "postgres://localhost/foo" } } actual = resolve_config(config) expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } } assert_equal expected, actual end def test_hash config = { "production" => { "adapter" => "postgres", "database" => "foo" } } actual = resolve_config(config) assert_equal config, actual end def test_blank config = {} actual = resolve_config(config) assert_equal config, actual end def test_blank_with_database_url ENV['DATABASE_URL'] = "postgres://localhost/foo" config = {} actual = resolve_config(config) expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } assert_equal expected, actual["default_env"] assert_equal nil, actual["production"] assert_equal nil, actual["development"] assert_equal nil, actual["test"] assert_equal nil, actual[:default_env] assert_equal nil, actual[:production] assert_equal nil, actual[:development] assert_equal nil, actual[:test] end def test_blank_with_database_url_with_rails_env ENV['RAILS_ENV'] = "not_production" ENV['DATABASE_URL'] = "postgres://localhost/foo" config = {} actual = resolve_config(config) expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } assert_equal expected, actual["not_production"] assert_equal nil, actual["production"] assert_equal nil, actual["default_env"] assert_equal nil, actual["development"] assert_equal nil, actual["test"] assert_equal nil, actual[:default_env] assert_equal nil, actual[:not_production] assert_equal nil, actual[:production] assert_equal nil, actual[:development] assert_equal nil, actual[:test] end def test_blank_with_database_url_with_rack_env ENV['RACK_ENV'] = "not_production" ENV['DATABASE_URL'] = "postgres://localhost/foo" config = {} actual = resolve_config(config) expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } assert_equal expected, actual["not_production"] assert_equal nil, actual["production"] assert_equal nil, actual["default_env"] assert_equal nil, actual["development"] assert_equal nil, actual["test"] assert_equal nil, actual[:default_env] assert_equal nil, actual[:not_production] assert_equal nil, actual[:production] assert_equal nil, actual[:development] assert_equal nil, actual[:test] end def test_database_url_with_ipv6_host_and_port ENV['DATABASE_URL'] = "postgres://[::1]:5454/foo" config = {} actual = resolve_config(config) expected = { "adapter" => "postgresql", "database" => "foo", "host" => "::1", "port" => 5454 } assert_equal expected, actual["default_env"] end def test_url_sub_key_with_database_url ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO" config = { "default_env" => { "url" => "postgres://localhost/foo" } } actual = resolve_config(config) expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } } assert_equal expected, actual end def test_merge_no_conflicts_with_database_url ENV['DATABASE_URL'] = "postgres://localhost/foo" config = {"default_env" => { "pool" => "5" } } actual = resolve_config(config) expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost", "pool" => "5" } } assert_equal expected, actual end def test_merge_conflicts_with_database_url ENV['DATABASE_URL'] = "postgres://localhost/foo" config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } } actual = resolve_config(config) expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost", "pool" => "5" } } assert_equal expected, actual end end end end rails-4.2.6/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb000066400000000000000000000034511266740050600306020ustar00rootroot00000000000000require "cases/helper" if current_adapter?(:MysqlAdapter, :Mysql2Adapter) module ActiveRecord module ConnectionAdapters class MysqlTypeLookupTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection end def test_boolean_types emulate_booleans(true) do assert_lookup_type :boolean, 'tinyint(1)' assert_lookup_type :boolean, 'TINYINT(1)' end end def test_string_types assert_lookup_type :string, "enum('one', 'two', 'three')" assert_lookup_type :string, "ENUM('one', 'two', 'three')" assert_lookup_type :string, "set('one', 'two', 'three')" assert_lookup_type :string, "SET('one', 'two', 'three')" end def test_enum_type_with_value_matching_other_type assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')" end def test_binary_types assert_lookup_type :binary, 'bit' assert_lookup_type :binary, 'BIT' end def test_integer_types emulate_booleans(false) do assert_lookup_type :integer, 'tinyint(1)' assert_lookup_type :integer, 'TINYINT(1)' assert_lookup_type :integer, 'year' assert_lookup_type :integer, 'YEAR' end end private def assert_lookup_type(type, lookup) cast_type = @connection.type_map.lookup(lookup) assert_equal type, cast_type.type end def emulate_booleans(value) old_emulate_booleans = @connection.emulate_booleans change_emulate_booleans(value) yield ensure change_emulate_booleans(old_emulate_booleans) end def change_emulate_booleans(value) @connection.emulate_booleans = value @connection.clear_cache! end end end end end rails-4.2.6/activerecord/test/cases/connection_adapters/quoting_test.rb000066400000000000000000000004371266740050600264720ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters module Quoting class QuotingTest < ActiveRecord::TestCase def test_quoting_classes assert_equal "'Object'", AbstractAdapter.new(nil).quote(Object) end end end end end rails-4.2.6/activerecord/test/cases/connection_adapters/schema_cache_test.rb000066400000000000000000000026431266740050600273700ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class SchemaCacheTest < ActiveRecord::TestCase def setup connection = ActiveRecord::Base.connection @cache = SchemaCache.new connection end def test_primary_key assert_equal 'id', @cache.primary_keys('posts') end def test_primary_key_for_non_existent_table assert_nil @cache.primary_keys('omgponies') end def test_caches_columns columns = @cache.columns('posts') assert_equal columns, @cache.columns('posts') end def test_caches_columns_hash columns_hash = @cache.columns_hash('posts') assert_equal columns_hash, @cache.columns_hash('posts') end def test_clearing @cache.columns('posts') @cache.columns_hash('posts') @cache.tables('posts') @cache.primary_keys('posts') @cache.clear! assert_equal 0, @cache.size end def test_dump_and_load @cache.columns('posts') @cache.columns_hash('posts') @cache.tables('posts') @cache.primary_keys('posts') @cache = Marshal.load(Marshal.dump(@cache)) assert_equal 11, @cache.columns('posts').size assert_equal 11, @cache.columns_hash('posts').size assert @cache.tables('posts') assert_equal 'id', @cache.primary_keys('posts') end end end end rails-4.2.6/activerecord/test/cases/connection_adapters/type_lookup_test.rb000066400000000000000000000064241266740050600273600ustar00rootroot00000000000000require "cases/helper" unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strigns for lookup module ActiveRecord module ConnectionAdapters class TypeLookupTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection end def test_boolean_types assert_lookup_type :boolean, 'boolean' assert_lookup_type :boolean, 'BOOLEAN' end def test_string_types assert_lookup_type :string, 'char' assert_lookup_type :string, 'varchar' assert_lookup_type :string, 'VARCHAR' assert_lookup_type :string, 'varchar(255)' assert_lookup_type :string, 'character varying' end def test_binary_types assert_lookup_type :binary, 'binary' assert_lookup_type :binary, 'BINARY' assert_lookup_type :binary, 'blob' assert_lookup_type :binary, 'BLOB' end def test_text_types assert_lookup_type :text, 'text' assert_lookup_type :text, 'TEXT' assert_lookup_type :text, 'clob' assert_lookup_type :text, 'CLOB' end def test_date_types assert_lookup_type :date, 'date' assert_lookup_type :date, 'DATE' end def test_time_types assert_lookup_type :time, 'time' assert_lookup_type :time, 'TIME' end def test_datetime_types assert_lookup_type :datetime, 'datetime' assert_lookup_type :datetime, 'DATETIME' assert_lookup_type :datetime, 'timestamp' assert_lookup_type :datetime, 'TIMESTAMP' end def test_decimal_types assert_lookup_type :decimal, 'decimal' assert_lookup_type :decimal, 'decimal(2,8)' assert_lookup_type :decimal, 'DECIMAL' assert_lookup_type :decimal, 'numeric' assert_lookup_type :decimal, 'numeric(2,8)' assert_lookup_type :decimal, 'NUMERIC' assert_lookup_type :decimal, 'number' assert_lookup_type :decimal, 'number(2,8)' assert_lookup_type :decimal, 'NUMBER' end def test_float_types assert_lookup_type :float, 'float' assert_lookup_type :float, 'FLOAT' assert_lookup_type :float, 'double' assert_lookup_type :float, 'DOUBLE' end def test_integer_types assert_lookup_type :integer, 'integer' assert_lookup_type :integer, 'INTEGER' assert_lookup_type :integer, 'tinyint' assert_lookup_type :integer, 'smallint' assert_lookup_type :integer, 'bigint' end def test_bigint_limit cast_type = @connection.type_map.lookup("bigint") if current_adapter?(:OracleAdapter) assert_equal 19, cast_type.limit else assert_equal 8, cast_type.limit end end def test_decimal_without_scale types = %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)} types.each do |type| cast_type = @connection.type_map.lookup(type) assert_equal :decimal, cast_type.type assert_equal 2, cast_type.type_cast_from_user(2.1) end end private def assert_lookup_type(type, lookup) cast_type = @connection.type_map.lookup(lookup) assert_equal type, cast_type.type end end end end end rails-4.2.6/activerecord/test/cases/connection_management_test.rb000066400000000000000000000074101266740050600253130ustar00rootroot00000000000000require "cases/helper" require "rack" module ActiveRecord module ConnectionAdapters class ConnectionManagementTest < ActiveRecord::TestCase class App attr_reader :calls def initialize @calls = [] end def call(env) @calls << env [200, {}, ['hi mom']] end end def setup @env = {} @app = App.new @management = ConnectionManagement.new(@app) # make sure we have an active connection assert ActiveRecord::Base.connection assert ActiveRecord::Base.connection_handler.active_connections? end if Process.respond_to?(:fork) def test_connection_pool_per_pid object_id = ActiveRecord::Base.connection.object_id rd, wr = IO.pipe rd.binmode wr.binmode pid = fork { rd.close wr.write Marshal.dump ActiveRecord::Base.connection.object_id wr.close exit! } wr.close Process.waitpid pid assert_not_equal object_id, Marshal.load(rd.read) rd.close end end def test_app_delegation manager = ConnectionManagement.new(@app) manager.call @env assert_equal [@env], @app.calls end def test_connections_are_active_after_call @management.call(@env) assert ActiveRecord::Base.connection_handler.active_connections? end def test_body_responds_to_each _, _, body = @management.call(@env) bits = [] body.each { |bit| bits << bit } assert_equal ['hi mom'], bits end def test_connections_are_cleared_after_body_close _, _, body = @management.call(@env) body.close assert !ActiveRecord::Base.connection_handler.active_connections? end def test_active_connections_are_not_cleared_on_body_close_during_test @env['rack.test'] = true _, _, body = @management.call(@env) body.close assert ActiveRecord::Base.connection_handler.active_connections? end def test_connections_closed_if_exception app = Class.new(App) { def call(env); raise NotImplementedError; end }.new explosive = ConnectionManagement.new(app) assert_raises(NotImplementedError) { explosive.call(@env) } assert !ActiveRecord::Base.connection_handler.active_connections? end def test_connections_not_closed_if_exception_and_test @env['rack.test'] = true app = Class.new(App) { def call(env); raise; end }.new explosive = ConnectionManagement.new(app) assert_raises(RuntimeError) { explosive.call(@env) } assert ActiveRecord::Base.connection_handler.active_connections? end def test_connections_closed_if_exception_and_explicitly_not_test @env['rack.test'] = false app = Class.new(App) { def call(env); raise NotImplementedError; end }.new explosive = ConnectionManagement.new(app) assert_raises(NotImplementedError) { explosive.call(@env) } assert !ActiveRecord::Base.connection_handler.active_connections? end test "doesn't clear active connections when running in a test case" do @env['rack.test'] = true @management.call(@env) assert ActiveRecord::Base.connection_handler.active_connections? end test "proxy is polite to it's body and responds to it" do body = Class.new(String) { def to_path; "/path"; end }.new app = lambda { |_| [200, {}, body] } response_body = ConnectionManagement.new(app).call(@env)[2] assert response_body.respond_to?(:to_path) assert_equal response_body.to_path, "/path" end end end end rails-4.2.6/activerecord/test/cases/connection_pool_test.rb000066400000000000000000000226621266740050600241560ustar00rootroot00000000000000require "cases/helper" require 'active_support/concurrency/latch' module ActiveRecord module ConnectionAdapters class ConnectionPoolTest < ActiveRecord::TestCase attr_reader :pool def setup super # Keep a duplicate pool so we do not bother others @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec if in_memory_db? # Separate connections to an in-memory database create an entirely new database, # with an empty schema etc, so we just stub out this schema on the fly. @pool.with_connection do |connection| connection.create_table :posts do |t| t.integer :cololumn end end end end teardown do @pool.disconnect! end def active_connections(pool) pool.connections.find_all(&:in_use?) end def test_checkout_after_close connection = pool.connection assert connection.in_use? connection.close assert !connection.in_use? assert pool.connection.in_use? end def test_released_connection_moves_between_threads thread_conn = nil Thread.new { pool.with_connection do |conn| thread_conn = conn end }.join assert thread_conn Thread.new { pool.with_connection do |conn| assert_equal thread_conn, conn end }.join end def test_with_connection assert_equal 0, active_connections(pool).size main_thread = pool.connection assert_equal 1, active_connections(pool).size Thread.new { pool.with_connection do |conn| assert conn assert_equal 2, active_connections(pool).size end assert_equal 1, active_connections(pool).size }.join main_thread.close assert_equal 0, active_connections(pool).size end def test_active_connection_in_use assert !pool.active_connection? main_thread = pool.connection assert pool.active_connection? main_thread.close assert !pool.active_connection? end def test_full_pool_exception @pool.size.times { @pool.checkout } assert_raises(ConnectionTimeoutError) do @pool.checkout end end def test_full_pool_blocks cs = @pool.size.times.map { @pool.checkout } t = Thread.new { @pool.checkout } # make sure our thread is in the timeout section Thread.pass until t.status == "sleep" connection = cs.first connection.close assert_equal connection, t.join.value end def test_removing_releases_latch cs = @pool.size.times.map { @pool.checkout } t = Thread.new { @pool.checkout } # make sure our thread is in the timeout section Thread.pass until t.status == "sleep" connection = cs.first @pool.remove connection assert_respond_to t.join.value, :execute connection.close end def test_reap_and_active @pool.checkout @pool.checkout @pool.checkout connections = @pool.connections.dup @pool.reap assert_equal connections.length, @pool.connections.length end def test_reap_inactive ready = ActiveSupport::Concurrency::Latch.new @pool.checkout child = Thread.new do @pool.checkout @pool.checkout ready.release Thread.stop end ready.await assert_equal 3, active_connections(@pool).size child.terminate child.join @pool.reap assert_equal 1, active_connections(@pool).size ensure @pool.connections.each(&:close) end def test_remove_connection conn = @pool.checkout assert conn.in_use? length = @pool.connections.length @pool.remove conn assert conn.in_use? assert_equal(length - 1, @pool.connections.length) ensure conn.close end def test_remove_connection_for_thread conn = @pool.connection @pool.remove conn assert_not_equal(conn, @pool.connection) ensure conn.close if conn end def test_active_connection? assert !@pool.active_connection? assert @pool.connection assert @pool.active_connection? @pool.release_connection assert !@pool.active_connection? end def test_checkout_behaviour pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec connection = pool.connection assert_not_nil connection threads = [] 4.times do |i| threads << Thread.new(i) do connection = pool.connection assert_not_nil connection connection.close end end threads.each(&:join) Thread.new do assert pool.connection pool.connection.close end.join end # The connection pool is "fair" if threads waiting for # connections receive them the order in which they began # waiting. This ensures that we don't timeout one HTTP request # even while well under capacity in a multi-threaded environment # such as a Java servlet container. # # We don't need strict fairness: if two connections become # available at the same time, it's fine of two threads that were # waiting acquire the connections out of order. # # Thus this test prepares waiting threads and then trickles in # available connections slowly, ensuring the wakeup order is # correct in this case. def test_checkout_fairness @pool.instance_variable_set(:@size, 10) expected = (1..@pool.size).to_a.freeze # check out all connections so our threads start out waiting conns = expected.map { @pool.checkout } mutex = Mutex.new order = [] errors = [] threads = expected.map do |i| t = Thread.new { begin @pool.checkout # never checked back in mutex.synchronize { order << i } rescue => e mutex.synchronize { errors << e } end } Thread.pass until t.status == "sleep" t end # this should wake up the waiting threads one by one in order conns.each { |conn| @pool.checkin(conn); sleep 0.1 } threads.each(&:join) raise errors.first if errors.any? assert_equal(expected, order) end # As mentioned in #test_checkout_fairness, we don't care about # strict fairness. This test creates two groups of threads: # group1 whose members all start waiting before any thread in # group2. Enough connections are checked in to wakeup all # group1 threads, and the fact that only group1 and no group2 # threads acquired a connection is enforced. def test_checkout_fairness_by_group @pool.instance_variable_set(:@size, 10) # take all the connections conns = (1..10).map { @pool.checkout } mutex = Mutex.new successes = [] # threads that successfully got a connection errors = [] make_thread = proc do |i| t = Thread.new { begin @pool.checkout # never checked back in mutex.synchronize { successes << i } rescue => e mutex.synchronize { errors << e } end } Thread.pass until t.status == "sleep" t end # all group1 threads start waiting before any in group2 group1 = (1..5).map(&make_thread) group2 = (6..10).map(&make_thread) # checkin n connections back to the pool checkin = proc do |n| n.times do c = conns.pop @pool.checkin(c) end end checkin.call(group1.size) # should wake up all group1 loop do sleep 0.1 break if mutex.synchronize { (successes.size + errors.size) == group1.size } end winners = mutex.synchronize { successes.dup } checkin.call(group2.size) # should wake up everyone remaining group1.each(&:join) group2.each(&:join) assert_equal((1..group1.size).to_a, winners.sort) if errors.any? raise errors.first end end def test_automatic_reconnect= pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec assert pool.automatic_reconnect assert pool.connection pool.disconnect! assert pool.connection pool.disconnect! pool.automatic_reconnect = false assert_raises(ConnectionNotEstablished) do pool.connection end assert_raises(ConnectionNotEstablished) do pool.with_connection end end def test_pool_sets_connection_visitor assert @pool.connection.visitor.is_a?(Arel::Visitors::ToSql) end # make sure exceptions are thrown when establish_connection # is called with an anonymous class def test_anonymous_class_exception anonymous = Class.new(ActiveRecord::Base) handler = ActiveRecord::Base.connection_handler assert_raises(RuntimeError) { handler.establish_connection anonymous, nil } end end end end rails-4.2.6/activerecord/test/cases/connection_specification/000077500000000000000000000000001266740050600244315ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/connection_specification/resolver_test.rb000066400000000000000000000071761266740050600276710ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class ConnectionSpecification class ResolverTest < ActiveRecord::TestCase def resolve(spec, config={}) Resolver.new(config).resolve(spec) end def spec(spec, config={}) Resolver.new(config).spec(spec) end def test_url_invalid_adapter error = assert_raises(LoadError) do spec 'ridiculous://foo?encoding=utf8' end assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message end # The abstract adapter is used simply to bypass the bit of code that # checks that the adapter file can be required in. def test_url_from_environment spec = resolve :production, 'production' => 'abstract://foo?encoding=utf8' assert_equal({ "adapter" => "abstract", "host" => "foo", "encoding" => "utf8" }, spec) end def test_url_sub_key spec = resolve :production, 'production' => {"url" => 'abstract://foo?encoding=utf8'} assert_equal({ "adapter" => "abstract", "host" => "foo", "encoding" => "utf8" }, spec) end def test_url_sub_key_merges_correctly hash = {"url" => 'abstract://foo?encoding=utf8&', "adapter" => "sqlite3", "host" => "bar", "pool" => "3"} spec = resolve :production, 'production' => hash assert_equal({ "adapter" => "abstract", "host" => "foo", "encoding" => "utf8", "pool" => "3" }, spec) end def test_url_host_no_db spec = resolve 'abstract://foo?encoding=utf8' assert_equal({ "adapter" => "abstract", "host" => "foo", "encoding" => "utf8" }, spec) end def test_url_host_db spec = resolve 'abstract://foo/bar?encoding=utf8' assert_equal({ "adapter" => "abstract", "database" => "bar", "host" => "foo", "encoding" => "utf8" }, spec) end def test_url_port spec = resolve 'abstract://foo:123?encoding=utf8' assert_equal({ "adapter" => "abstract", "port" => 123, "host" => "foo", "encoding" => "utf8" }, spec) end def test_encoded_password password = 'am@z1ng_p@ssw0rd#!' encoded_password = URI.encode_www_form_component(password) spec = resolve "abstract://foo:#{encoded_password}@localhost/bar" assert_equal password, spec["password"] end def test_url_with_authority_for_sqlite3 spec = resolve 'sqlite3:///foo_test' assert_equal('/foo_test', spec["database"]) end def test_url_absolute_path_for_sqlite3 spec = resolve 'sqlite3:/foo_test' assert_equal('/foo_test', spec["database"]) end def test_url_relative_path_for_sqlite3 spec = resolve 'sqlite3:foo_test' assert_equal('foo_test', spec["database"]) end def test_url_memory_db_for_sqlite3 spec = resolve 'sqlite3::memory:' assert_equal(':memory:', spec["database"]) end def test_url_sub_key_for_sqlite3 spec = resolve :production, 'production' => {"url" => 'sqlite3:foo?encoding=utf8'} assert_equal({ "adapter" => "sqlite3", "database" => "foo", "encoding" => "utf8" }, spec) end end end end end rails-4.2.6/activerecord/test/cases/core_test.rb000066400000000000000000000067651266740050600217240ustar00rootroot00000000000000require 'cases/helper' require 'models/person' require 'models/topic' require 'pp' require 'active_support/core_ext/string/strip' class NonExistentTable < ActiveRecord::Base; end class CoreTest < ActiveRecord::TestCase fixtures :topics def test_inspect_class assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect assert_equal 'LoosePerson(abstract)', LoosePerson.inspect assert_match(/^Topic\(id: integer, title: string/, Topic.inspect) end def test_inspect_instance topic = topics(:first) assert_equal %(#), topic.inspect end def test_inspect_new_instance assert_match(/Topic id: nil/, Topic.new.inspect) end def test_inspect_limited_select_instance assert_equal %(#), Topic.all.merge!(:select => 'id', :where => 'id = 1').first.inspect assert_equal %(#), Topic.all.merge!(:select => 'id, title', :where => 'id = 1').first.inspect end def test_inspect_class_without_table assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect end def test_pretty_print_new topic = Topic.new actual = '' PP.pp(topic, StringIO.new(actual)) expected = <<-PRETTY.strip_heredoc # PRETTY assert actual.start_with?(expected.split('XXXXXX').first) assert actual.end_with?(expected.split('XXXXXX').last) end def test_pretty_print_persisted topic = topics(:first) actual = '' PP.pp(topic, StringIO.new(actual)) expected = <<-PRETTY.strip_heredoc #]+> PRETTY assert_match(/\A#{expected}\z/, actual) end def test_pretty_print_uninitialized topic = Topic.allocate actual = '' PP.pp(topic, StringIO.new(actual)) expected = "#\n" assert actual.start_with?(expected.split('XXXXXX').first) assert actual.end_with?(expected.split('XXXXXX').last) end def test_pretty_print_overridden_by_inspect subtopic = Class.new(Topic) do def inspect "inspecting topic" end end actual = '' PP.pp(subtopic.new, StringIO.new(actual)) assert_equal "inspecting topic\n", actual end end rails-4.2.6/activerecord/test/cases/counter_cache_test.rb000066400000000000000000000146541266740050600235720ustar00rootroot00000000000000require 'cases/helper' require 'models/topic' require 'models/car' require 'models/wheel' require 'models/engine' require 'models/reply' require 'models/category' require 'models/categorization' require 'models/dog' require 'models/dog_lover' require 'models/person' require 'models/friendship' require 'models/subscriber' require 'models/subscription' require 'models/book' class CounterCacheTest < ActiveRecord::TestCase fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships, :subscribers, :subscriptions, :books class ::SpecialTopic < ::Topic has_many :special_replies, :foreign_key => 'parent_id' has_many :lightweight_special_replies, -> { select('topics.id, topics.title') }, :foreign_key => 'parent_id', :class_name => 'SpecialReply' end class ::SpecialReply < ::Reply belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count' end setup do @topic = Topic.find(1) end test "increment counter" do assert_difference '@topic.reload.replies_count' do Topic.increment_counter(:replies_count, @topic.id) end end test "decrement counter" do assert_difference '@topic.reload.replies_count', -1 do Topic.decrement_counter(:replies_count, @topic.id) end end test "reset counters" do # throw the count off by 1 Topic.increment_counter(:replies_count, @topic.id) # check that it gets reset assert_difference '@topic.reload.replies_count', -1 do Topic.reset_counters(@topic.id, :replies) end end test "reset counters by counter name" do # throw the count off by 1 Topic.increment_counter(:replies_count, @topic.id) # check that it gets reset assert_difference '@topic.reload.replies_count', -1 do Topic.reset_counters(@topic.id, :replies_count) end end test 'reset multiple counters' do Topic.update_counters @topic.id, replies_count: 1, unique_replies_count: 1 assert_difference ['@topic.reload.replies_count', '@topic.reload.unique_replies_count'], -1 do Topic.reset_counters(@topic.id, :replies, :unique_replies) end end test "reset counters with string argument" do Topic.increment_counter('replies_count', @topic.id) assert_difference '@topic.reload.replies_count', -1 do Topic.reset_counters(@topic.id, 'replies') end end test "reset counters with modularized and camelized classnames" do special = SpecialTopic.create!(:title => 'Special') SpecialTopic.increment_counter(:replies_count, special.id) assert_difference 'special.reload.replies_count', -1 do SpecialTopic.reset_counters(special.id, :special_replies) end end test "reset counter with belongs_to which has class_name" do car = cars(:honda) assert_nothing_raised do Car.reset_counters(car.id, :engines) end assert_nothing_raised do Car.reset_counters(car.id, :wheels) end end test "reset the right counter if two have the same class_name" do david = dog_lovers(:david) DogLover.increment_counter(:bred_dogs_count, david.id) DogLover.increment_counter(:trained_dogs_count, david.id) assert_difference 'david.reload.bred_dogs_count', -1 do DogLover.reset_counters(david.id, :bred_dogs) end assert_difference 'david.reload.trained_dogs_count', -1 do DogLover.reset_counters(david.id, :trained_dogs) end end test "update counter with initial null value" do category = categories(:general) assert_equal 2, category.categorizations.count assert_nil category.categorizations_count Category.update_counters(category.id, :categorizations_count => category.categorizations.count) assert_equal 2, category.reload.categorizations_count end test "update counter for decrement" do assert_difference '@topic.reload.replies_count', -3 do Topic.update_counters(@topic.id, :replies_count => -3) end end test "update counters of multiple records" do t1, t2 = topics(:first, :second) assert_difference ['t1.reload.replies_count', 't2.reload.replies_count'], 2 do Topic.update_counters([t1.id, t2.id], :replies_count => 2) end end test 'update multiple counters' do assert_difference ['@topic.reload.replies_count', '@topic.reload.unique_replies_count'], 2 do Topic.update_counters @topic.id, replies_count: 2, unique_replies_count: 2 end end test "update other counters on parent destroy" do david, joanna = dog_lovers(:david, :joanna) joanna = joanna # squelch a warning assert_difference 'joanna.reload.dogs_count', -1 do david.destroy end end test "reset the right counter if two have the same foreign key" do michael = people(:michael) assert_nothing_raised(ActiveRecord::StatementInvalid) do Person.reset_counters(michael.id, :friends_too) end end test "reset counter of has_many :through association" do subscriber = subscribers('second') Subscriber.reset_counters(subscriber.id, 'books') Subscriber.increment_counter('books_count', subscriber.id) assert_difference 'subscriber.reload.books_count', -1 do Subscriber.reset_counters(subscriber.id, 'books') end end test "the passed symbol needs to be an association name or counter name" do e = assert_raises(ArgumentError) do Topic.reset_counters(@topic.id, :undefined_count) end assert_equal "'Topic' has no association called 'undefined_count'", e.message end test "reset counter works with select declared on association" do special = SpecialTopic.create!(:title => 'Special') SpecialTopic.increment_counter(:replies_count, special.id) assert_difference 'special.reload.replies_count', -1 do SpecialTopic.reset_counters(special.id, :lightweight_special_replies) end end test "counters are updated both in memory and in the database on create" do car = Car.new(engines_count: 0) car.engines = [Engine.new, Engine.new] car.save! assert_equal 2, car.engines_count assert_equal 2, car.reload.engines_count end test "counter caches are updated in memory when the default value is nil" do car = Car.new(engines_count: nil) car.engines = [Engine.new, Engine.new] car.save! assert_equal 2, car.engines_count assert_equal 2, car.reload.engines_count end test "counter caches are not updated in memory when not selected" do car = Car.select(:id).first assert_nothing_raised do car.engines << Engine.new end end end rails-4.2.6/activerecord/test/cases/custom_locking_test.rb000066400000000000000000000007021266740050600237750ustar00rootroot00000000000000require "cases/helper" require 'models/person' module ActiveRecord class CustomLockingTest < ActiveRecord::TestCase fixtures :people def test_custom_lock if current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_match 'SHARE MODE', Person.lock('LOCK IN SHARE MODE').to_sql assert_sql(/LOCK IN SHARE MODE/) do Person.all.merge!(:lock => 'LOCK IN SHARE MODE').find(1) end end end end end rails-4.2.6/activerecord/test/cases/database_statements_test.rb000066400000000000000000000013611266740050600247720ustar00rootroot00000000000000require "cases/helper" class DatabaseStatementsTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection end def test_insert_should_return_the_inserted_id # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method if current_adapter?(:OracleAdapter) sequence_name = "accounts_seq" id_value = @connection.next_sequence_value(sequence_name) id = @connection.insert("INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name) else id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") end assert_not_nil id end end rails-4.2.6/activerecord/test/cases/date_time_test.rb000066400000000000000000000030371266740050600227140ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/task' class DateTimeTest < ActiveRecord::TestCase include InTimeZone def test_saves_both_date_and_time with_env_tz 'America/New_York' do with_timezone_config default: :utc do time_values = [1807, 2, 10, 15, 30, 45] # create DateTime value with local time zone offset local_offset = Rational(Time.local(*time_values).utc_offset, 86400) now = DateTime.civil(*(time_values + [local_offset])) task = Task.new task.starting = now task.save! # check against Time.local, since some platforms will return a Time instead of a DateTime assert_equal Time.local(*time_values), Task.find(task.id).starting end end end def test_assign_empty_date_time task = Task.new task.starting = '' task.ending = nil assert_nil task.starting assert_nil task.ending end def test_assign_bad_date_time_with_timezone in_time_zone "Pacific Time (US & Canada)" do task = Task.new task.starting = '2014-07-01T24:59:59GMT' assert_nil task.starting end end def test_assign_empty_date topic = Topic.new topic.last_read = '' assert_nil topic.last_read end def test_assign_empty_time topic = Topic.new topic.bonus_time = '' assert_nil topic.bonus_time end def test_assign_in_local_timezone now = DateTime.now with_timezone_config default: :local do task = Task.new starting: now assert_equal now, task.starting end end end rails-4.2.6/activerecord/test/cases/defaults_test.rb000066400000000000000000000203161266740050600225670ustar00rootroot00000000000000require "cases/helper" require 'models/default' require 'models/entrant' class DefaultTest < ActiveRecord::TestCase def test_nil_defaults_for_not_null_columns column_defaults = if current_adapter?(:MysqlAdapter) && (Mysql.client_version < 50051 || (50100..50122).include?(Mysql.client_version)) { 'id' => nil, 'name' => '', 'course_id' => nil } else { 'id' => nil, 'name' => nil, 'course_id' => nil } end column_defaults.each do |name, default| column = Entrant.columns_hash[name] assert !column.null, "#{name} column should be NOT NULL" assert_equal default, column.default, "#{name} column should be DEFAULT #{default.inspect}" end end if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) def test_default_integers default = Default.new assert_instance_of Fixnum, default.positive_integer assert_equal 1, default.positive_integer assert_instance_of Fixnum, default.negative_integer assert_equal(-1, default.negative_integer) assert_instance_of BigDecimal, default.decimal_number assert_equal BigDecimal.new("2.78"), default.decimal_number end end if current_adapter?(:PostgreSQLAdapter) def test_multiline_default_text # older postgres versions represent the default with escapes ("\\012" for a newline) assert( "--- []\n\n" == Default.columns_hash['multiline_default'].default || "--- []\\012\\012" == Default.columns_hash['multiline_default'].default) end def test_default_negative_integer assert_equal "-1", Default.columns_hash['negative_integer'].default end end end class DefaultStringsTest < ActiveRecord::TestCase class DefaultString < ActiveRecord::Base; end setup do @connection = ActiveRecord::Base.connection @connection.create_table :default_strings do |t| t.string :string_col, default: "Smith" t.string :string_col_with_quotes, default: "O'Connor" end DefaultString.reset_column_information end def test_default_strings assert_equal "Smith", DefaultString.new.string_col end def test_default_strings_containing_single_quotes assert_equal "O'Connor", DefaultString.new.string_col_with_quotes end teardown do @connection.drop_table :default_strings end end if current_adapter?(:MysqlAdapter, :Mysql2Adapter) class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase # ActiveRecord::Base#create! (and #save and other related methods) will # open a new transaction. When in transactional fixtures mode, this will # cause Active Record to create a new savepoint. However, since MySQL doesn't # support DDL transactions, creating a table will result in any created # savepoints to be automatically released. This in turn causes the savepoint # release code in AbstractAdapter#transaction to fail. # # We don't want that to happen, so we disable transactional fixtures here. self.use_transactional_fixtures = false def using_strict(strict) connection = ActiveRecord::Base.remove_connection ActiveRecord::Base.establish_connection connection.merge(strict: strict) yield ensure ActiveRecord::Base.remove_connection ActiveRecord::Base.establish_connection connection end # MySQL cannot have defaults on text/blob columns. It reports the # default value as null. # # Despite this, in non-strict mode, MySQL will use an empty string # as the default value of the field, if no other value is # specified. # # Therefore, in non-strict mode, we want column.default to report # an empty string as its default, to be consistent with that. # # In strict mode, column.default should be nil. def test_mysql_text_not_null_defaults_non_strict using_strict(false) do with_text_blob_not_null_table do |klass| assert_equal '', klass.columns_hash['non_null_blob'].default assert_equal '', klass.columns_hash['non_null_text'].default assert_nil klass.columns_hash['null_blob'].default assert_nil klass.columns_hash['null_text'].default instance = klass.create! assert_equal '', instance.non_null_text assert_equal '', instance.non_null_blob assert_nil instance.null_text assert_nil instance.null_blob end end end def test_mysql_text_not_null_defaults_strict using_strict(true) do with_text_blob_not_null_table do |klass| assert_nil klass.columns_hash['non_null_blob'].default assert_nil klass.columns_hash['non_null_text'].default assert_nil klass.columns_hash['null_blob'].default assert_nil klass.columns_hash['null_text'].default assert_raises(ActiveRecord::StatementInvalid) { klass.create } end end end def with_text_blob_not_null_table klass = Class.new(ActiveRecord::Base) klass.table_name = 'test_mysql_text_not_null_defaults' klass.connection.create_table klass.table_name do |t| t.column :non_null_text, :text, :null => false t.column :non_null_blob, :blob, :null => false t.column :null_text, :text, :null => true t.column :null_blob, :blob, :null => true end yield klass ensure klass.connection.drop_table(klass.table_name) rescue nil end # MySQL uses an implicit default 0 rather than NULL unless in strict mode. # We use an implicit NULL so schema.rb is compatible with other databases. def test_mysql_integer_not_null_defaults klass = Class.new(ActiveRecord::Base) klass.table_name = 'test_integer_not_null_default_zero' klass.connection.create_table klass.table_name do |t| t.column :zero, :integer, :null => false, :default => 0 t.column :omit, :integer, :null => false end assert_equal '0', klass.columns_hash['zero'].default assert !klass.columns_hash['zero'].null # 0 in MySQL 4, nil in 5. assert [0, nil].include?(klass.columns_hash['omit'].default) assert !klass.columns_hash['omit'].null assert_raise(ActiveRecord::StatementInvalid) { klass.create! } assert_nothing_raised do instance = klass.create!(:omit => 1) assert_equal 0, instance.zero assert_equal 1, instance.omit end ensure klass.connection.drop_table(klass.table_name) rescue nil end end end if current_adapter?(:PostgreSQLAdapter) class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase def setup @connection = ActiveRecord::Base.connection @old_search_path = @connection.schema_search_path @connection.schema_search_path = "schema_1, pg_catalog" @connection.create_table "defaults" do |t| t.text "text_col", :default => "some value" t.string "string_col", :default => "some value" end Default.reset_column_information end def test_text_defaults_in_new_schema_when_overriding_domain assert_equal "some value", Default.new.text_col, "Default of text column was not correctly parse" end def test_string_defaults_in_new_schema_when_overriding_domain assert_equal "some value", Default.new.string_col, "Default of string column was not correctly parse" end def test_bpchar_defaults_in_new_schema_when_overriding_domain @connection.execute "ALTER TABLE defaults ADD bpchar_col bpchar DEFAULT 'some value'" Default.reset_column_information assert_equal "some value", Default.new.bpchar_col, "Default of bpchar column was not correctly parse" end def test_text_defaults_after_updating_column_default @connection.execute "ALTER TABLE defaults ALTER COLUMN text_col SET DEFAULT 'some text'::schema_1.text" assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parse after updating default using '::text' since postgreSQL will add parens to the default in db" end def test_default_containing_quote_and_colons @connection.execute "ALTER TABLE defaults ALTER COLUMN string_col SET DEFAULT 'foo''::bar'" assert_equal "foo'::bar", Default.new.string_col end teardown do @connection.schema_search_path = @old_search_path Default.reset_column_information end end end rails-4.2.6/activerecord/test/cases/dirty_test.rb000066400000000000000000000555361266740050600221270ustar00rootroot00000000000000require 'cases/helper' require 'models/topic' # For booleans require 'models/pirate' # For timestamps require 'models/parrot' require 'models/person' # For optimistic locking require 'models/aircraft' class Pirate # Just reopening it, not defining it attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected attr_accessor :changes_detected_in_after_update # Actual changes after_update :check_changes private # after_save/update and the model itself # can end up checking dirty status and acting on the results def check_changes if self.changed? self.detected_changes_in_after_update = true self.changes_detected_in_after_update = self.changes end end end class NumericData < ActiveRecord::Base self.table_name = 'numeric_data' end class DirtyTest < ActiveRecord::TestCase include InTimeZone # Dummy to force column loads so query counts are clean. def setup Person.create :first_name => 'foo' end def test_attribute_changes # New record - no changes. pirate = Pirate.new assert !pirate.catchphrase_changed? assert_nil pirate.catchphrase_change # Change catchphrase. pirate.catchphrase = 'arrr' assert pirate.catchphrase_changed? assert_nil pirate.catchphrase_was assert_equal [nil, 'arrr'], pirate.catchphrase_change # Saved - no changes. pirate.save! assert !pirate.catchphrase_changed? assert_nil pirate.catchphrase_change # Same value - no changes. pirate.catchphrase = 'arrr' assert !pirate.catchphrase_changed? assert_nil pirate.catchphrase_change end def test_time_attributes_changes_with_time_zone in_time_zone 'Paris' do target = Class.new(ActiveRecord::Base) target.table_name = 'pirates' # New record - no changes. pirate = target.new assert !pirate.created_on_changed? assert_nil pirate.created_on_change # Saved - no changes. pirate.catchphrase = 'arrrr, time zone!!' pirate.save! assert !pirate.created_on_changed? assert_nil pirate.created_on_change # Change created_on. old_created_on = pirate.created_on pirate.created_on = Time.now - 1.day assert pirate.created_on_changed? assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was assert_equal old_created_on, pirate.created_on_was pirate.created_on = old_created_on assert !pirate.created_on_changed? end end def test_setting_time_attributes_with_time_zone_field_to_itself_should_not_be_marked_as_a_change in_time_zone 'Paris' do target = Class.new(ActiveRecord::Base) target.table_name = 'pirates' pirate = target.create pirate.created_on = pirate.created_on assert !pirate.created_on_changed? end end def test_time_attributes_changes_without_time_zone_by_skip in_time_zone 'Paris' do target = Class.new(ActiveRecord::Base) target.table_name = 'pirates' target.skip_time_zone_conversion_for_attributes = [:created_on] # New record - no changes. pirate = target.new assert !pirate.created_on_changed? assert_nil pirate.created_on_change # Saved - no changes. pirate.catchphrase = 'arrrr, time zone!!' pirate.save! assert !pirate.created_on_changed? assert_nil pirate.created_on_change # Change created_on. old_created_on = pirate.created_on pirate.created_on = Time.now + 1.day assert pirate.created_on_changed? # kind_of does not work because # ActiveSupport::TimeWithZone.name == 'Time' assert_instance_of Time, pirate.created_on_was assert_equal old_created_on, pirate.created_on_was end end def test_time_attributes_changes_without_time_zone with_timezone_config aware_attributes: false do target = Class.new(ActiveRecord::Base) target.table_name = 'pirates' # New record - no changes. pirate = target.new assert !pirate.created_on_changed? assert_nil pirate.created_on_change # Saved - no changes. pirate.catchphrase = 'arrrr, time zone!!' pirate.save! assert !pirate.created_on_changed? assert_nil pirate.created_on_change # Change created_on. old_created_on = pirate.created_on pirate.created_on = Time.now + 1.day assert pirate.created_on_changed? # kind_of does not work because # ActiveSupport::TimeWithZone.name == 'Time' assert_instance_of Time, pirate.created_on_was assert_equal old_created_on, pirate.created_on_was end end def test_aliased_attribute_changes # the actual attribute here is name, title is an # alias setup via alias_attribute parrot = Parrot.new assert !parrot.title_changed? assert_nil parrot.title_change parrot.name = 'Sam' assert parrot.title_changed? assert_nil parrot.title_was assert_equal parrot.name_change, parrot.title_change end def test_reset_attribute! pirate = Pirate.create!(:catchphrase => 'Yar!') pirate.catchphrase = 'Ahoy!' assert_deprecated do pirate.reset_catchphrase! end assert_equal "Yar!", pirate.catchphrase assert_equal Hash.new, pirate.changes assert !pirate.catchphrase_changed? end def test_restore_attribute! pirate = Pirate.create!(:catchphrase => 'Yar!') pirate.catchphrase = 'Ahoy!' pirate.restore_catchphrase! assert_equal "Yar!", pirate.catchphrase assert_equal Hash.new, pirate.changes assert !pirate.catchphrase_changed? end def test_nullable_number_not_marked_as_changed_if_new_value_is_blank pirate = Pirate.new ["", nil].each do |value| pirate.parrot_id = value assert !pirate.parrot_id_changed? assert_nil pirate.parrot_id_change end end def test_nullable_decimal_not_marked_as_changed_if_new_value_is_blank numeric_data = NumericData.new ["", nil].each do |value| numeric_data.bank_balance = value assert !numeric_data.bank_balance_changed? assert_nil numeric_data.bank_balance_change end end def test_nullable_float_not_marked_as_changed_if_new_value_is_blank numeric_data = NumericData.new ["", nil].each do |value| numeric_data.temperature = value assert !numeric_data.temperature_changed? assert_nil numeric_data.temperature_change end end def test_nullable_datetime_not_marked_as_changed_if_new_value_is_blank in_time_zone 'Edinburgh' do target = Class.new(ActiveRecord::Base) target.table_name = 'topics' topic = target.create assert_nil topic.written_on ["", nil].each do |value| topic.written_on = value assert_nil topic.written_on assert !topic.written_on_changed? end end end def test_integer_zero_to_string_zero_not_marked_as_changed pirate = Pirate.new pirate.parrot_id = 0 pirate.catchphrase = 'arrr' assert pirate.save! assert !pirate.changed? pirate.parrot_id = '0' assert !pirate.changed? end def test_integer_zero_to_integer_zero_not_marked_as_changed pirate = Pirate.new pirate.parrot_id = 0 pirate.catchphrase = 'arrr' assert pirate.save! assert !pirate.changed? pirate.parrot_id = 0 assert !pirate.changed? end def test_float_zero_to_string_zero_not_marked_as_changed data = NumericData.new :temperature => 0.0 data.save! assert_not data.changed? data.temperature = '0' assert_empty data.changes data.temperature = '0.0' assert_empty data.changes data.temperature = '0.00' assert_empty data.changes end def test_zero_to_blank_marked_as_changed pirate = Pirate.new pirate.catchphrase = "Yarrrr, me hearties" pirate.parrot_id = 1 pirate.save # check the change from 1 to '' pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties") pirate.parrot_id = '' assert pirate.parrot_id_changed? assert_equal([1, nil], pirate.parrot_id_change) pirate.save # check the change from nil to 0 pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties") pirate.parrot_id = 0 assert pirate.parrot_id_changed? assert_equal([nil, 0], pirate.parrot_id_change) pirate.save # check the change from 0 to '' pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties") pirate.parrot_id = '' assert pirate.parrot_id_changed? assert_equal([0, nil], pirate.parrot_id_change) end def test_object_should_be_changed_if_any_attribute_is_changed pirate = Pirate.new assert !pirate.changed? assert_equal [], pirate.changed assert_equal Hash.new, pirate.changes pirate.catchphrase = 'arrr' assert pirate.changed? assert_nil pirate.catchphrase_was assert_equal %w(catchphrase), pirate.changed assert_equal({'catchphrase' => [nil, 'arrr']}, pirate.changes) pirate.save assert !pirate.changed? assert_equal [], pirate.changed assert_equal Hash.new, pirate.changes end def test_attribute_will_change! pirate = Pirate.create!(:catchphrase => 'arr') assert !pirate.catchphrase_changed? assert pirate.catchphrase_will_change! assert pirate.catchphrase_changed? assert_equal ['arr', 'arr'], pirate.catchphrase_change pirate.catchphrase << ' matey!' assert pirate.catchphrase_changed? assert_equal ['arr', 'arr matey!'], pirate.catchphrase_change end def test_association_assignment_changes_foreign_key pirate = Pirate.create!(:catchphrase => 'jarl') pirate.parrot = Parrot.create!(:name => 'Lorre') assert pirate.changed? assert_equal %w(parrot_id), pirate.changed end def test_attribute_should_be_compared_with_type_cast topic = Topic.new assert topic.approved? assert !topic.approved_changed? # Coming from web form. params = {:topic => {:approved => 1}} # In the controller. topic.attributes = params[:topic] assert topic.approved? assert !topic.approved_changed? end def test_partial_update pirate = Pirate.new(:catchphrase => 'foo') old_updated_on = 1.hour.ago.beginning_of_day with_partial_writes Pirate, false do assert_queries(2) { 2.times { pirate.save! } } Pirate.where(id: pirate.id).update_all(:updated_on => old_updated_on) end with_partial_writes Pirate, true do assert_queries(0) { 2.times { pirate.save! } } assert_equal old_updated_on, pirate.reload.updated_on assert_queries(1) { pirate.catchphrase = 'bar'; pirate.save! } assert_not_equal old_updated_on, pirate.reload.updated_on end end def test_partial_update_with_optimistic_locking person = Person.new(:first_name => 'foo') old_lock_version = 1 with_partial_writes Person, false do assert_queries(2) { 2.times { person.save! } } Person.where(id: person.id).update_all(:first_name => 'baz') end with_partial_writes Person, true do assert_queries(0) { 2.times { person.save! } } assert_equal old_lock_version, person.reload.lock_version assert_queries(1) { person.first_name = 'bar'; person.save! } assert_not_equal old_lock_version, person.reload.lock_version end end def test_changed_attributes_should_be_preserved_if_save_failure pirate = Pirate.new pirate.parrot_id = 1 assert !pirate.save check_pirate_after_save_failure(pirate) pirate = Pirate.new pirate.parrot_id = 1 assert_raise(ActiveRecord::RecordInvalid) { pirate.save! } check_pirate_after_save_failure(pirate) end def test_reload_should_clear_changed_attributes pirate = Pirate.create!(:catchphrase => "shiver me timbers") pirate.catchphrase = "*hic*" assert pirate.changed? pirate.reload assert !pirate.changed? end def test_dup_objects_should_not_copy_dirty_flag_from_creator pirate = Pirate.create!(:catchphrase => "shiver me timbers") pirate_dup = pirate.dup pirate_dup.restore_catchphrase! pirate.catchphrase = "I love Rum" assert pirate.catchphrase_changed? assert !pirate_dup.catchphrase_changed? end def test_reverted_changes_are_not_dirty phrase = "shiver me timbers" pirate = Pirate.create!(:catchphrase => phrase) pirate.catchphrase = "*hic*" assert pirate.changed? pirate.catchphrase = phrase assert !pirate.changed? end def test_reverted_changes_are_not_dirty_after_multiple_changes phrase = "shiver me timbers" pirate = Pirate.create!(:catchphrase => phrase) 10.times do |i| pirate.catchphrase = "*hic*" * i assert pirate.changed? end assert pirate.changed? pirate.catchphrase = phrase assert !pirate.changed? end def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back pirate = Pirate.create!(:catchphrase => "Yar!") pirate.parrot_id = 1 assert pirate.changed? assert pirate.parrot_id_changed? assert !pirate.catchphrase_changed? pirate.parrot_id = nil assert !pirate.changed? assert !pirate.parrot_id_changed? assert !pirate.catchphrase_changed? end def test_save_should_store_serialized_attributes_even_with_partial_writes with_partial_writes(Topic) do topic = Topic.create!(:content => {:a => "a"}) assert_not topic.changed? topic.content[:b] = "b" assert topic.changed? topic.save! assert_not topic.changed? assert_equal "b", topic.content[:b] topic.reload assert_equal "b", topic.content[:b] end end def test_save_always_should_update_timestamps_when_serialized_attributes_are_present with_partial_writes(Topic) do topic = Topic.create!(:content => {:a => "a"}) topic.save! updated_at = topic.updated_at topic.content[:hello] = 'world' topic.save! assert_not_equal updated_at, topic.updated_at assert_equal 'world', topic.content[:hello] end end def test_save_should_not_save_serialized_attribute_with_partial_writes_if_not_present with_partial_writes(Topic) do Topic.create!(:author_name => 'Bill', :content => {:a => "a"}) topic = Topic.select('id, author_name').first topic.update_columns author_name: 'John' topic = Topic.first assert_not_nil topic.content end end def test_previous_changes # original values should be in previous_changes pirate = Pirate.new assert_equal Hash.new, pirate.previous_changes pirate.catchphrase = "arrr" pirate.save! assert_equal 4, pirate.previous_changes.size assert_equal [nil, "arrr"], pirate.previous_changes['catchphrase'] assert_equal [nil, pirate.id], pirate.previous_changes['id'] assert_nil pirate.previous_changes['updated_on'][0] assert_not_nil pirate.previous_changes['updated_on'][1] assert_nil pirate.previous_changes['created_on'][0] assert_not_nil pirate.previous_changes['created_on'][1] assert !pirate.previous_changes.key?('parrot_id') # original values should be in previous_changes pirate = Pirate.new assert_equal Hash.new, pirate.previous_changes pirate.catchphrase = "arrr" pirate.save assert_equal 4, pirate.previous_changes.size assert_equal [nil, "arrr"], pirate.previous_changes['catchphrase'] assert_equal [nil, pirate.id], pirate.previous_changes['id'] assert pirate.previous_changes.include?('updated_on') assert pirate.previous_changes.include?('created_on') assert !pirate.previous_changes.key?('parrot_id') pirate.catchphrase = "Yar!!" pirate.reload assert_equal Hash.new, pirate.previous_changes pirate = Pirate.find_by_catchphrase("arrr") pirate.catchphrase = "Me Maties!" pirate.save! assert_equal 2, pirate.previous_changes.size assert_equal ["arrr", "Me Maties!"], pirate.previous_changes['catchphrase'] assert_not_nil pirate.previous_changes['updated_on'][0] assert_not_nil pirate.previous_changes['updated_on'][1] assert !pirate.previous_changes.key?('parrot_id') assert !pirate.previous_changes.key?('created_on') pirate = Pirate.find_by_catchphrase("Me Maties!") pirate.catchphrase = "Thar She Blows!" pirate.save assert_equal 2, pirate.previous_changes.size assert_equal ["Me Maties!", "Thar She Blows!"], pirate.previous_changes['catchphrase'] assert_not_nil pirate.previous_changes['updated_on'][0] assert_not_nil pirate.previous_changes['updated_on'][1] assert !pirate.previous_changes.key?('parrot_id') assert !pirate.previous_changes.key?('created_on') pirate = Pirate.find_by_catchphrase("Thar She Blows!") pirate.update(catchphrase: "Ahoy!") assert_equal 2, pirate.previous_changes.size assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes['catchphrase'] assert_not_nil pirate.previous_changes['updated_on'][0] assert_not_nil pirate.previous_changes['updated_on'][1] assert !pirate.previous_changes.key?('parrot_id') assert !pirate.previous_changes.key?('created_on') pirate = Pirate.find_by_catchphrase("Ahoy!") pirate.update_attribute(:catchphrase, "Ninjas suck!") assert_equal 2, pirate.previous_changes.size assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes['catchphrase'] assert_not_nil pirate.previous_changes['updated_on'][0] assert_not_nil pirate.previous_changes['updated_on'][1] assert !pirate.previous_changes.key?('parrot_id') assert !pirate.previous_changes.key?('created_on') end if ActiveRecord::Base.connection.supports_migrations? class Testings < ActiveRecord::Base; end def test_field_named_field ActiveRecord::Base.connection.create_table :testings do |t| t.string :field end assert_nothing_raised do Testings.new.attributes end ensure ActiveRecord::Base.connection.drop_table :testings rescue nil end end def test_datetime_attribute_can_be_updated_with_fractional_seconds in_time_zone 'Paris' do target = Class.new(ActiveRecord::Base) target.table_name = 'topics' written_on = Time.utc(2012, 12, 1, 12, 0, 0).in_time_zone('Paris') topic = target.create(:written_on => written_on) topic.written_on += 0.3 assert topic.written_on_changed?, 'Fractional second update not detected' end end def test_datetime_attribute_doesnt_change_if_zone_is_modified_in_string time_in_paris = Time.utc(2014, 1, 1, 12, 0, 0).in_time_zone('Paris') pirate = Pirate.create!(:catchphrase => 'rrrr', :created_on => time_in_paris) pirate.created_on = pirate.created_on.in_time_zone('Tokyo').to_s assert !pirate.created_on_changed? end test "partial insert" do with_partial_writes Person do jon = nil assert_sql(/first_name/i) do jon = Person.create! first_name: 'Jon' end assert ActiveRecord::SQLCounter.log_all.none? { |sql| sql =~ /followers_count/ } jon.reload assert_equal 'Jon', jon.first_name assert_equal 0, jon.followers_count assert_not_nil jon.id end end test "partial insert with empty values" do with_partial_writes Aircraft do a = Aircraft.create! a.reload assert_not_nil a.id end end test "defaults with type that implements `type_cast_for_database`" do type = Class.new(ActiveRecord::Type::Value) do def type_cast(value) value.to_i end def type_cast_for_database(value) value.to_s end end model_class = Class.new(ActiveRecord::Base) do self.table_name = 'numeric_data' attribute :foo, type.new, default: 1 end model = model_class.new assert_not model.foo_changed? model = model_class.new(foo: 1) assert_not model.foo_changed? model = model_class.new(foo: '1') assert_not model.foo_changed? end test "in place mutation detection" do pirate = Pirate.create!(catchphrase: "arrrr") pirate.catchphrase << " matey!" assert pirate.catchphrase_changed? expected_changes = { "catchphrase" => ["arrrr", "arrrr matey!"] } assert_equal(expected_changes, pirate.changes) assert_equal("arrrr", pirate.catchphrase_was) assert pirate.catchphrase_changed?(from: "arrrr") assert_not pirate.catchphrase_changed?(from: "anything else") assert pirate.changed_attributes.include?(:catchphrase) pirate.save! pirate.reload assert_equal "arrrr matey!", pirate.catchphrase assert_not pirate.changed? end test "in place mutation for binary" do klass = Class.new(ActiveRecord::Base) do self.table_name = :binaries serialize :data end binary = klass.create!(data: "\\\\foo") assert_not binary.changed? binary.data = binary.data.dup assert_not binary.changed? binary = klass.last assert_not binary.changed? binary.data << "bar" assert binary.changed? end test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do test_type_class = Class.new(ActiveRecord::Type::Value) do define_method(:changed_in_place?) do |*| raise end end klass = Class.new(ActiveRecord::Base) do self.table_name = 'people' attribute :foo, test_type_class.new end model = klass.new(first_name: "Jim") assert model.first_name_changed? end test "attribute_will_change! doesn't try to save non-persistable attributes" do klass = Class.new(ActiveRecord::Base) do self.table_name = 'people' attribute :non_persisted_attribute, ActiveRecord::Type::String.new end record = klass.new(first_name: "Sean") record.non_persisted_attribute_will_change! assert record.non_persisted_attribute_changed? assert record.save end test "mutating and then assigning doesn't remove the change" do pirate = Pirate.create!(catchphrase: "arrrr") pirate.catchphrase << " matey!" pirate.catchphrase = "arrrr matey!" assert pirate.catchphrase_changed?(from: "arrrr", to: "arrrr matey!") end test "getters with side effects are allowed" do klass = Class.new(Pirate) do def catchphrase if super.blank? update_attribute(:catchphrase, "arr") # what could possibly go wrong? end super end end pirate = klass.create!(catchphrase: "lol") pirate.update_attribute(:catchphrase, nil) assert_equal "arr", pirate.catchphrase end test "cloning and modifying an object in-place only registers changes on the new object" do pirate = Pirate.create!(catchphrase: "arrrr") assert_equal({}, pirate.changed_attributes) pirate_clone = pirate.dup assert_equal({"catchphrase" => nil}, pirate_clone.changed_attributes) pirate_clone.catchphrase = "arrrr matey!" assert_equal({}, pirate.changed_attributes) assert_equal({"catchphrase" => nil}, pirate_clone.changed_attributes) end private def with_partial_writes(klass, on = true) old = klass.partial_writes? klass.partial_writes = on yield ensure klass.partial_writes = old end def check_pirate_after_save_failure(pirate) assert pirate.changed? assert pirate.parrot_id_changed? assert_equal %w(parrot_id), pirate.changed assert_nil pirate.parrot_id_was end end rails-4.2.6/activerecord/test/cases/disconnected_test.rb000066400000000000000000000012631266740050600234220ustar00rootroot00000000000000require "cases/helper" class TestRecord < ActiveRecord::Base end class TestDisconnectedAdapter < ActiveRecord::TestCase self.use_transactional_fixtures = false def setup @connection = ActiveRecord::Base.connection end teardown do return if in_memory_db? spec = ActiveRecord::Base.connection_config ActiveRecord::Base.establish_connection(spec) end unless in_memory_db? test "can't execute statements while disconnected" do @connection.execute "SELECT count(*) from products" @connection.disconnect! assert_raises(ActiveRecord::StatementInvalid) do @connection.execute "SELECT count(*) from products" end end end end rails-4.2.6/activerecord/test/cases/dup_test.rb000066400000000000000000000071771266740050600215620ustar00rootroot00000000000000require "cases/helper" require 'models/reply' require 'models/topic' module ActiveRecord class DupTest < ActiveRecord::TestCase fixtures :topics def test_dup assert !Topic.new.freeze.dup.frozen? end def test_not_readonly topic = Topic.first duped = topic.dup assert !duped.readonly?, 'should not be readonly' end def test_is_readonly topic = Topic.first topic.readonly! duped = topic.dup assert duped.readonly?, 'should be readonly' end def test_dup_not_persisted topic = Topic.first duped = topic.dup assert !duped.persisted?, 'topic not persisted' assert duped.new_record?, 'topic is new' end def test_dup_not_destroyed topic = Topic.first topic.destroy duped = topic.dup assert_not duped.destroyed? end def test_dup_has_no_id topic = Topic.first duped = topic.dup assert_nil duped.id end def test_dup_with_modified_attributes topic = Topic.first topic.author_name = 'Aaron' duped = topic.dup assert_equal 'Aaron', duped.author_name end def test_dup_with_changes dbtopic = Topic.first topic = Topic.new topic.attributes = dbtopic.attributes.except("id") #duped has no timestamp values duped = dbtopic.dup #clear topic timestamp values topic.send(:clear_timestamp_attributes) assert_equal topic.changes, duped.changes end def test_dup_topics_are_independent topic = Topic.first topic.author_name = 'Aaron' duped = topic.dup duped.author_name = 'meow' assert_not_equal topic.changes, duped.changes end def test_dup_attributes_are_independent topic = Topic.first duped = topic.dup duped.author_name = 'meow' topic.author_name = 'Aaron' assert_equal 'Aaron', topic.author_name assert_equal 'meow', duped.author_name end def test_dup_timestamps_are_cleared topic = Topic.first assert_not_nil topic.updated_at assert_not_nil topic.created_at # temporary change to the topic object topic.updated_at -= 3.days #dup should not preserve the timestamps if present new_topic = topic.dup assert_nil new_topic.updated_at assert_nil new_topic.created_at new_topic.save assert_not_nil new_topic.updated_at assert_not_nil new_topic.created_at end def test_dup_after_initialize_callbacks topic = Topic.new assert Topic.after_initialize_called Topic.after_initialize_called = false topic.dup assert Topic.after_initialize_called end def test_dup_validity_is_independent repair_validations(Topic) do Topic.validates_presence_of :title topic = Topic.new("title" => "Literature") topic.valid? duped = topic.dup duped.title = nil assert duped.invalid? topic.title = nil duped.title = 'Mathematics' assert topic.invalid? assert duped.valid? end end def test_dup_with_default_scope prev_default_scopes = Topic.default_scopes Topic.default_scopes = [proc { Topic.where(:approved => true) }] topic = Topic.new(:approved => false) assert !topic.dup.approved?, "should not be overridden by default scopes" ensure Topic.default_scopes = prev_default_scopes end def test_dup_without_primary_key klass = Class.new(ActiveRecord::Base) do self.table_name = 'parrots_pirates' end record = klass.create! assert_nothing_raised do record.dup end end end end rails-4.2.6/activerecord/test/cases/enum_test.rb000066400000000000000000000171251266740050600217300ustar00rootroot00000000000000require 'cases/helper' require 'models/book' class EnumTest < ActiveRecord::TestCase fixtures :books setup do @book = books(:awdr) end test "query state by predicate" do assert @book.proposed? assert_not @book.written? assert_not @book.published? assert @book.unread? end test "query state with strings" do assert_equal "proposed", @book.status assert_equal "unread", @book.read_status end test "find via scope" do assert_equal @book, Book.proposed.first assert_equal @book, Book.unread.first end test "update by declaration" do @book.written! assert @book.written? end test "update by setter" do @book.update! status: :written assert @book.written? end test "enum methods are overwritable" do assert_equal "do publish work...", @book.published! assert @book.published? end test "direct assignment" do @book.status = :written assert @book.written? end test "assign string value" do @book.status = "written" assert @book.written? end test "enum changed attributes" do old_status = @book.status @book.status = :published assert_equal old_status, @book.changed_attributes[:status] end test "enum changes" do old_status = @book.status @book.status = :published assert_equal [old_status, 'published'], @book.changes[:status] end test "enum attribute was" do old_status = @book.status @book.status = :published assert_equal old_status, @book.attribute_was(:status) end test "enum attribute changed" do @book.status = :published assert @book.attribute_changed?(:status) end test "enum attribute changed to" do @book.status = :published assert @book.attribute_changed?(:status, to: 'published') end test "enum attribute changed from" do old_status = @book.status @book.status = :published assert @book.attribute_changed?(:status, from: old_status) end test "enum attribute changed from old status to new status" do old_status = @book.status @book.status = :published assert @book.attribute_changed?(:status, from: old_status, to: 'published') end test "enum didn't change" do old_status = @book.status @book.status = old_status assert_not @book.attribute_changed?(:status) end test "persist changes that are dirty" do @book.status = :published assert @book.attribute_changed?(:status) @book.status = :written assert @book.attribute_changed?(:status) end test "reverted changes that are not dirty" do old_status = @book.status @book.status = :published assert @book.attribute_changed?(:status) @book.status = old_status assert_not @book.attribute_changed?(:status) end test "reverted changes are not dirty going from nil to value and back" do book = Book.create!(nullable_status: nil) book.nullable_status = :married assert book.attribute_changed?(:nullable_status) book.nullable_status = nil assert_not book.attribute_changed?(:nullable_status) end test "assign non existing value raises an error" do e = assert_raises(ArgumentError) do @book.status = :unknown end assert_equal "'unknown' is not a valid status", e.message end test "assign nil value" do @book.status = nil assert @book.status.nil? end test "assign empty string value" do @book.status = '' assert @book.status.nil? end test "assign long empty string value" do @book.status = ' ' assert @book.status.nil? end test "constant to access the mapping" do assert_equal 0, Book.statuses[:proposed] assert_equal 1, Book.statuses["written"] assert_equal 2, Book.statuses[:published] end test "building new objects with enum scopes" do assert Book.written.build.written? assert Book.read.build.read? end test "creating new objects with enum scopes" do assert Book.written.create.written? assert Book.read.create.read? end test "_before_type_cast returns the enum label (required for form fields)" do assert_equal "proposed", @book.status_before_type_cast end test "reserved enum names" do klass = Class.new(ActiveRecord::Base) do self.table_name = "books" enum status: [:proposed, :written, :published] end conflicts = [ :column, # generates class method .columns, which conflicts with an AR method :logger, # generates #logger, which conflicts with an AR method :attributes, # generates #attributes=, which conflicts with an AR method ] conflicts.each_with_index do |name, i| assert_raises(ArgumentError, "enum name `#{name}` should not be allowed") do klass.class_eval { enum name => ["value_#{i}"] } end end end test "reserved enum values" do klass = Class.new(ActiveRecord::Base) do self.table_name = "books" enum status: [:proposed, :written, :published] end conflicts = [ :new, # generates a scope that conflicts with an AR class method :valid, # generates #valid?, which conflicts with an AR method :save, # generates #save!, which conflicts with an AR method :proposed, # same value as an existing enum :public, :private, :protected, # some important methods on Module and Class :name, :parent, :superclass ] conflicts.each_with_index do |value, i| assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do klass.class_eval { enum "status_#{i}" => [value] } end end end test "overriding enum method should not raise" do assert_nothing_raised do Class.new(ActiveRecord::Base) do self.table_name = "books" def published! super "do publish work..." end enum status: [:proposed, :written, :published] def written! super "do written work..." end end end end test "validate uniqueness" do klass = Class.new(ActiveRecord::Base) do def self.name; 'Book'; end enum status: [:proposed, :written] validates_uniqueness_of :status end klass.delete_all klass.create!(status: "proposed") book = klass.new(status: "written") assert book.valid? book.status = "proposed" assert_not book.valid? end test "validate inclusion of value in array" do klass = Class.new(ActiveRecord::Base) do def self.name; 'Book'; end enum status: [:proposed, :written] validates_inclusion_of :status, in: ["written"] end klass.delete_all invalid_book = klass.new(status: "proposed") assert_not invalid_book.valid? valid_book = klass.new(status: "written") assert valid_book.valid? end test "enums are distinct per class" do klass1 = Class.new(ActiveRecord::Base) do self.table_name = "books" enum status: [:proposed, :written] end klass2 = Class.new(ActiveRecord::Base) do self.table_name = "books" enum status: [:drafted, :uploaded] end book1 = klass1.proposed.create! book1.status = :written assert_equal ['proposed', 'written'], book1.status_change book2 = klass2.drafted.create! book2.status = :uploaded assert_equal ['drafted', 'uploaded'], book2.status_change end test "enums are inheritable" do subklass1 = Class.new(Book) subklass2 = Class.new(Book) do enum status: [:drafted, :uploaded] end book1 = subklass1.proposed.create! book1.status = :written assert_equal ['proposed', 'written'], book1.status_change book2 = subklass2.drafted.create! book2.status = :uploaded assert_equal ['drafted', 'uploaded'], book2.status_change end end rails-4.2.6/activerecord/test/cases/explain_subscriber_test.rb000066400000000000000000000036601266740050600246460ustar00rootroot00000000000000require 'cases/helper' require 'active_record/explain_subscriber' require 'active_record/explain_registry' if ActiveRecord::Base.connection.supports_explain? class ExplainSubscriberTest < ActiveRecord::TestCase SUBSCRIBER = ActiveRecord::ExplainSubscriber.new def setup ActiveRecord::ExplainRegistry.reset ActiveRecord::ExplainRegistry.collect = true end def test_collects_nothing_if_the_payload_has_an_exception SUBSCRIBER.finish(nil, nil, exception: Exception.new) assert queries.empty? end def test_collects_nothing_for_ignored_payloads ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| SUBSCRIBER.finish(nil, nil, name: ip) end assert queries.empty? end def test_collects_nothing_if_collect_is_false ActiveRecord::ExplainRegistry.collect = false SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select 1 from users', binds: [1, 2]) assert queries.empty? end def test_collects_pairs_of_queries_and_binds sql = 'select 1 from users' binds = [1, 2] SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: sql, binds: binds) assert_equal 1, queries.size assert_equal sql, queries[0][0] assert_equal binds, queries[0][1] end def test_collects_nothing_if_the_statement_is_not_whitelisted SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'SHOW max_identifier_length') assert queries.empty? end def test_collects_nothing_if_the_statement_is_only_partially_matched SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select_db yo_mama') assert queries.empty? end def test_collects_cte_queries SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'with s as (values(3)) select 1 from s') assert_equal 1, queries.size end teardown do ActiveRecord::ExplainRegistry.reset end def queries ActiveRecord::ExplainRegistry.queries end end end rails-4.2.6/activerecord/test/cases/explain_test.rb000066400000000000000000000037331266740050600224240ustar00rootroot00000000000000require 'cases/helper' require 'models/car' require 'active_support/core_ext/string/strip' if ActiveRecord::Base.connection.supports_explain? class ExplainTest < ActiveRecord::TestCase fixtures :cars def base ActiveRecord::Base end def connection base.connection end def test_relation_explain message = Car.where(:name => 'honda').explain assert_match(/^EXPLAIN for:/, message) end def test_collecting_queries_for_explain queries = ActiveRecord::Base.collecting_queries_for_explain do Car.where(:name => 'honda').to_a end sql, binds = queries[0] assert_match "SELECT", sql if binds.any? assert_equal 1, binds.length assert_equal "honda", binds.flatten.last else assert_match 'honda', sql end end def test_exec_explain_with_no_binds sqls = %w(foo bar) binds = [[], []] queries = sqls.zip(binds) connection.stubs(:explain).returns('query plan foo', 'query plan bar') expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n") assert_equal expected, base.exec_explain(queries) end def test_exec_explain_with_binds cols = [Object.new, Object.new] cols[0].expects(:name).returns('wadus') cols[1].expects(:name).returns('chaflan') sqls = %w(foo bar) binds = [[[cols[0], 1]], [[cols[1], 2]]] queries = sqls.zip(binds) connection.stubs(:explain).returns("query plan foo\n", "query plan bar\n") expected = <<-SQL.strip_heredoc EXPLAIN for: #{sqls[0]} [["wadus", 1]] query plan foo EXPLAIN for: #{sqls[1]} [["chaflan", 2]] query plan bar SQL assert_equal expected, base.exec_explain(queries) end def test_unsupported_connection_adapter connection.stubs(:supports_explain?).returns(false) base.logger.expects(:warn).never Car.where(:name => 'honda').to_a end end end rails-4.2.6/activerecord/test/cases/finder_respond_to_test.rb000066400000000000000000000040321266740050600244600ustar00rootroot00000000000000require "cases/helper" require 'models/topic' class FinderRespondToTest < ActiveRecord::TestCase fixtures :topics def test_should_preserve_normal_respond_to_behaviour_on_base assert_respond_to ActiveRecord::Base, :new assert !ActiveRecord::Base.respond_to?(:find_by_something) end def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { } assert_respond_to Topic, :method_added_for_finder_respond_to_test ensure class << Topic; self; end.send(:remove_method, :method_added_for_finder_respond_to_test) end def test_should_preserve_normal_respond_to_behaviour_and_respond_to_standard_object_method assert_respond_to Topic, :to_s end def test_should_respond_to_find_by_one_attribute_before_caching ensure_topic_method_is_not_cached(:find_by_title) assert_respond_to Topic, :find_by_title end def test_should_respond_to_find_by_with_bang ensure_topic_method_is_not_cached(:find_by_title!) assert_respond_to Topic, :find_by_title! end def test_should_respond_to_find_by_two_attributes ensure_topic_method_is_not_cached(:find_by_title_and_author_name) assert_respond_to Topic, :find_by_title_and_author_name end def test_should_respond_to_find_all_by_an_aliased_attribute ensure_topic_method_is_not_cached(:find_by_heading) assert_respond_to Topic, :find_by_heading end def test_should_not_respond_to_find_by_one_missing_attribute assert !Topic.respond_to?(:find_by_undertitle) end def test_should_not_respond_to_find_by_invalid_method_syntax assert !Topic.respond_to?(:fail_to_find_by_title) assert !Topic.respond_to?(:find_by_title?) assert !Topic.respond_to?(:fail_to_find_or_create_by_title) assert !Topic.respond_to?(:find_or_create_by_title?) end private def ensure_topic_method_is_not_cached(method_id) class << Topic; self; end.send(:remove_method, method_id) if Topic.public_methods.include? method_id end end rails-4.2.6/activerecord/test/cases/finder_test.rb000066400000000000000000001224351266740050600222340ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/author' require 'models/categorization' require 'models/comment' require 'models/company' require 'models/tagging' require 'models/topic' require 'models/reply' require 'models/entrant' require 'models/project' require 'models/developer' require 'models/computer' require 'models/customer' require 'models/toy' require 'models/matey' require 'models/dog' require 'models/car' require 'models/tyre' class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars def test_find_by_id_with_hash assert_raises(ActiveRecord::StatementInvalid) do Post.find_by_id(:limit => 1) end end def test_find_by_title_and_id_with_hash assert_raises(ActiveRecord::StatementInvalid) do Post.find_by_title_and_id('foo', :limit => 1) end end def test_find assert_equal(topics(:first).title, Topic.find(1).title) end def test_find_with_proc_parameter_and_block exception = assert_raises(RuntimeError) do Topic.all.find(-> { raise "should happen" }) { |e| e.title == "non-existing-title" } end assert_equal "should happen", exception.message assert_nothing_raised(RuntimeError) do Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title } end end def test_find_passing_active_record_object_is_deprecated assert_deprecated do Topic.find(Topic.last) end end def test_symbols_table_ref gc_disabled = GC.disable Post.where("author_id" => nil) # warm up x = Symbol.all_symbols.count Post.where("title" => {"xxxqqqq" => "bar"}) assert_equal x, Symbol.all_symbols.count ensure GC.enable if gc_disabled == false end # find should handle strings that come from URLs # (example: Category.find(params[:id])) def test_find_with_string assert_equal(Topic.find(1).title,Topic.find("1").title) end def test_exists assert_equal true, Topic.exists?(1) assert_equal true, Topic.exists?("1") assert_equal true, Topic.exists?(title: "The First Topic") assert_equal true, Topic.exists?(heading: "The First Topic") assert_equal true, Topic.exists?(:author_name => "Mary", :approved => true) assert_equal true, Topic.exists?(["parent_id = ?", 1]) assert_equal true, Topic.exists?(id: [1, 9999]) assert_equal false, Topic.exists?(45) assert_equal false, Topic.exists?(Topic.new.id) assert_raise(NoMethodError) { Topic.exists?([1,2]) } end def test_exists_with_polymorphic_relation post = Post.create!(title: 'Post', body: 'default', taggings: [Tagging.new(comment: 'tagging comment')]) relation = Post.tagged_with_comment('tagging comment') assert_equal true, relation.exists?(title: ['Post']) assert_equal true, relation.exists?(['title LIKE ?', 'Post%']) assert_equal true, relation.exists? assert_equal true, relation.exists?(post.id) assert_equal true, relation.exists?(post.id.to_s) assert_equal false, relation.exists?(false) end def test_exists_passing_active_record_object_is_deprecated assert_deprecated do Topic.exists?(Topic.new) end end def test_exists_fails_when_parameter_has_invalid_type assert_raises(RangeError) do assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int end assert_equal false, Topic.exists?("foo") end def test_exists_does_not_select_columns_without_alias assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do Topic.exists? end end def test_exists_returns_true_with_one_record_and_no_args assert_equal true, Topic.exists? end def test_exists_returns_false_with_false_arg assert_equal false, Topic.exists?(false) end # exists? should handle nil for id's that come from URLs and always return false # (example: Topic.exists?(params[:id])) where params[:id] is nil def test_exists_with_nil_arg assert_equal false, Topic.exists?(nil) assert_equal true, Topic.exists? assert_equal false, Topic.first.replies.exists?(nil) assert_equal true, Topic.first.replies.exists? end # ensures +exists?+ runs valid SQL by excluding order value def test_exists_with_order assert_equal true, Topic.order(:id).distinct.exists? end def test_exists_with_includes_limit_and_empty_result assert_equal false, Topic.includes(:replies).limit(0).exists? assert_equal false, Topic.includes(:replies).limit(1).where('0 = 1').exists? end def test_exists_with_distinct_association_includes_and_limit author = Author.first assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists? assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists? end def test_exists_with_distinct_association_includes_limit_and_order author = Author.first assert_equal false, author.unique_categorized_posts.includes(:special_comments).order('comments.tags_count DESC').limit(0).exists? assert_equal true, author.unique_categorized_posts.includes(:special_comments).order('comments.tags_count DESC').limit(1).exists? end def test_exists_with_empty_table_and_no_args_given Topic.delete_all assert_equal false, Topic.exists? end def test_exists_with_aggregate_having_three_mappings existing_address = customers(:david).address assert_equal true, Customer.exists?(:address => existing_address) end def test_exists_with_aggregate_having_three_mappings_with_one_difference existing_address = customers(:david).address assert_equal false, Customer.exists?(:address => Address.new(existing_address.street, existing_address.city, existing_address.country + "1")) assert_equal false, Customer.exists?(:address => Address.new(existing_address.street, existing_address.city + "1", existing_address.country)) assert_equal false, Customer.exists?(:address => Address.new(existing_address.street + "1", existing_address.city, existing_address.country)) end def test_exists_does_not_instantiate_records Developer.expects(:instantiate).never Developer.exists? end def test_find_by_array_of_one_id assert_kind_of(Array, Topic.find([ 1 ])) assert_equal(1, Topic.find([ 1 ]).length) end def test_find_by_ids assert_equal 2, Topic.find(1, 2).size assert_equal topics(:second).title, Topic.find([2]).first.title end def test_find_by_ids_with_limit_and_offset assert_equal 2, Entrant.limit(2).find([1,3,2]).size assert_equal 1, Entrant.limit(3).offset(2).find([1,3,2]).size # Also test an edge case: If you have 11 results, and you set a # limit of 3 and offset of 9, then you should find that there # will be only 2 results, regardless of the limit. devs = Developer.all last_devs = Developer.limit(3).offset(9).find devs.map(&:id) assert_equal 2, last_devs.size end def test_find_with_large_number assert_raises(ActiveRecord::RecordNotFound) { Topic.find('9999999999999999999999999999999') } end def test_find_by_with_large_number assert_nil Topic.find_by(id: '9999999999999999999999999999999') end def test_find_by_id_with_large_number assert_nil Topic.find_by_id('9999999999999999999999999999999') end def test_find_on_relation_with_large_number assert_nil Topic.where('1=1').find_by(id: 9999999999999999999999999999999) end def test_find_by_bang_on_relation_with_large_number assert_raises(ActiveRecord::RecordNotFound) do Topic.where('1=1').find_by!(id: 9999999999999999999999999999999) end end def test_find_an_empty_array assert_equal [], Topic.find([]) end def test_find_doesnt_have_implicit_ordering assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) } end def test_find_by_ids_missing_one assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) } end def test_find_with_group_and_sanitized_having_method developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').to_a assert_equal 3, developers.size assert_equal 3, developers.map(&:salary).uniq.size assert developers.all? { |developer| developer.salary > 10000 } end def test_find_with_entire_select_statement topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'" assert_equal(1, topics.size) assert_equal(topics(:second).title, topics.first.title) end def test_find_with_prepared_select_statement topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"] assert_equal(1, topics.size) assert_equal(topics(:second).title, topics.first.title) end def test_find_by_sql_with_sti_on_joined_table accounts = Account.find_by_sql("SELECT * FROM accounts INNER JOIN companies ON companies.id = accounts.firm_id") assert_equal [Account], accounts.collect(&:class).uniq end def test_take assert_equal topics(:first), Topic.take end def test_take_failing assert_nil Topic.where("title = 'This title does not exist'").take end def test_take_bang_present assert_nothing_raised do assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").take! end end def test_take_bang_missing assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.where("title = 'This title does not exist'").take! end end def test_first assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title end def test_first_failing assert_nil Topic.where("title = 'The Second Topic of the day!'").first end def test_first_bang_present assert_nothing_raised do assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").first! end end def test_first_bang_missing assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.where("title = 'This title does not exist'").first! end end def test_first_have_primary_key_order_by_default expected = topics(:first) expected.touch # PostgreSQL changes the default order if no order clause is used assert_equal expected, Topic.first end def test_model_class_responds_to_first_bang assert Topic.first! Topic.delete_all assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.first! end end def test_second assert_equal topics(:second).title, Topic.second.title end def test_second_with_offset assert_equal topics(:fifth), Topic.offset(3).second end def test_second_have_primary_key_order_by_default expected = topics(:second) expected.touch # PostgreSQL changes the default order if no order clause is used assert_equal expected, Topic.second end def test_model_class_responds_to_second_bang assert Topic.second! Topic.delete_all assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.second! end end def test_third assert_equal topics(:third).title, Topic.third.title end def test_third_with_offset assert_equal topics(:fifth), Topic.offset(2).third end def test_third_have_primary_key_order_by_default expected = topics(:third) expected.touch # PostgreSQL changes the default order if no order clause is used assert_equal expected, Topic.third end def test_model_class_responds_to_third_bang assert Topic.third! Topic.delete_all assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.third! end end def test_fourth assert_equal topics(:fourth).title, Topic.fourth.title end def test_fourth_with_offset assert_equal topics(:fifth), Topic.offset(1).fourth end def test_fourth_have_primary_key_order_by_default expected = topics(:fourth) expected.touch # PostgreSQL changes the default order if no order clause is used assert_equal expected, Topic.fourth end def test_model_class_responds_to_fourth_bang assert Topic.fourth! Topic.delete_all assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.fourth! end end def test_fifth assert_equal topics(:fifth).title, Topic.fifth.title end def test_fifth_with_offset assert_equal topics(:fifth), Topic.offset(0).fifth end def test_fifth_have_primary_key_order_by_default expected = topics(:fifth) expected.touch # PostgreSQL changes the default order if no order clause is used assert_equal expected, Topic.fifth end def test_model_class_responds_to_fifth_bang assert Topic.fifth! Topic.delete_all assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.fifth! end end def test_last_bang_present assert_nothing_raised do assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last! end end def test_last_bang_missing assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.where("title = 'This title does not exist'").last! end end def test_model_class_responds_to_last_bang assert_equal topics(:fifth), Topic.last! assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do Topic.delete_all Topic.last! end end def test_take_and_first_and_last_with_integer_should_use_sql_limit assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries } assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries } assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries } end def test_last_with_integer_and_order_should_keep_the_order assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2) end def test_last_with_integer_and_order_should_not_use_sql_limit query = assert_sql { Topic.order("title").last(5).entries } assert_equal 1, query.length assert_no_match(/LIMIT/, query.first) end def test_last_with_integer_and_reorder_should_not_use_sql_limit query = assert_sql { Topic.reorder("title").last(5).entries } assert_equal 1, query.length assert_no_match(/LIMIT/, query.first) end def test_take_and_first_and_last_with_integer_should_return_an_array assert_kind_of Array, Topic.take(5) assert_kind_of Array, Topic.first(5) assert_kind_of Array, Topic.last(5) end def test_unexisting_record_exception_handling assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1).parent } Topic.find(2).topic end def test_find_only_some_columns topic = Topic.select("author_name").find(1) assert_raise(ActiveModel::MissingAttributeError) {topic.title} assert_raise(ActiveModel::MissingAttributeError) {topic.title?} assert_nil topic.read_attribute("title") assert_equal "David", topic.author_name assert !topic.attribute_present?("title") assert !topic.attribute_present?(:title) assert topic.attribute_present?("author_name") assert_respond_to topic, "author_name" end def test_find_on_array_conditions assert Topic.where(["approved = ?", false]).find(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.where(["approved = ?", true]).find(1) } end def test_find_on_hash_conditions assert Topic.where(approved: false).find(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) } end def test_find_on_hash_conditions_with_explicit_table_name assert Topic.where('topics.approved' => false).find(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) } end def test_find_on_hash_conditions_with_hashed_table_name assert Topic.where(topics: { approved: false }).find(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) } end def test_find_with_hash_conditions_on_joined_table firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 }) assert_equal 1, firms.size assert_equal companies(:first_firm), firms.first end def test_find_with_hash_conditions_on_joined_table_and_with_range firms = DependentFirm.joins(:account).where(name: 'RailsCore', accounts: { credit_limit: 55..60 }) assert_equal 1, firms.size assert_equal companies(:rails_core), firms.first end def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate david = customers(:david) assert Customer.where('customers.name' => david.name, :address => david.address).find(david.id) assert_raise(ActiveRecord::RecordNotFound) { Customer.where('customers.name' => david.name + "1", :address => david.address).find(david.id) } end def test_find_on_association_proxy_conditions assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.where(post_id: authors(:david).posts).map(&:id).sort end def test_find_on_hash_conditions_with_range assert_equal [1,2], Topic.where(id: 1..2).to_a.map(&:id).sort assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2..3).find(1) } end def test_find_on_hash_conditions_with_end_exclusive_range assert_equal [1,2,3], Topic.where(id: 1..3).to_a.map(&:id).sort assert_equal [1,2], Topic.where(id: 1...3).to_a.map(&:id).sort assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2...3).find(3) } end def test_find_on_hash_conditions_with_multiple_ranges assert_equal [1,2,3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort assert_equal [1], Comment.where(id: 1..1, post_id: 1..10).to_a.map(&:id).sort end def test_find_on_hash_conditions_with_array_of_integers_and_ranges assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [1..2, 3, 5, 6..8, 9]).to_a.map(&:id).sort end def test_find_on_hash_conditions_with_array_of_ranges assert_equal [1,2,6,7,8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort end def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges assert_deprecated do assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [[1..2], 3, [5], 6..8, 9]).to_a.map(&:id).sort end end def test_find_on_hash_conditions_with_array_of_integers_and_arrays assert_deprecated do assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [[1, 2], 3, 5, [6, [7], 8], 9]).to_a.map(&:id).sort end end def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges_and_nils assert_deprecated do assert_equal [1,3,4,5], Topic.where(parent_id: [[2..6], nil]).to_a.map(&:id).sort end end def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges_and_more_nils assert_deprecated do assert_equal [], Topic.where(parent_id: [[7..10, nil, [nil]], [nil]]).to_a.map(&:id).sort end end def test_find_on_multiple_hash_conditions assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) } assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "HHC", replies_count: 1, approved: false).find(1) } assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) } end def test_condition_interpolation assert_kind_of Firm, Company.where("name = '%s'", "37signals").first assert_nil Company.where(["name = '%s'", "37signals!"]).first assert_nil Company.where(["name = '%s'", "37signals!' OR 1=1"]).first assert_kind_of Time, Topic.where(["id = %d", 1]).first.written_on end def test_condition_array_interpolation assert_kind_of Firm, Company.where(["name = '%s'", "37signals"]).first assert_nil Company.where(["name = '%s'", "37signals!"]).first assert_nil Company.where(["name = '%s'", "37signals!' OR 1=1"]).first assert_kind_of Time, Topic.where(["id = %d", 1]).first.written_on end def test_condition_hash_interpolation assert_kind_of Firm, Company.where(name: "37signals").first assert_nil Company.where(name: "37signals!").first assert_kind_of Time, Topic.where(id: 1).first.written_on end def test_hash_condition_find_malformed assert_raise(ActiveRecord::StatementInvalid) { Company.where(id: 2, dhh: true).first } end def test_hash_condition_find_with_escaped_characters Company.create("name" => "Ain't noth'n like' \#stuff") assert Company.where(name: "Ain't noth'n like' \#stuff").first end def test_hash_condition_find_with_array p1, p2 = Post.limit(2).order('id asc').to_a assert_equal [p1, p2], Post.where(id: [p1, p2]).order('id asc').to_a assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order('id asc').to_a end def test_hash_condition_find_with_nil topic = Topic.where(last_read: nil).first assert_not_nil topic assert_nil topic.last_read end def test_hash_condition_find_with_aggregate_having_one_mapping balance = customers(:david).balance assert_kind_of Money, balance found_customer = Customer.where(:balance => balance).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location found_customer = Customer.where(:gps_location => gps_location).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value balance = customers(:david).balance assert_kind_of Money, balance found_customer = Customer.where(:balance => balance.amount).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location found_customer = Customer.where(:gps_location => gps_location.gps_location).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_having_three_mappings address = customers(:david).address assert_kind_of Address, address found_customer = Customer.where(:address => address).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not address = customers(:david).address assert_kind_of Address, address found_customer = Customer.where(:address => address, :name => customers(:david).name).first assert_equal customers(:david), found_customer end def test_condition_utc_time_interpolation_with_default_timezone_local with_env_tz 'America/New_York' do with_timezone_config default: :local do topic = Topic.first assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getutc]).first end end end def test_hash_condition_utc_time_interpolation_with_default_timezone_local with_env_tz 'America/New_York' do with_timezone_config default: :local do topic = Topic.first assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first end end end def test_condition_local_time_interpolation_with_default_timezone_utc with_env_tz 'America/New_York' do with_timezone_config default: :utc do topic = Topic.first assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getlocal]).first end end end def test_hash_condition_local_time_interpolation_with_default_timezone_utc with_env_tz 'America/New_York' do with_timezone_config default: :utc do topic = Topic.first assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first end end end def test_bind_variables assert_kind_of Firm, Company.where(["name = ?", "37signals"]).first assert_nil Company.where(["name = ?", "37signals!"]).first assert_nil Company.where(["name = ?", "37signals!' OR 1=1"]).first assert_kind_of Time, Topic.where(["id = ?", 1]).first.written_on assert_raise(ActiveRecord::PreparedStatementInvalid) { Company.where(["id=? AND name = ?", 2]).first } assert_raise(ActiveRecord::PreparedStatementInvalid) { Company.where(["id=?", 2, 3, 4]).first } end def test_bind_variables_with_quotes Company.create("name" => "37signals' go'es agains") assert Company.where(["name = ?", "37signals' go'es agains"]).first end def test_named_bind_variables_with_quotes Company.create("name" => "37signals' go'es agains") assert Company.where(["name = :name", {name: "37signals' go'es agains"}]).first end def test_bind_arity assert_nothing_raised { bind '' } assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 } assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' } assert_nothing_raised { bind '?', 1 } assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 } end def test_named_bind_variables assert_equal '1', bind(':a', :a => 1) # ' ruby-mode assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode assert_nothing_raised { bind("'+00:00'", :foo => "bar") } assert_kind_of Firm, Company.where(["name = :name", { name: "37signals" }]).first assert_nil Company.where(["name = :name", { name: "37signals!" }]).first assert_nil Company.where(["name = :name", { name: "37signals!' OR 1=1" }]).first assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on end class SimpleEnumerable include Enumerable def initialize(ary) @ary = ary end def each(&b) @ary.each(&b) end end def test_bind_enumerable quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) assert_equal '1,2,3', bind('?', [1, 2, 3]) assert_equal quoted_abc, bind('?', %w(a b c)) assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3])) assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c))) assert_equal '1,2,3', bind(':a', :a => SimpleEnumerable.new([1, 2, 3])) assert_equal quoted_abc, bind(':a', :a => SimpleEnumerable.new(%w(a b c))) # ' end def test_bind_empty_enumerable quoted_nil = ActiveRecord::Base.connection.quote(nil) assert_equal quoted_nil, bind('?', []) assert_equal " in (#{quoted_nil})", bind(' in (?)', []) assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', []) end def test_bind_empty_string quoted_empty = ActiveRecord::Base.connection.quote('') assert_equal quoted_empty, bind('?', '') end def test_bind_chars quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi") assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper") assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars) assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars) end def test_bind_record o = Struct.new(:quoted_id).new(1) assert_equal '1', bind('?', o) os = [o] * 3 assert_equal '1,1,1', bind('?', os) end def test_named_bind_with_postgresql_type_casts l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') } assert_nothing_raised(&l) assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call end def test_string_sanitation assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") end def test_count_by_sql assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3")) assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2])) assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1])) end def test_find_by_one_attribute assert_equal topics(:first), Topic.find_by_title("The First Topic") assert_nil Topic.find_by_title("The First Topic!") end def test_find_by_one_attribute_bang assert_equal topics(:first), Topic.find_by_title!("The First Topic") assert_raises_with_message(ActiveRecord::RecordNotFound, "Couldn't find Topic") do Topic.find_by_title!("The First Topic!") end end def test_find_by_on_attribute_that_is_a_reserved_word dog_alias = 'Dog' dog = Dog.create(alias: dog_alias) assert_equal dog, Dog.find_by_alias(dog_alias) end def test_find_by_one_attribute_that_is_an_alias assert_equal topics(:first), Topic.find_by_heading("The First Topic") assert_nil Topic.find_by_heading("The First Topic!") end def test_find_by_one_attribute_bang_with_blank_defined blank_topic = BlankTopic.create(title: "The Blank One") assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One") end def test_find_by_one_attribute_with_conditions assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50) end def test_find_by_one_attribute_that_is_an_aggregate address = customers(:david).address assert_kind_of Address, address found_customer = Customer.find_by_address(address) assert_equal customers(:david), found_customer end def test_find_by_one_attribute_that_is_an_aggregate_with_one_attribute_difference address = customers(:david).address assert_kind_of Address, address missing_address = Address.new(address.street, address.city, address.country + "1") assert_nil Customer.find_by_address(missing_address) missing_address = Address.new(address.street, address.city + "1", address.country) assert_nil Customer.find_by_address(missing_address) missing_address = Address.new(address.street + "1", address.city, address.country) assert_nil Customer.find_by_address(missing_address) end def test_find_by_two_attributes_that_are_both_aggregates balance = customers(:david).balance address = customers(:david).address assert_kind_of Money, balance assert_kind_of Address, address found_customer = Customer.find_by_balance_and_address(balance, address) assert_equal customers(:david), found_customer end def test_find_by_two_attributes_with_one_being_an_aggregate balance = customers(:david).balance assert_kind_of Money, balance found_customer = Customer.find_by_balance_and_name(balance, customers(:david).name) assert_equal customers(:david), found_customer end def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching # ensure this test can run independently of order class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit) a = Account.where('firm_id = ?', 6).find_by_credit_limit(50) assert_equal a, Account.where('firm_id = ?', 6).find_by_credit_limit(50) # find_by_credit_limit has been cached end def test_find_by_one_attribute_with_several_options assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50) end def test_find_by_one_missing_attribute assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") } end def test_find_by_invalid_method_syntax assert_raise(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") } assert_raise(NoMethodError) { Topic.find_by_title?("The First Topic") } assert_raise(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") } assert_raise(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") } end def test_find_by_two_attributes assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David") assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary") end def test_find_by_two_attributes_but_passing_only_one assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") } end def test_find_last_with_offset devs = Developer.order('id') assert_equal devs[2], Developer.offset(2).first assert_equal devs[-3], Developer.offset(2).last assert_equal devs[-3], Developer.offset(2).last assert_equal devs[-3], Developer.offset(2).order('id DESC').first end def test_find_by_nil_attribute topic = Topic.find_by_last_read nil assert_not_nil topic assert_nil topic.last_read end def test_find_by_nil_and_not_nil_attributes topic = Topic.find_by_last_read_and_author_name nil, "Mary" assert_equal "Mary", topic.author_name end def test_find_with_bad_sql assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } end def test_find_all_with_join developers_on_project_one = Developer. joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id'). where('project_id=1').to_a assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map { |d| d.name } assert developer_names.include?('David') assert developer_names.include?('Jamis') end def test_joins_dont_clobber_id first = Firm. joins('INNER JOIN companies clients ON clients.firm_id = companies.id'). where('companies.id = 1').first assert_equal 1, first.id end def test_joins_with_string_array person_with_reader_and_post = Post. joins(["INNER JOIN categorizations ON categorizations.post_id = posts.id", "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" ]) assert_equal 1, person_with_reader_and_post.size end def test_find_by_id_with_conditions_with_or assert_nothing_raised do Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3]) end end # http://dev.rubyonrails.org/ticket/6778 def test_find_ignores_previously_inserted_record Post.create!(:title => 'test', :body => 'it out') assert_equal [], Post.where(id: nil) end def test_find_by_empty_ids assert_equal [], Post.find([]) end def test_find_by_empty_in_condition assert_equal [], Post.where('id in (?)', []) end def test_find_by_records p1, p2 = Post.limit(2).order('id asc').to_a assert_equal [p1, p2], Post.where(['id in (?)', [p1, p2]]).order('id asc') assert_equal [p1, p2], Post.where(['id in (?)', [p1, p2.id]]).order('id asc') end def test_select_value assert_equal "37signals", Company.connection.select_value("SELECT name FROM companies WHERE id = 1") assert_nil Company.connection.select_value("SELECT name FROM companies WHERE id = -1") # make sure we didn't break count... assert_equal 0, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = 'Halliburton'") assert_equal 1, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = '37signals'") end def test_select_values assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s } assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id") end def test_select_rows assert_equal( [["1", "1", nil, "37signals"], ["2", "1", "2", "Summit"], ["3", "1", "1", "Microsoft"]], Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}) assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]], Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}} end def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct assert_equal 2, Post.includes(authors: :author_address). where.not(author_addresses: { id: nil }). order('author_addresses.id DESC').limit(2).to_a.size assert_equal 3, Post.includes(author: :author_address, authors: :author_address). where.not(author_addresses_authors: { id: nil }). order('author_addresses_authors.id DESC').limit(3).to_a.size end def test_find_with_nil_inside_set_passed_for_one_attribute client_of = Company. where(client_of: [2, 1, nil], name: ['37signals', 'Summit', 'Microsoft']). order('client_of DESC'). map { |x| x.client_of } assert client_of.include?(nil) assert_equal [2, 1].sort, client_of.compact.sort end def test_find_with_nil_inside_set_passed_for_attribute client_of = Company. where(client_of: [nil]). order('client_of DESC'). map { |x| x.client_of } assert_equal [], client_of.compact end def test_with_limiting_with_custom_select posts = Post.references(:authors).merge( :includes => :author, :select => 'posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id' ).to_a assert_equal 3, posts.size assert_equal [0, 1, 1], posts.map(&:author_id).sort end def test_find_one_message_with_custom_primary_key table_with_custom_primary_key do |model| model.primary_key = :name e = assert_raises(ActiveRecord::RecordNotFound) do model.find 'Hello World!' end assert_equal %Q{Couldn't find MercedesCar with 'name'=Hello World!}, e.message end end def test_find_some_message_with_custom_primary_key table_with_custom_primary_key do |model| model.primary_key = :name e = assert_raises(ActiveRecord::RecordNotFound) do model.find 'Hello', 'World!' end assert_equal %Q{Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)}, e.message end end def test_find_without_primary_key assert_raises(ActiveRecord::UnknownPrimaryKey) do Matey.find(1) end end def test_finder_with_offset_string assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a } end test "find_by with hash conditions returns the first matching record" do assert_equal posts(:eager_other), Post.find_by(id: posts(:eager_other).id) end test "find_by with non-hash conditions returns the first matching record" do assert_equal posts(:eager_other), Post.find_by("id = #{posts(:eager_other).id}") end test "find_by with multi-arg conditions returns the first matching record" do assert_equal posts(:eager_other), Post.find_by('id = ?', posts(:eager_other).id) end test "find_by returns nil if the record is missing" do assert_equal nil, Post.find_by("1 = 0") end test "find_by with associations" do assert_equal authors(:david), Post.find_by(author: authors(:david)).author assert_equal authors(:mary) , Post.find_by(author: authors(:mary) ).author end test "find_by doesn't have implicit ordering" do assert_sql(/^((?!ORDER).)*$/) { Post.find_by(id: posts(:eager_other).id) } end test "find_by! with hash conditions returns the first matching record" do assert_equal posts(:eager_other), Post.find_by!(id: posts(:eager_other).id) end test "find_by! with non-hash conditions returns the first matching record" do assert_equal posts(:eager_other), Post.find_by!("id = #{posts(:eager_other).id}") end test "find_by! with multi-arg conditions returns the first matching record" do assert_equal posts(:eager_other), Post.find_by!('id = ?', posts(:eager_other).id) end test "find_by! doesn't have implicit ordering" do assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(id: posts(:eager_other).id) } end test "find_by! raises RecordNotFound if the record is missing" do assert_raises(ActiveRecord::RecordNotFound) do Post.find_by!("1 = 0") end end test "find on a scope does not perform statement caching" do honda = cars(:honda) zyke = cars(:zyke) tyre = honda.tyres.create! tyre2 = zyke.tyres.create! assert_equal tyre, honda.tyres.custom_find(tyre.id) assert_equal tyre2, zyke.tyres.custom_find(tyre2.id) end test "find_by on a scope does not perform statement caching" do honda = cars(:honda) zyke = cars(:zyke) tyre = honda.tyres.create! tyre2 = zyke.tyres.create! assert_equal tyre, honda.tyres.custom_find_by(id: tyre.id) assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id) end protected def bind(statement, *vars) if vars.first.is_a?(Hash) ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first) else ActiveRecord::Base.send(:replace_bind_variables, statement, vars) end end def table_with_custom_primary_key yield(Class.new(Toy) do def self.name 'MercedesCar' end end) end def assert_raises_with_message(exception_class, message, &block) err = assert_raises(exception_class) { block.call } assert_match message, err.message end end rails-4.2.6/activerecord/test/cases/fixture_set/000077500000000000000000000000001266740050600217335ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/fixture_set/file_test.rb000066400000000000000000000101551266740050600242400ustar00rootroot00000000000000require 'cases/helper' require 'tempfile' module ActiveRecord class FixtureSet class FileTest < ActiveRecord::TestCase def test_open fh = File.open(::File.join(FIXTURES_ROOT, "accounts.yml")) assert_equal 6, fh.to_a.length end def test_open_with_block called = false File.open(::File.join(FIXTURES_ROOT, "accounts.yml")) do |fh| called = true assert_equal 6, fh.to_a.length end assert called, 'block called' end def test_names File.open(::File.join(FIXTURES_ROOT, "accounts.yml")) do |fh| assert_equal ["signals37", "unknown", "rails_core_account", "last_account", "rails_core_account_2", "odegy_account"].sort, fh.to_a.map(&:first).sort end end def test_values File.open(::File.join(FIXTURES_ROOT, "accounts.yml")) do |fh| assert_equal [1,2,3,4,5,6].sort, fh.to_a.map(&:last).map { |x| x['id'] }.sort end end def test_erb_processing File.open(::File.join(FIXTURES_ROOT, "developers.yml")) do |fh| devs = Array.new(8) { |i| "dev_#{i + 3}" } assert_equal [], devs - fh.to_a.map(&:first) end end def test_empty_file tmp_yaml ['empty', 'yml'], '' do |t| assert_equal [], File.open(t.path) { |fh| fh.to_a } end end # A valid YAML file is not necessarily a value Fixture file. Make sure # an exception is raised if the format is not valid Fixture format. def test_wrong_fixture_format_string tmp_yaml ['empty', 'yml'], 'qwerty' do |t| assert_raises(ActiveRecord::Fixture::FormatError) do File.open(t.path) { |fh| fh.to_a } end end end def test_wrong_fixture_format_nested tmp_yaml ['empty', 'yml'], 'one: two' do |t| assert_raises(ActiveRecord::Fixture::FormatError) do File.open(t.path) { |fh| fh.to_a } end end end def test_render_context_helper ActiveRecord::FixtureSet.context_class.class_eval do def fixture_helper "Fixture helper" end end yaml = "one:\n name: <%= fixture_helper %>\n" tmp_yaml ['curious', 'yml'], yaml do |t| golden = [["one", {"name" => "Fixture helper"}]] assert_equal golden, File.open(t.path) { |fh| fh.to_a } end ActiveRecord::FixtureSet.context_class.class_eval do remove_method :fixture_helper end end def test_render_context_lookup_scope yaml = < ActiveRecord_FixtureSet: <%= defined? ActiveRecord::FixtureSet %> FixtureSet: <%= defined? FixtureSet %> ActiveRecord_FixtureSet_File: <%= defined? ActiveRecord::FixtureSet::File %> File: <%= File.name %> END golden = [['one', { 'ActiveRecord' => 'constant', 'ActiveRecord_FixtureSet' => 'constant', 'FixtureSet' => nil, 'ActiveRecord_FixtureSet_File' => 'constant', 'File' => 'File' }]] tmp_yaml ['curious', 'yml'], yaml do |t| assert_equal golden, File.open(t.path) { |fh| fh.to_a } end end # Make sure that each fixture gets its own rendering context so that # fixtures are independent. def test_independent_render_contexts yaml1 = "<% def leaked_method; 'leak'; end %>\n" yaml2 = "one:\n name: <%= leaked_method %>\n" tmp_yaml ['leaky', 'yml'], yaml1 do |t1| tmp_yaml ['curious', 'yml'], yaml2 do |t2| File.open(t1.path) { |fh| fh.to_a } assert_raises(NameError) do File.open(t2.path) { |fh| fh.to_a } end end end end private def tmp_yaml(name, contents) t = Tempfile.new name t.binmode t.write contents t.close yield t ensure t.close true end end end end rails-4.2.6/activerecord/test/cases/fixtures_test.rb000066400000000000000000000711601266740050600226340ustar00rootroot00000000000000require 'cases/helper' require 'models/admin' require 'models/admin/account' require 'models/admin/randomly_named_c1' require 'models/admin/user' require 'models/binary' require 'models/book' require 'models/bulb' require 'models/category' require 'models/company' require 'models/computer' require 'models/course' require 'models/developer' require 'models/computer' require 'models/joke' require 'models/matey' require 'models/parrot' require 'models/pirate' require 'models/doubloon' require 'models/post' require 'models/randomly_named_c1' require 'models/reply' require 'models/ship' require 'models/task' require 'models/topic' require 'models/traffic_light' require 'models/treasure' require 'tempfile' class FixturesTest < ActiveRecord::TestCase self.use_instantiated_fixtures = true self.use_transactional_fixtures = false # other_topics fixture should not be included here fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes, :binaries, :traffic_lights FIXTURES = %w( accounts binaries companies customers developers developers_projects entrants movies projects subscribers topics tasks ) MATCH_ATTRIBUTE_NAME = /[a-zA-Z][-\w]*/ def test_clean_fixtures FIXTURES.each do |name| fixtures = nil assert_nothing_raised { fixtures = create_fixtures(name).first } assert_kind_of(ActiveRecord::FixtureSet, fixtures) fixtures.each { |_name, fixture| fixture.each { |key, value| assert_match(MATCH_ATTRIBUTE_NAME, key) } } end end def test_broken_yaml_exception badyaml = Tempfile.new ['foo', '.yml'] badyaml.write 'a: : ' badyaml.flush dir = File.dirname badyaml.path name = File.basename badyaml.path, '.yml' assert_raises(ActiveRecord::Fixture::FormatError) do ActiveRecord::FixtureSet.create_fixtures(dir, name) end ensure badyaml.close badyaml.unlink end def test_create_fixtures fixtures = ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, "parrots") assert Parrot.find_by_name('Curious George'), 'George is not in the database' assert fixtures.detect { |f| f.name == 'parrots' }, "no fixtures named 'parrots' in #{fixtures.map(&:name).inspect}" end def test_multiple_clean_fixtures fixtures_array = nil assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) } assert_kind_of(Array, fixtures_array) fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::FixtureSet, fixtures) } end def test_create_symbol_fixtures fixtures = ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, :collections => Course) { Course.connection } assert Course.find_by_name('Collection'), 'course is not in the database' assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" end def test_attributes topics = create_fixtures("topics").first assert_equal("The First Topic", topics["first"]["title"]) assert_nil(topics["second"]["author_email_address"]) end def test_inserts create_fixtures("topics") first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'") assert_equal("The First Topic", first_row["title"]) second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'Mary'") assert_nil(second_row["author_email_address"]) end if ActiveRecord::Base.connection.supports_migrations? def test_inserts_with_pre_and_suffix # Reset cache to make finds on the new table work ActiveRecord::FixtureSet.reset_cache ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t| t.column :title, :string t.column :author_name, :string t.column :author_email_address, :string t.column :written_on, :datetime t.column :bonus_time, :time t.column :last_read, :date t.column :content, :string t.column :approved, :boolean, :default => true t.column :replies_count, :integer, :default => 0 t.column :parent_id, :integer t.column :type, :string, :limit => 50 end # Store existing prefix/suffix old_prefix = ActiveRecord::Base.table_name_prefix old_suffix = ActiveRecord::Base.table_name_suffix # Set a prefix/suffix we can test against ActiveRecord::Base.table_name_prefix = 'prefix_' ActiveRecord::Base.table_name_suffix = '_suffix' other_topic_klass = Class.new(ActiveRecord::Base) do def self.name "OtherTopic" end end topics = [create_fixtures("other_topics")].flatten.first # This checks for a caching problem which causes a bug in the fixtures # class-level configuration helper. assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create" first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'") assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found" assert_equal("The First Topic", first_row["title"]) second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'") assert_nil(second_row["author_email_address"]) assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym # This assertion should preferably be the last in the list, because calling # other_topic_klass.table_name sets a class-level instance variable assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym ensure # Restore prefix/suffix to its previous values ActiveRecord::Base.table_name_prefix = old_prefix ActiveRecord::Base.table_name_suffix = old_suffix ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil end end def test_insert_with_datetime create_fixtures("tasks") first = Task.find(1) assert first end def test_logger_level_invariant level = ActiveRecord::Base.logger.level create_fixtures('topics') assert_equal level, ActiveRecord::Base.logger.level end def test_instantiation topics = create_fixtures("topics").first assert_kind_of Topic, topics["first"].find end def test_complete_instantiation assert_equal "The First Topic", @first.title end def test_fixtures_from_root_yml_with_instantiation # assert_equal 2, @accounts.size assert_equal 50, @unknown.credit_limit end def test_erb_in_fixtures assert_equal "fixture_5", @dev_5.name end def test_empty_yaml_fixture assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts") end def test_empty_yaml_fixture_with_a_comment_in_it assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies") end def test_nonexistent_fixture_file nonexistent_fixture_path = FIXTURES_ROOT + "/imnothere" #sanity check to make sure that this file never exists assert Dir[nonexistent_fixture_path+"*"].empty? assert_raise(Errno::ENOENT) do ActiveRecord::FixtureSet.new( Account.connection, "companies", Company, nonexistent_fixture_path) end end def test_dirty_dirty_yaml_file assert_raise(ActiveRecord::Fixture::FormatError) do ActiveRecord::FixtureSet.new( Account.connection, "courses", Course, FIXTURES_ROOT + "/naked/yml/courses") end end def test_yaml_file_with_invalid_column e = assert_raise(ActiveRecord::Fixture::FixtureError) do ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots") end assert_equal(%(table "parrots" has no column named "arrr".), e.message) end def test_yaml_file_with_symbol_columns ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "trees") end def test_omap_fixtures assert_nothing_raised do fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered") fixtures.each.with_index do |(name, fixture), i| assert_equal "fixture_no_#{i}", name assert_equal "Category #{i}", fixture['name'] end end end def test_yml_file_in_subdirectory assert_equal(categories(:sub_special_1).name, "A special category in a subdir file") assert_equal(categories(:sub_special_1).class, SpecialCategory) end def test_subsubdir_file_with_arbitrary_name assert_equal(categories(:sub_special_3).name, "A special category in an arbitrarily named subsubdir file") assert_equal(categories(:sub_special_3).class, SpecialCategory) end def test_binary_in_fixtures data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read } data.force_encoding('ASCII-8BIT') data.freeze assert_equal data, @flowers.data end def test_serialized_fixtures assert_equal ["Green", "Red", "Orange"], traffic_lights(:uk).state end def test_fixtures_are_set_up_with_database_env_variable db_url_tmp = ENV['DATABASE_URL'] ENV['DATABASE_URL'] = "sqlite3::memory:" ActiveRecord::Base.stubs(:configurations).returns({}) test_case = Class.new(ActiveRecord::TestCase) do fixtures :accounts def test_fixtures assert accounts(:signals37) end end result = test_case.new(:test_fixtures).run assert result.passed?, "Expected #{result.name} to pass:\n#{result}" ensure ENV['DATABASE_URL'] = db_url_tmp end end class HasManyThroughFixture < ActiveSupport::TestCase def make_model(name) Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } end def test_has_many_through_with_default_table_name pt = make_model "ParrotTreasure" parrot = make_model "Parrot" treasure = make_model "Treasure" pt.table_name = "parrots_treasures" pt.belongs_to :parrot, :anonymous_class => parrot pt.belongs_to :treasure, :anonymous_class => treasure parrot.has_many :parrot_treasures, :anonymous_class => pt parrot.has_many :treasures, :through => :parrot_treasures parrots = File.join FIXTURES_ROOT, 'parrots' fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots rows = fs.table_rows assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrots_treasures'] end def test_has_many_through_with_renamed_table pt = make_model "ParrotTreasure" parrot = make_model "Parrot" treasure = make_model "Treasure" pt.belongs_to :parrot, :anonymous_class => parrot pt.belongs_to :treasure, :anonymous_class => treasure parrot.has_many :parrot_treasures, :anonymous_class => pt parrot.has_many :treasures, :through => :parrot_treasures parrots = File.join FIXTURES_ROOT, 'parrots' fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots rows = fs.table_rows assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrot_treasures'] end def load_has_and_belongs_to_many parrot = make_model "Parrot" parrot.has_and_belongs_to_many :treasures parrots = File.join FIXTURES_ROOT, 'parrots' fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots fs.table_rows end end if Account.connection.respond_to?(:reset_pk_sequence!) class FixturesResetPkSequenceTest < ActiveRecord::TestCase fixtures :accounts fixtures :companies def setup @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting'), Course.new(name: 'Test')] ActiveRecord::FixtureSet.reset_cache # make sure tables get reinitialized end def test_resets_to_min_pk_with_specified_pk_and_sequence @instances.each do |instance| model = instance.class model.delete_all model.connection.reset_pk_sequence!(model.table_name, model.primary_key, model.sequence_name) instance.save! assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed." end end def test_resets_to_min_pk_with_default_pk_and_sequence @instances.each do |instance| model = instance.class model.delete_all model.connection.reset_pk_sequence!(model.table_name) instance.save! assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed." end end def test_create_fixtures_resets_sequences_when_not_cached @instances.each do |instance| max_id = create_fixtures(instance.class.table_name).first.fixtures.inject(0) do |_max_id, (_, fixture)| fixture_id = fixture['id'].to_i fixture_id > _max_id ? fixture_id : _max_id end # Clone the last fixture to check that it gets the next greatest id. instance.save! assert_equal max_id + 1, instance.id, "Sequence reset for #{instance.class.table_name} failed." end end end end class FixturesWithoutInstantiationTest < ActiveRecord::TestCase self.use_instantiated_fixtures = false fixtures :topics, :developers, :accounts def test_without_complete_instantiation assert !defined?(@first) assert !defined?(@topics) assert !defined?(@developers) assert !defined?(@accounts) end def test_fixtures_from_root_yml_without_instantiation assert !defined?(@unknown), "@unknown is not defined" end def test_visibility_of_accessor_method assert_equal false, respond_to?(:topics, false), "should be private method" assert_equal true, respond_to?(:topics, true), "confirm to respond surely" end def test_accessor_methods assert_equal "The First Topic", topics(:first).title assert_equal "Jamis", developers(:jamis).name assert_equal 50, accounts(:signals37).credit_limit end def test_accessor_methods_with_multiple_args assert_equal 2, topics(:first, :second).size assert_raise(StandardError) { topics([:first, :second]) } end def test_reloading_fixtures_through_accessor_methods assert_equal "The First Topic", topics(:first).title @loaded_fixtures['topics']['first'].expects(:find).returns(stub(:title => "Fresh Topic!")) assert_equal "Fresh Topic!", topics(:first, true).title end end class FixturesWithoutInstanceInstantiationTest < ActiveRecord::TestCase self.use_instantiated_fixtures = true self.use_instantiated_fixtures = :no_instances fixtures :topics, :developers, :accounts def test_without_instance_instantiation assert !defined?(@first), "@first is not defined" end end class TransactionalFixturesTest < ActiveRecord::TestCase self.use_instantiated_fixtures = true self.use_transactional_fixtures = true fixtures :topics def test_destroy assert_not_nil @first @first.destroy end def test_destroy_just_kidding assert_not_nil @first end end class MultipleFixturesTest < ActiveRecord::TestCase fixtures :topics fixtures :developers, :accounts def test_fixture_table_names assert_equal %w(topics developers accounts), fixture_table_names end end class SetupTest < ActiveRecord::TestCase # fixtures :topics def setup @first = true end def test_nothing end end class SetupSubclassTest < SetupTest def setup super @second = true end def test_subclassing_should_preserve_setups assert @first assert @second end end class OverlappingFixturesTest < ActiveRecord::TestCase fixtures :topics, :developers fixtures :developers, :accounts def test_fixture_table_names assert_equal %w(topics developers accounts), fixture_table_names end end class ForeignKeyFixturesTest < ActiveRecord::TestCase fixtures :fk_test_has_pk, :fk_test_has_fk # if foreign keys are implemented and fixtures # are not deleted in reverse order then this test # case will raise StatementInvalid def test_number1 assert true end def test_number2 assert true end end class OverRideFixtureMethodTest < ActiveRecord::TestCase fixtures :topics def topics(name) topic = super topic.title = 'omg' topic end def test_fixture_methods_can_be_overridden x = topics :first assert_equal 'omg', x.title end end class CheckSetTableNameFixturesTest < ActiveRecord::TestCase set_fixture_class :funny_jokes => Joke fixtures :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class self.use_transactional_fixtures = false def test_table_method assert_kind_of Joke, funny_jokes(:a_joke) end end class FixtureNameIsNotTableNameFixturesTest < ActiveRecord::TestCase set_fixture_class :items => Book fixtures :items # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class self.use_transactional_fixtures = false def test_named_accessor assert_kind_of Book, items(:dvd) end end class FixtureNameIsNotTableNameMultipleFixturesTest < ActiveRecord::TestCase set_fixture_class :items => Book, :funny_jokes => Joke fixtures :items, :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class self.use_transactional_fixtures = false def test_named_accessor_of_differently_named_fixture assert_kind_of Book, items(:dvd) end def test_named_accessor_of_same_named_fixture assert_kind_of Joke, funny_jokes(:a_joke) end end class CustomConnectionFixturesTest < ActiveRecord::TestCase set_fixture_class :courses => Course fixtures :courses self.use_transactional_fixtures = false def test_leaky_destroy assert_nothing_raised { courses(:ruby) } courses(:ruby).destroy end def test_it_twice_in_whatever_order_to_check_for_fixture_leakage test_leaky_destroy end end class TransactionalFixturesOnCustomConnectionTest < ActiveRecord::TestCase set_fixture_class :courses => Course fixtures :courses self.use_transactional_fixtures = true def test_leaky_destroy assert_nothing_raised { courses(:ruby) } courses(:ruby).destroy end def test_it_twice_in_whatever_order_to_check_for_fixture_leakage test_leaky_destroy end end class InvalidTableNameFixturesTest < ActiveRecord::TestCase fixtures :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our lack of set_fixture_class self.use_transactional_fixtures = false def test_raises_error assert_raise ActiveRecord::FixtureClassNotFound do funny_jokes(:a_joke) end end end class CheckEscapedYamlFixturesTest < ActiveRecord::TestCase set_fixture_class :funny_jokes => Joke fixtures :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class self.use_transactional_fixtures = false def test_proper_escaped_fixture assert_equal "The \\n Aristocrats\nAte the candy\n", funny_jokes(:another_joke).name end end class DevelopersProject; end class ManyToManyFixturesWithClassDefined < ActiveRecord::TestCase fixtures :developers_projects def test_this_should_run_cleanly assert true end end class FixturesBrokenRollbackTest < ActiveRecord::TestCase def blank_setup @fixture_connections = [ActiveRecord::Base.connection] end alias_method :ar_setup_fixtures, :setup_fixtures alias_method :setup_fixtures, :blank_setup alias_method :setup, :blank_setup def blank_teardown; end alias_method :ar_teardown_fixtures, :teardown_fixtures alias_method :teardown_fixtures, :blank_teardown alias_method :teardown, :blank_teardown def test_no_rollback_in_teardown_unless_transaction_active assert_equal 0, ActiveRecord::Base.connection.open_transactions assert_raise(RuntimeError) { ar_setup_fixtures } assert_equal 0, ActiveRecord::Base.connection.open_transactions assert_nothing_raised { ar_teardown_fixtures } assert_equal 0, ActiveRecord::Base.connection.open_transactions end private def load_fixtures(config) raise 'argh' end end class LoadAllFixturesTest < ActiveRecord::TestCase def test_all_there self.class.fixture_path = FIXTURES_ROOT + "/all" self.class.fixtures :all if File.symlink? FIXTURES_ROOT + "/all/admin" assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort end ensure ActiveRecord::FixtureSet.reset_cache end end class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase def test_all_there self.class.fixture_path = Pathname.new(FIXTURES_ROOT).join('all') self.class.fixtures :all if File.symlink? FIXTURES_ROOT + "/all/admin" assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort end ensure ActiveRecord::FixtureSet.reset_cache end end class FasterFixturesTest < ActiveRecord::TestCase self.use_transactional_fixtures = false fixtures :categories, :authors def load_extra_fixture(name) fixture = create_fixtures(name).first assert fixture.is_a?(ActiveRecord::FixtureSet) @loaded_fixtures[fixture.table_name] = fixture end def test_cache assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'categories') assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'authors') assert_no_queries do create_fixtures('categories') create_fixtures('authors') end load_extra_fixture('posts') assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') self.class.setup_fixture_accessors :posts assert_equal 'Welcome to the weblog', posts(:welcome).title end end class FoxyFixturesTest < ActiveRecord::TestCase fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users" if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' require 'models/uuid_parent' require 'models/uuid_child' fixtures :uuid_parents, :uuid_children end def test_identifies_strings assert_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("foo")) assert_not_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("FOO")) end def test_identifies_symbols assert_equal(ActiveRecord::FixtureSet.identify(:foo), ActiveRecord::FixtureSet.identify(:foo)) end def test_identifies_consistently assert_equal 207281424, ActiveRecord::FixtureSet.identify(:ruby) assert_equal 1066363776, ActiveRecord::FixtureSet.identify(:sapphire_2) assert_equal 'f92b6bda-0d0d-5fe1-9124-502b18badded', ActiveRecord::FixtureSet.identify(:daddy, :uuid) assert_equal 'b4b10018-ad47-595d-b42f-d8bdaa6d01bf', ActiveRecord::FixtureSet.identify(:sonny, :uuid) end TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) def test_populates_timestamp_columns TIMESTAMP_COLUMNS.each do |property| assert_not_nil(parrots(:george).send(property), "should set #{property}") end end def test_does_not_populate_timestamp_columns_if_model_has_set_record_timestamps_to_false TIMESTAMP_COLUMNS.each do |property| assert_nil(ships(:black_pearl).send(property), "should not set #{property}") end end def test_populates_all_columns_with_the_same_time last = nil TIMESTAMP_COLUMNS.each do |property| current = parrots(:george).send(property) last ||= current assert_equal(last, current) last = current end end def test_only_populates_columns_that_exist assert_not_nil(pirates(:blackbeard).created_on) assert_not_nil(pirates(:blackbeard).updated_on) end def test_preserves_existing_fixture_data assert_equal(2.weeks.ago.to_date, pirates(:redbeard).created_on.to_date) assert_equal(2.weeks.ago.to_date, pirates(:redbeard).updated_on.to_date) end def test_generates_unique_ids assert_not_nil(parrots(:george).id) assert_not_equal(parrots(:george).id, parrots(:louis).id) end def test_automatically_sets_primary_key assert_not_nil(ships(:black_pearl)) end def test_preserves_existing_primary_key assert_equal(2, ships(:interceptor).id) end def test_resolves_belongs_to_symbols assert_equal(parrots(:george), pirates(:blackbeard).parrot) end def test_ignores_belongs_to_symbols_if_association_and_foreign_key_are_named_the_same assert_equal(developers(:david), computers(:workstation).developer) end def test_supports_join_tables assert(pirates(:blackbeard).parrots.include?(parrots(:george))) assert(pirates(:blackbeard).parrots.include?(parrots(:louis))) assert(parrots(:george).pirates.include?(pirates(:blackbeard))) end def test_supports_inline_habtm assert(parrots(:george).treasures.include?(treasures(:diamond))) assert(parrots(:george).treasures.include?(treasures(:sapphire))) assert(!parrots(:george).treasures.include?(treasures(:ruby))) end def test_supports_inline_habtm_with_specified_id assert(parrots(:polly).treasures.include?(treasures(:ruby))) assert(parrots(:polly).treasures.include?(treasures(:sapphire))) assert(!parrots(:polly).treasures.include?(treasures(:diamond))) end def test_supports_yaml_arrays assert(parrots(:louis).treasures.include?(treasures(:diamond))) assert(parrots(:louis).treasures.include?(treasures(:sapphire))) end def test_strips_DEFAULTS_key assert_raise(StandardError) { parrots(:DEFAULTS) } # this lets us do YAML defaults and not have an extra fixture entry %w(sapphire ruby).each { |t| assert(parrots(:davey).treasures.include?(treasures(t))) } end def test_supports_label_interpolation assert_equal("frederick", parrots(:frederick).name) end def test_supports_label_string_interpolation assert_equal("X marks the spot!", pirates(:mark).catchphrase) end def test_supports_polymorphic_belongs_to assert_equal(pirates(:redbeard), treasures(:sapphire).looter) assert_equal(parrots(:louis), treasures(:ruby).looter) end def test_only_generates_a_pk_if_necessary m = Matey.first m.pirate = pirates(:blackbeard) m.target = pirates(:redbeard) end def test_supports_sti assert_kind_of DeadParrot, parrots(:polly) assert_equal pirates(:blackbeard), parrots(:polly).killer end def test_namespaced_models assert admin_accounts(:signals37).users.include?(admin_users(:david)) assert_equal 2, admin_accounts(:signals37).users.size end end class ActiveSupportSubclassWithFixturesTest < ActiveRecord::TestCase fixtures :parrots # This seemingly useless assertion catches a bug that caused the fixtures # setup code call nil[] def test_foo assert_equal parrots(:louis), Parrot.find_by_name("King Louis") end end class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase ActiveRecord::FixtureSet.reset_cache set_fixture_class :randomly_named_a9 => ClassNameThatDoesNotFollowCONVENTIONS, :'admin/randomly_named_a9' => Admin::ClassNameThatDoesNotFollowCONVENTIONS, 'admin/randomly_named_b0' => Admin::ClassNameThatDoesNotFollowCONVENTIONS fixtures :randomly_named_a9, 'admin/randomly_named_a9', :'admin/randomly_named_b0' def test_named_accessor_for_randomly_named_fixture_and_class assert_kind_of ClassNameThatDoesNotFollowCONVENTIONS, randomly_named_a9(:first_instance) end def test_named_accessor_for_randomly_named_namespaced_fixture_and_class assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, admin_randomly_named_a9(:first_instance) assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, admin_randomly_named_b0(:second_instance) end def test_table_name_is_defined_in_the_model assert_equal 'randomly_named_table', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name end end class FixturesWithDefaultScopeTest < ActiveRecord::TestCase fixtures :bulbs test "inserts fixtures excluded by a default scope" do assert_equal 1, Bulb.count assert_equal 2, Bulb.unscoped.count end test "allows access to fixtures excluded by a default scope" do assert_equal "special", bulbs(:special).name end end class FixturesWithAbstractBelongsTo < ActiveRecord::TestCase fixtures :pirates, :doubloons test "creates fixtures with belongs_to associations defined in abstract base classes" do assert_not_nil doubloons(:blackbeards_doubloon) assert_equal pirates(:blackbeard), doubloons(:blackbeards_doubloon).pirate end end rails-4.2.6/activerecord/test/cases/forbidden_attributes_protection_test.rb000066400000000000000000000053121266740050600274270ustar00rootroot00000000000000require 'cases/helper' require 'active_support/core_ext/hash/indifferent_access' require 'models/person' require 'models/company' class ProtectedParams < ActiveSupport::HashWithIndifferentAccess attr_accessor :permitted alias :permitted? :permitted def initialize(attributes) super(attributes) @permitted = false end def permit! @permitted = true self end def dup super.tap do |duplicate| duplicate.instance_variable_set :@permitted, @permitted end end end class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase def test_forbidden_attributes_cannot_be_used_for_mass_assignment params = ProtectedParams.new(first_name: 'Guille', gender: 'm') assert_raises(ActiveModel::ForbiddenAttributesError) do Person.new(params) end end def test_permitted_attributes_can_be_used_for_mass_assignment params = ProtectedParams.new(first_name: 'Guille', gender: 'm') params.permit! person = Person.new(params) assert_equal 'Guille', person.first_name assert_equal 'm', person.gender end def test_forbidden_attributes_cannot_be_used_for_sti_inheritance_column params = ProtectedParams.new(type: 'Client') assert_raises(ActiveModel::ForbiddenAttributesError) do Company.new(params) end end def test_permitted_attributes_can_be_used_for_sti_inheritance_column params = ProtectedParams.new(type: 'Client') params.permit! person = Company.new(params) assert_equal person.class, Client end def test_regular_hash_should_still_be_used_for_mass_assignment person = Person.new(first_name: 'Guille', gender: 'm') assert_equal 'Guille', person.first_name assert_equal 'm', person.gender end def test_blank_attributes_should_not_raise person = Person.new assert_nil person.assign_attributes(ProtectedParams.new({})) end def test_create_with_checks_permitted params = ProtectedParams.new(first_name: 'Guille', gender: 'm') assert_raises(ActiveModel::ForbiddenAttributesError) do Person.create_with(params).create! end end def test_create_with_works_with_params_values params = ProtectedParams.new(first_name: 'Guille') person = Person.create_with(first_name: params[:first_name]).create! assert_equal 'Guille', person.first_name end def test_where_checks_permitted params = ProtectedParams.new(first_name: 'Guille', gender: 'm') assert_raises(ActiveModel::ForbiddenAttributesError) do Person.where(params).create! end end def test_where_works_with_params_values params = ProtectedParams.new(first_name: 'Guille') person = Person.where(first_name: params[:first_name]).create! assert_equal 'Guille', person.first_name end end rails-4.2.6/activerecord/test/cases/habtm_destroy_order_test.rb000066400000000000000000000034501266740050600250170ustar00rootroot00000000000000require "cases/helper" require "models/lesson" require "models/student" class HabtmDestroyOrderTest < ActiveRecord::TestCase test "may not delete a lesson with students" do sicp = Lesson.new(:name => "SICP") ben = Student.new(:name => "Ben Bitdiddle") sicp.students << ben sicp.save! assert_raises LessonError do assert_no_difference('Lesson.count') do sicp.destroy end end assert !sicp.destroyed? end test 'should not raise error if have foreign key in the join table' do student = Student.new(:name => "Ben Bitdiddle") lesson = Lesson.new(:name => "SICP") lesson.students << student lesson.save! assert_nothing_raised do student.destroy end end test "not destroying a student with lessons leaves student<=>lesson association intact" do # test a normal before_destroy doesn't destroy the habtm joins begin sicp = Lesson.new(:name => "SICP") ben = Student.new(:name => "Ben Bitdiddle") # add a before destroy to student Student.class_eval do before_destroy do raise ActiveRecord::Rollback unless lessons.empty? end end ben.lessons << sicp ben.save! ben.destroy assert !ben.reload.lessons.empty? ensure # get rid of it so Student is still like it was Student.reset_callbacks(:destroy) end end test "not destroying a lesson with students leaves student<=>lesson association intact" do # test a more aggressive before_destroy doesn't destroy the habtm joins and still throws the exception sicp = Lesson.new(:name => "SICP") ben = Student.new(:name => "Ben Bitdiddle") sicp.students << ben sicp.save! assert_raises LessonError do sicp.destroy end assert !sicp.reload.students.empty? end end rails-4.2.6/activerecord/test/cases/helper.rb000066400000000000000000000133551266740050600212050ustar00rootroot00000000000000require File.expand_path('../../../../load_paths', __FILE__) require 'config' require 'active_support/testing/autorun' require 'stringio' require 'active_record' require 'cases/test_case' require 'active_support/dependencies' require 'active_support/logger' require 'active_support/core_ext/string/strip' require 'support/config' require 'support/connection' # TODO: Move all these random hacks into the ARTest namespace and into the support/ dir Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false # Enable raise errors in after_commit and after_rollback. ActiveRecord::Base.raise_in_transactional_callbacks = true # Connect to the database ARTest.connect # Quote "type" if it's a reserved word for the current connection. QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') def current_adapter?(*types) types.any? do |type| ActiveRecord::ConnectionAdapters.const_defined?(type) && ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters.const_get(type)) end end def in_memory_db? current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:" end def mysql_56? current_adapter?(:Mysql2Adapter) && ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0" end def mysql_enforcing_gtid_consistency? current_adapter?(:MysqlAdapter, :Mysql2Adapter) && 'ON' == ActiveRecord::Base.connection.show_variable('enforce_gtid_consistency') end def supports_savepoints? ActiveRecord::Base.connection.supports_savepoints? end def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz yield ensure old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end def with_timezone_config(cfg) verify_default_timezone_config old_default_zone = ActiveRecord::Base.default_timezone old_awareness = ActiveRecord::Base.time_zone_aware_attributes old_zone = Time.zone if cfg.has_key?(:default) ActiveRecord::Base.default_timezone = cfg[:default] end if cfg.has_key?(:aware_attributes) ActiveRecord::Base.time_zone_aware_attributes = cfg[:aware_attributes] end if cfg.has_key?(:zone) Time.zone = cfg[:zone] end yield ensure ActiveRecord::Base.default_timezone = old_default_zone ActiveRecord::Base.time_zone_aware_attributes = old_awareness Time.zone = old_zone end # This method makes sure that tests don't leak global state related to time zones. EXPECTED_ZONE = nil EXPECTED_DEFAULT_TIMEZONE = :utc EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES = false def verify_default_timezone_config if Time.zone != EXPECTED_ZONE $stderr.puts <<-MSG \n#{self} Global state `Time.zone` was leaked. Expected: #{EXPECTED_ZONE} Got: #{Time.zone} MSG end if ActiveRecord::Base.default_timezone != EXPECTED_DEFAULT_TIMEZONE $stderr.puts <<-MSG \n#{self} Global state `ActiveRecord::Base.default_timezone` was leaked. Expected: #{EXPECTED_DEFAULT_TIMEZONE} Got: #{ActiveRecord::Base.default_timezone} MSG end if ActiveRecord::Base.time_zone_aware_attributes != EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES $stderr.puts <<-MSG \n#{self} Global state `ActiveRecord::Base.time_zone_aware_attributes` was leaked. Expected: #{EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES} Got: #{ActiveRecord::Base.time_zone_aware_attributes} MSG end end def enable_extension!(extension, connection) return false unless connection.supports_extensions? return connection.reconnect! if connection.extension_enabled?(extension) connection.enable_extension extension connection.commit_db_transaction connection.reconnect! end def disable_extension!(extension, connection) return false unless connection.supports_extensions? return true unless connection.extension_enabled?(extension) connection.disable_extension extension connection.reconnect! end require "cases/validations_repair_helper" class ActiveSupport::TestCase include ActiveRecord::TestFixtures include ActiveRecord::ValidationsRepairHelper self.fixture_path = FIXTURES_ROOT self.use_instantiated_fixtures = false self.use_transactional_fixtures = true def create_fixtures(*fixture_set_names, &block) ActiveRecord::FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block) end end def load_schema # silence verbose schema loading original_stdout = $stdout $stdout = StringIO.new adapter_name = ActiveRecord::Base.connection.adapter_name.downcase adapter_specific_schema_file = SCHEMA_ROOT + "/#{adapter_name}_specific_schema.rb" load SCHEMA_ROOT + "/schema.rb" if File.exist?(adapter_specific_schema_file) load adapter_specific_schema_file end ensure $stdout = original_stdout end load_schema class SQLSubscriber attr_reader :logged attr_reader :payloads def initialize @logged = [] @payloads = [] end def start(name, id, payload) @payloads << payload @logged << [payload[:sql].squish, payload[:name], payload[:binds]] end def finish(name, id, payload); end end module InTimeZone private def in_time_zone(zone) old_zone = Time.zone old_tz = ActiveRecord::Base.time_zone_aware_attributes Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? yield ensure Time.zone = old_zone ActiveRecord::Base.time_zone_aware_attributes = old_tz end end require 'mocha/setup' # FIXME: stop using mocha # FIXME: we have tests that depend on run order, we should fix that and # remove this method call. require 'active_support/test_case' ActiveSupport::TestCase.test_order = :sorted rails-4.2.6/activerecord/test/cases/hot_compatibility_test.rb000066400000000000000000000025051266740050600245030ustar00rootroot00000000000000require 'cases/helper' class HotCompatibilityTest < ActiveRecord::TestCase self.use_transactional_fixtures = false setup do @klass = Class.new(ActiveRecord::Base) do connection.create_table :hot_compatibilities, force: true do |t| t.string :foo t.string :bar end def self.name; 'HotCompatibility'; end end end teardown do ActiveRecord::Base.connection.drop_table :hot_compatibilities end test "insert after remove_column" do # warm cache @klass.create! # we have 3 columns assert_equal 3, @klass.columns.length # remove one of them @klass.connection.remove_column :hot_compatibilities, :bar # we still have 3 columns in the cache assert_equal 3, @klass.columns.length # but we can successfully create a record so long as we don't # reference the removed column record = @klass.create! foo: 'foo' record.reload assert_equal 'foo', record.foo end test "update after remove_column" do record = @klass.create! foo: 'foo' assert_equal 3, @klass.columns.length @klass.connection.remove_column :hot_compatibilities, :bar assert_equal 3, @klass.columns.length record.reload assert_equal 'foo', record.foo record.foo = 'bar' record.save! record.reload assert_equal 'bar', record.foo end end rails-4.2.6/activerecord/test/cases/i18n_test.rb000066400000000000000000000034621266740050600215420ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/reply' class ActiveRecordI18nTests < ActiveRecord::TestCase def setup I18n.backend = I18n::Backend::Simple.new end def test_translated_model_attributes I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } assert_equal 'topic title attribute', Topic.human_attribute_name('title') end def test_translated_model_attributes_with_symbols I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } assert_equal 'topic title attribute', Topic.human_attribute_name(:title) end def test_translated_model_attributes_with_sti I18n.backend.store_translations 'en', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } } assert_equal 'reply title attribute', Reply.human_attribute_name('title') end def test_translated_model_attributes_with_sti_fallback I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } assert_equal 'topic title attribute', Reply.human_attribute_name('title') end def test_translated_model_names I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} } assert_equal 'topic model', Topic.model_name.human end def test_translated_model_names_with_sti I18n.backend.store_translations 'en', :activerecord => {:models => {:reply => 'reply model'} } assert_equal 'reply model', Reply.model_name.human end def test_translated_model_names_with_sti_fallback I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} } assert_equal 'topic model', Reply.model_name.human end end rails-4.2.6/activerecord/test/cases/inheritance_test.rb000066400000000000000000000315751266740050600232620ustar00rootroot00000000000000require 'cases/helper' require 'models/company' require 'models/person' require 'models/post' require 'models/project' require 'models/subscriber' require 'models/vegetables' require 'models/shop' class InheritanceTest < ActiveRecord::TestCase fixtures :companies, :projects, :subscribers, :accounts, :vegetables def test_class_with_store_full_sti_class_returns_full_name old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true assert_equal 'Namespaced::Company', Namespaced::Company.sti_name ensure ActiveRecord::Base.store_full_sti_class = old end def test_class_with_blank_sti_name company = Company.first company = company.dup company.extend(Module.new { def _read_attribute(name) return ' ' if name == 'type' super end }) company.save! company = Company.all.to_a.find { |x| x.id == company.id } assert_equal ' ', company.type end def test_class_without_store_full_sti_class_returns_demodulized_name old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = false assert_equal 'Company', Namespaced::Company.sti_name ensure ActiveRecord::Base.store_full_sti_class = old end def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = false item = Namespaced::Company.new assert_equal 'Company', item[:type] ensure ActiveRecord::Base.store_full_sti_class = old end def test_should_store_full_class_name_with_store_full_sti_class_option_enabled old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true item = Namespaced::Company.new assert_equal 'Namespaced::Company', item[:type] ensure ActiveRecord::Base.store_full_sti_class = old end def test_different_namespace_subclass_should_load_correctly_with_store_full_sti_class_option old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true item = Namespaced::Company.create :name => "Wolverine 2" assert_not_nil Company.find(item.id) assert_not_nil Namespaced::Company.find(item.id) ensure ActiveRecord::Base.store_full_sti_class = old end def test_company_descends_from_active_record assert !ActiveRecord::Base.descends_from_active_record? assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base' assert Company.descends_from_active_record?, 'Company should descend from ActiveRecord::Base' assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base' end def test_inheritance_base_class assert_equal Post, Post.base_class assert_equal Post, SpecialPost.base_class assert_equal Post, StiPost.base_class assert_equal SubStiPost, SubStiPost.base_class end def test_abstract_inheritance_base_class assert_equal LoosePerson, LoosePerson.base_class assert_equal LooseDescendant, LooseDescendant.base_class assert_equal TightPerson, TightPerson.base_class assert_equal TightPerson, TightDescendant.base_class end def test_base_class_activerecord_error klass = Class.new { include ActiveRecord::Inheritance } assert_raise(ActiveRecord::ActiveRecordError) { klass.base_class } end def test_a_bad_type_column Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')" assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) } end def test_inheritance_find assert_kind_of Firm, Company.find(1), "37signals should be a firm" assert_kind_of Firm, Firm.find(1), "37signals should be a firm" assert_kind_of Client, Company.find(2), "Summit should be a client" assert_kind_of Client, Client.find(2), "Summit should be a client" end def test_alt_inheritance_find assert_kind_of Cucumber, Vegetable.find(1) assert_kind_of Cucumber, Cucumber.find(1) assert_kind_of Cabbage, Vegetable.find(2) assert_kind_of Cabbage, Cabbage.find(2) end def test_alt_becomes_works_with_sti vegetable = Vegetable.find(1) assert_kind_of Vegetable, vegetable cabbage = vegetable.becomes(Cabbage) assert_kind_of Cabbage, cabbage end def test_becomes_and_change_tracking_for_inheritance_columns cucumber = Vegetable.find(1) cabbage = cucumber.becomes!(Cabbage) assert_equal ['Cucumber', 'Cabbage'], cabbage.custom_type_change end def test_alt_becomes_bang_resets_inheritance_type_column vegetable = Vegetable.create!(name: "Red Pepper") assert_nil vegetable.custom_type cabbage = vegetable.becomes!(Cabbage) assert_equal "Cabbage", cabbage.custom_type vegetable = cabbage.becomes!(Vegetable) assert_nil cabbage.custom_type end def test_inheritance_find_all companies = Company.all.merge!(:order => 'id').to_a assert_kind_of Firm, companies[0], "37signals should be a firm" assert_kind_of Client, companies[1], "Summit should be a client" end def test_alt_inheritance_find_all companies = Vegetable.all.merge!(:order => 'id').to_a assert_kind_of Cucumber, companies[0] assert_kind_of Cabbage, companies[1] end def test_inheritance_save firm = Firm.new firm.name = "Next Angle" firm.save next_angle = Company.find(firm.id) assert_kind_of Firm, next_angle, "Next Angle should be a firm" end def test_alt_inheritance_save cabbage = Cabbage.new(:name => 'Savoy') cabbage.save! savoy = Vegetable.find(cabbage.id) assert_kind_of Cabbage, savoy end def test_inheritance_new_with_default_class company = Company.new assert_equal Company, company.class end def test_inheritance_new_with_base_class company = Company.new(:type => 'Company') assert_equal Company, company.class end def test_inheritance_new_with_subclass firm = Company.new(:type => 'Firm') assert_equal Firm, firm.class end def test_new_with_abstract_class e = assert_raises(NotImplementedError) do AbstractCompany.new end assert_equal("AbstractCompany is an abstract class and cannot be instantiated.", e.message) end def test_new_with_ar_base e = assert_raises(NotImplementedError) do ActiveRecord::Base.new end assert_equal("ActiveRecord::Base is an abstract class and cannot be instantiated.", e.message) end def test_new_with_invalid_type assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'InvalidType') } end def test_new_with_unrelated_type assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') } end def test_new_with_complex_inheritance assert_nothing_raised { Client.new(type: 'VerySpecialClient') } end def test_new_with_autoload_paths path = File.expand_path('../../models/autoloadable', __FILE__) ActiveSupport::Dependencies.autoload_paths << path firm = Company.new(:type => 'ExtraFirm') assert_equal ExtraFirm, firm.class ensure ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path } ActiveSupport::Dependencies.clear end def test_inheritance_condition assert_equal 11, Company.count assert_equal 2, Firm.count assert_equal 5, Client.count end def test_alt_inheritance_condition assert_equal 4, Vegetable.count assert_equal 1, Cucumber.count assert_equal 3, Cabbage.count end def test_finding_incorrect_type_data assert_raise(ActiveRecord::RecordNotFound) { Firm.find(2) } assert_nothing_raised { Firm.find(1) } end def test_alt_finding_incorrect_type_data assert_raise(ActiveRecord::RecordNotFound) { Cucumber.find(2) } assert_nothing_raised { Cucumber.find(1) } end def test_update_all_within_inheritance Client.update_all "name = 'I am a client'" assert_equal "I am a client", Client.first.name # Order by added as otherwise Oracle tests were failing because of different order of results assert_equal "37signals", Firm.all.merge!(:order => "id").to_a.first.name end def test_alt_update_all_within_inheritance Cabbage.update_all "name = 'the cabbage'" assert_equal "the cabbage", Cabbage.first.name assert_equal ["my cucumber"], Cucumber.all.map(&:name).uniq end def test_destroy_all_within_inheritance Client.destroy_all assert_equal 0, Client.count assert_equal 2, Firm.count end def test_alt_destroy_all_within_inheritance Cabbage.destroy_all assert_equal 0, Cabbage.count assert_equal 1, Cucumber.count end def test_find_first_within_inheritance assert_kind_of Firm, Company.all.merge!(:where => "name = '37signals'").first assert_kind_of Firm, Firm.all.merge!(:where => "name = '37signals'").first assert_nil Client.all.merge!(:where => "name = '37signals'").first end def test_alt_find_first_within_inheritance assert_kind_of Cabbage, Vegetable.all.merge!(:where => "name = 'his cabbage'").first assert_kind_of Cabbage, Cabbage.all.merge!(:where => "name = 'his cabbage'").first assert_nil Cucumber.all.merge!(:where => "name = 'his cabbage'").first end def test_complex_inheritance very_special_client = VerySpecialClient.create("name" => "veryspecial") assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first assert_equal very_special_client, SpecialClient.all.merge!(:where => "name = 'veryspecial'").first assert_equal very_special_client, Company.all.merge!(:where => "name = 'veryspecial'").first assert_equal very_special_client, Client.all.merge!(:where => "name = 'veryspecial'").first assert_equal 1, Client.all.merge!(:where => "name = 'Summit'").to_a.size assert_equal very_special_client, Client.find(very_special_client.id) end def test_alt_complex_inheritance king_cole = KingCole.create("name" => "uniform heads") assert_equal king_cole, KingCole.where("name = 'uniform heads'").first assert_equal king_cole, GreenCabbage.all.merge!(:where => "name = 'uniform heads'").first assert_equal king_cole, Cabbage.all.merge!(:where => "name = 'uniform heads'").first assert_equal king_cole, Vegetable.all.merge!(:where => "name = 'uniform heads'").first assert_equal 1, Cabbage.all.merge!(:where => "name = 'his cabbage'").to_a.size assert_equal king_cole, Cabbage.find(king_cole.id) end def test_eager_load_belongs_to_something_inherited account = Account.all.merge!(:includes => :firm).find(1) assert account.association(:firm).loaded?, "association was not eager loaded" end def test_alt_eager_loading cabbage = RedCabbage.all.merge!(:includes => :seller).find(4) assert cabbage.association(:seller).loaded?, "association was not eager loaded" end def test_eager_load_belongs_to_primary_key_quoting con = Account.connection assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)/) do Account.all.merge!(:includes => :firm).find(1) end end def test_inherits_custom_primary_key assert_equal Subscriber.primary_key, SpecialSubscriber.primary_key end def test_inheritance_without_mapping assert_kind_of SpecialSubscriber, SpecialSubscriber.find("webster132") assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = 'roger'; s.save } end def test_scope_inherited_properly assert_nothing_raised { Company.of_first_firm } assert_nothing_raised { Client.of_first_firm } end end class InheritanceComputeTypeTest < ActiveRecord::TestCase fixtures :companies def setup ActiveSupport::Dependencies.log_activity = true end teardown do ActiveSupport::Dependencies.log_activity = false self.class.const_remove :FirmOnTheFly rescue nil Firm.const_remove :FirmOnTheFly rescue nil end def test_instantiation_doesnt_try_to_require_corresponding_file ActiveRecord::Base.store_full_sti_class = false foo = Firm.first.clone foo.type = 'FirmOnTheFly' foo.save! # Should fail without FirmOnTheFly in the type condition. assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) } # Nest FirmOnTheFly in the test case where Dependencies won't see it. self.class.const_set :FirmOnTheFly, Class.new(Firm) assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) } # Nest FirmOnTheFly in Firm where Dependencies will see it. # This is analogous to nesting models in a migration. Firm.const_set :FirmOnTheFly, Class.new(Firm) # And instantiate will find the existing constant rather than trying # to require firm_on_the_fly. assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) } ensure ActiveRecord::Base.store_full_sti_class = true end def test_sti_type_from_attributes_disabled_in_non_sti_class phone = Shop::Product::Type.new(name: 'Phone') product = Shop::Product.new(:type => phone) assert product.save end end rails-4.2.6/activerecord/test/cases/integration_test.rb000066400000000000000000000074431266740050600233110ustar00rootroot00000000000000# encoding: utf-8 require 'cases/helper' require 'models/company' require 'models/developer' require 'models/computer' require 'models/owner' require 'models/pet' class IntegrationTest < ActiveRecord::TestCase fixtures :companies, :developers, :owners, :pets def test_to_param_should_return_string assert_kind_of String, Client.first.to_param end def test_to_param_returns_nil_if_not_persisted client = Client.new assert_equal nil, client.to_param end def test_to_param_returns_id_if_not_persisted_but_id_is_set client = Client.new client.id = 1 assert_equal '1', client.to_param end def test_to_param_class_method firm = Firm.find(4) assert_equal '4-flamboyant-software', firm.to_param end def test_to_param_class_method_truncates firm = Firm.find(4) firm.name = 'a ' * 100 assert_equal '4-a-a-a-a-a-a-a-a-a', firm.to_param end def test_to_param_class_method_truncates_edge_case firm = Firm.find(4) firm.name = 'David HeinemeierHansson' assert_equal '4-david', firm.to_param end def test_to_param_class_method_squishes firm = Firm.find(4) firm.name = "ab \n" * 100 assert_equal '4-ab-ab-ab-ab-ab-ab', firm.to_param end def test_to_param_class_method_multibyte_character firm = Firm.find(4) firm.name = "戦場ヶ原 ã²ãŸãŽ" assert_equal '4', firm.to_param end def test_to_param_class_method_uses_default_if_blank firm = Firm.find(4) firm.name = nil assert_equal '4', firm.to_param firm.name = ' ' assert_equal '4', firm.to_param end def test_to_param_class_method_uses_default_if_not_persisted firm = Firm.new(name: 'Fancy Shirts') assert_equal nil, firm.to_param end def test_to_param_with_no_arguments assert_equal 'Firm', Firm.to_param end def test_cache_key_for_existing_record_is_not_timezone_dependent utc_key = Developer.first.cache_key with_timezone_config zone: "EST" do est_key = Developer.first.cache_key assert_equal utc_key, est_key end end def test_cache_key_format_for_existing_record_with_updated_at dev = Developer.first assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key end def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format dev = CachedDeveloper.first assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key end def test_cache_key_changes_when_child_touched owner = owners(:blackbeard) pet = pets(:parrot) owner.update_column :updated_at, Time.current key = owner.cache_key assert pet.touch assert_not_equal key, owner.reload.cache_key end def test_cache_key_format_for_existing_record_with_nil_updated_timestamps dev = Developer.first dev.update_columns(updated_at: nil, updated_on: nil) assert_match(/\/#{dev.id}$/, dev.cache_key) end def test_cache_key_for_updated_on dev = Developer.first dev.updated_at = nil assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key end def test_cache_key_for_newer_updated_at dev = Developer.first dev.updated_at += 3600 assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key end def test_cache_key_for_newer_updated_on dev = Developer.first dev.updated_on += 3600 assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key end def test_cache_key_format_is_precise_enough dev = Developer.first key = dev.cache_key dev.touch assert_not_equal key, dev.cache_key end def test_named_timestamps_for_cache_key owner = owners(:blackbeard) assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:nsec)}", owner.cache_key(:updated_at, :happy_at) end end rails-4.2.6/activerecord/test/cases/invalid_connection_test.rb000066400000000000000000000010731266740050600246240ustar00rootroot00000000000000require "cases/helper" class TestAdapterWithInvalidConnection < ActiveRecord::TestCase self.use_transactional_fixtures = false class Bird < ActiveRecord::Base end def setup # Can't just use current adapter; sqlite3 will create a database # file on the fly. Bird.establish_connection adapter: 'mysql', database: 'i_do_not_exist' end teardown do Bird.remove_connection end test "inspect on Model class does not raise" do assert_equal "#{Bird.name} (call '#{Bird.name}.connection' to establish a connection)", Bird.inspect end end rails-4.2.6/activerecord/test/cases/invalid_date_test.rb000066400000000000000000000026001266740050600233770ustar00rootroot00000000000000require 'cases/helper' require 'models/topic' class InvalidDateTest < ActiveRecord::TestCase def test_assign_valid_dates valid_dates = [[2007, 11, 30], [1993, 2, 28], [2008, 2, 29]] invalid_dates = [[2007, 11, 31], [1993, 2, 29], [2007, 2, 29]] valid_dates.each do |date_src| topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s) # Oracle DATE columns are datetime columns and Oracle adapter returns Time value if current_adapter?(:OracleAdapter) assert_equal(topic.last_read.to_date, Date.new(*date_src)) else assert_equal(topic.last_read, Date.new(*date_src)) end end invalid_dates.each do |date_src| assert_nothing_raised do topic = Topic.new({"last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s}) # Oracle DATE columns are datetime columns and Oracle adapter returns Time value if current_adapter?(:OracleAdapter) assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behavior of the Time object") else assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behavior of the Time object") end end end end end rails-4.2.6/activerecord/test/cases/invertible_migration_test.rb000066400000000000000000000213041266740050600251720ustar00rootroot00000000000000require "cases/helper" module ActiveRecord class InvertibleMigrationTest < ActiveRecord::TestCase class SilentMigration < ActiveRecord::Migration def write(text = '') # sssshhhhh!! end end class InvertibleMigration < SilentMigration def change create_table("horses") do |t| t.column :content, :text t.column :remind_at, :datetime end end end class InvertibleRevertMigration < SilentMigration def change revert do create_table("horses") do |t| t.column :content, :text t.column :remind_at, :datetime end end end end class InvertibleByPartsMigration < SilentMigration attr_writer :test def change create_table("new_horses") do |t| t.column :breed, :string end reversible do |dir| @test.yield :both dir.up { @test.yield :up } dir.down { @test.yield :down } end revert do create_table("horses") do |t| t.column :content, :text t.column :remind_at, :datetime end end end end class NonInvertibleMigration < SilentMigration def change create_table("horses") do |t| t.column :content, :text t.column :remind_at, :datetime end remove_column "horses", :content end end class RemoveIndexMigration1 < SilentMigration def self.up create_table("horses") do |t| t.column :name, :string t.column :color, :string t.index [:name, :color] end end end class RemoveIndexMigration2 < SilentMigration def change change_table("horses") do |t| t.remove_index [:name, :color] end end end class LegacyMigration < ActiveRecord::Migration def self.up create_table("horses") do |t| t.column :content, :text t.column :remind_at, :datetime end end def self.down drop_table("horses") end end class RevertWholeMigration < SilentMigration def initialize(name = self.class.name, version = nil, migration) @migration = migration super(name, version) end def change revert @migration end end class NestedRevertWholeMigration < RevertWholeMigration def change revert { super } end end class RevertNamedIndexMigration1 < SilentMigration def change create_table("horses") do |t| t.column :content, :string t.column :remind_at, :datetime end add_index :horses, :content end end class RevertNamedIndexMigration2 < SilentMigration def change add_index :horses, :content, name: "horses_index_named" end end setup do @verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false end teardown do %w[horses new_horses].each do |table| if ActiveRecord::Base.connection.table_exists?(table) ActiveRecord::Base.connection.drop_table(table) end end ActiveRecord::Migration.verbose = @verbose_was end def test_no_reverse migration = NonInvertibleMigration.new migration.migrate(:up) assert_raises(IrreversibleMigration) do migration.migrate(:down) end end def test_exception_on_removing_index_without_column_option RemoveIndexMigration1.new.migrate(:up) migration = RemoveIndexMigration2.new migration.migrate(:up) assert_raises(IrreversibleMigration) do migration.migrate(:down) end end def test_migrate_up migration = InvertibleMigration.new migration.migrate(:up) assert migration.connection.table_exists?("horses"), "horses should exist" end def test_migrate_down migration = InvertibleMigration.new migration.migrate :up migration.migrate :down assert !migration.connection.table_exists?("horses") end def test_migrate_revert migration = InvertibleMigration.new revert = InvertibleRevertMigration.new migration.migrate :up revert.migrate :up assert !migration.connection.table_exists?("horses") revert.migrate :down assert migration.connection.table_exists?("horses") migration.migrate :down assert !migration.connection.table_exists?("horses") end def test_migrate_revert_by_part InvertibleMigration.new.migrate :up received = [] migration = InvertibleByPartsMigration.new migration.test = ->(dir){ assert migration.connection.table_exists?("horses") assert migration.connection.table_exists?("new_horses") received << dir } migration.migrate :up assert_equal [:both, :up], received assert !migration.connection.table_exists?("horses") assert migration.connection.table_exists?("new_horses") migration.migrate :down assert_equal [:both, :up, :both, :down], received assert migration.connection.table_exists?("horses") assert !migration.connection.table_exists?("new_horses") end def test_migrate_revert_whole_migration migration = InvertibleMigration.new [LegacyMigration, InvertibleMigration].each do |klass| revert = RevertWholeMigration.new(klass) migration.migrate :up revert.migrate :up assert !migration.connection.table_exists?("horses") revert.migrate :down assert migration.connection.table_exists?("horses") migration.migrate :down assert !migration.connection.table_exists?("horses") end end def test_migrate_nested_revert_whole_migration revert = NestedRevertWholeMigration.new(InvertibleRevertMigration) revert.migrate :down assert revert.connection.table_exists?("horses") revert.migrate :up assert !revert.connection.table_exists?("horses") end def test_revert_order block = Proc.new{|t| t.string :name } recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection) recorder.instance_eval do create_table("apples", &block) revert do create_table("bananas", &block) revert do create_table("clementines") create_table("dates") end create_table("elderberries") end revert do create_table("figs") create_table("grapes") end end assert_equal [[:create_table, ["apples"], block], [:drop_table, ["elderberries"], nil], [:create_table, ["clementines"], nil], [:create_table, ["dates"], nil], [:drop_table, ["bananas"], block], [:drop_table, ["grapes"], nil], [:drop_table, ["figs"], nil]], recorder.commands end def test_legacy_up LegacyMigration.migrate :up assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" end def test_legacy_down LegacyMigration.migrate :up LegacyMigration.migrate :down assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" end def test_up LegacyMigration.up assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" end def test_down LegacyMigration.up LegacyMigration.down assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" end def test_migrate_down_with_table_name_prefix ActiveRecord::Base.table_name_prefix = 'p_' ActiveRecord::Base.table_name_suffix = '_s' migration = InvertibleMigration.new migration.migrate(:up) assert_nothing_raised { migration.migrate(:down) } assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist" ensure ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = '' end # MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :OracleAdapter) def test_migrate_revert_add_index_with_name RevertNamedIndexMigration1.new.migrate(:up) RevertNamedIndexMigration2.new.migrate(:up) RevertNamedIndexMigration2.new.migrate(:down) connection = ActiveRecord::Base.connection assert connection.index_exists?(:horses, :content), "index on content should exist" assert !connection.index_exists?(:horses, :content, name: "horses_index_named"), "horses_index_named index should not exist" end end end end rails-4.2.6/activerecord/test/cases/json_serialization_test.rb000066400000000000000000000221771266740050600246750ustar00rootroot00000000000000require "cases/helper" require 'models/contact' require 'models/post' require 'models/author' require 'models/tagging' require 'models/tag' require 'models/comment' module JsonSerializationHelpers private def set_include_root_in_json(value) original_root_in_json = ActiveRecord::Base.include_root_in_json ActiveRecord::Base.include_root_in_json = value yield ensure ActiveRecord::Base.include_root_in_json = original_root_in_json end end class JsonSerializationTest < ActiveRecord::TestCase include JsonSerializationHelpers class NamespacedContact < Contact column :name, :string end def setup @contact = Contact.new( :name => 'Konata Izumi', :age => 16, :avatar => 'binarydata', :created_at => Time.utc(2006, 8, 1), :awesome => true, :preferences => { :shows => 'anime' } ) end def test_should_demodulize_root_in_json set_include_root_in_json(true) do @contact = NamespacedContact.new name: 'whatever' json = @contact.to_json assert_match %r{^\{"namespaced_contact":\{}, json end end def test_should_include_root_in_json set_include_root_in_json(true) do json = @contact.to_json assert_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end end def test_should_encode_all_encodable_attributes json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end def test_should_allow_attribute_filtering_with_only json = @contact.to_json(:only => [:name, :age]) assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert_no_match %r{"awesome":true}, json assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_no_match %r{"preferences":\{"shows":"anime"\}}, json end def test_should_allow_attribute_filtering_with_except json = @contact.to_json(:except => [:name, :age]) assert_no_match %r{"name":"Konata Izumi"}, json assert_no_match %r{"age":16}, json assert_match %r{"awesome":true}, json assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) assert_match %r{"preferences":\{"shows":"anime"\}}, json end def test_methods_are_called_on_object # Define methods on fixture. def @contact.label; "Has cheezburger"; end def @contact.favorite_quote; "Constraints are liberating"; end # Single method. assert_match %r{"label":"Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label) # Both methods. methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote]) assert_match %r{"label":"Has cheezburger"}, methods_json assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json end def test_uses_serializable_hash_with_only_option def @contact.serializable_hash(options=nil) super(only: %w(name)) end json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json assert_no_match %r{awesome}, json assert_no_match %r{age}, json end def test_uses_serializable_hash_with_except_option def @contact.serializable_hash(options=nil) super(except: %w(age)) end json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"awesome":true}, json assert_no_match %r{age}, json end def test_does_not_include_inheritance_column_from_sti @contact = ContactSti.new(@contact.attributes) assert_equal 'ContactSti', @contact.type json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json assert_no_match %r{type}, json assert_no_match %r{ContactSti}, json end def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti @contact = ContactSti.new(@contact.attributes) assert_equal 'ContactSti', @contact.type def @contact.serializable_hash(options={}) super({ except: %w(age) }.merge!(options)) end json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json assert_no_match %r{age}, json assert_no_match %r{type}, json assert_no_match %r{ContactSti}, json end def test_serializable_hash_should_not_modify_options_in_argument options = { :only => :name } @contact.serializable_hash(options) assert_nil options[:except] end end class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase fixtures :authors, :posts, :comments, :tags, :taggings include JsonSerializationHelpers def setup @david = authors(:david) @mary = authors(:mary) end def test_includes_uses_association_name json = @david.to_json(:include => :posts) assert_match %r{"posts":\[}, json assert_match %r{"id":1}, json assert_match %r{"name":"David"}, json assert_match %r{"author_id":1}, json assert_match %r{"title":"Welcome to the weblog"}, json assert_match %r{"body":"Such a lovely day"}, json assert_match %r{"title":"So I was thinking"}, json assert_match %r{"body":"Like I hopefully always am"}, json end def test_includes_uses_association_name_and_applies_attribute_filters json = @david.to_json(:include => { :posts => { :only => :title } }) assert_match %r{"name":"David"}, json assert_match %r{"posts":\[}, json assert_match %r{"title":"Welcome to the weblog"}, json assert_no_match %r{"body":"Such a lovely day"}, json assert_match %r{"title":"So I was thinking"}, json assert_no_match %r{"body":"Like I hopefully always am"}, json end def test_includes_fetches_second_level_associations json = @david.to_json(:include => { :posts => { :include => { :comments => { :only => :body } } } }) assert_match %r{"name":"David"}, json assert_match %r{"posts":\[}, json assert_match %r{"comments":\[}, json assert_match %r{\{"body":"Thank you again for the welcome"\}}, json assert_match %r{\{"body":"Don't think too hard"\}}, json assert_no_match %r{"post_id":}, json end def test_includes_fetches_nth_level_associations json = @david.to_json( :include => { :posts => { :include => { :taggings => { :include => { :tag => { :only => :name } } } } } }) assert_match %r{"name":"David"}, json assert_match %r{"posts":\[}, json assert_match %r{"taggings":\[}, json assert_match %r{"tag":\{"name":"General"\}}, json end def test_includes_doesnt_merge_opts_from_base json = @david.to_json( :only => :id, :include => :posts ) assert_match %{"title":"Welcome to the weblog"}, json end def test_should_not_call_methods_on_associations_that_dont_respond def @david.favorite_quote; "Constraints are liberating"; end json = @david.to_json(:include => :posts, :methods => :favorite_quote) assert !@david.posts.first.respond_to?(:favorite_quote) assert_match %r{"favorite_quote":"Constraints are liberating"}, json assert_equal %r{"favorite_quote":}.match(json).size, 1 end def test_should_allow_only_option_for_list_of_authors set_include_root_in_json(false) do authors = [@david, @mary] assert_equal %([{"name":"David"},{"name":"Mary"}]), ActiveSupport::JSON.encode(authors, only: :name) end end def test_should_allow_except_option_for_list_of_authors set_include_root_in_json(false) do authors = [@david, @mary] encoded = ActiveSupport::JSON.encode(authors, except: [ :name, :author_address_id, :author_address_extra_id, :organization_id, :owned_essay_id ]) assert_equal %([{"id":1},{"id":2}]), encoded end end def test_should_allow_includes_for_list_of_authors authors = [@david, @mary] json = ActiveSupport::JSON.encode(authors, :only => :name, :include => { :posts => { :only => :id } } ) ['"name":"David"', '"posts":[', '{"id":1}', '{"id":2}', '{"id":4}', '{"id":5}', '{"id":6}', '"name":"Mary"', '"posts":[', '{"id":7}', '{"id":9}'].each do |fragment| assert json.include?(fragment), json end end def test_should_allow_options_for_hash_of_authors set_include_root_in_json(true) do authors_hash = { 1 => @david, 2 => @mary } assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, only: [1, :name]) end end def test_should_be_able_to_encode_relation set_include_root_in_json(true) do authors_relation = Author.where(id: [@david.id, @mary.id]) json = ActiveSupport::JSON.encode authors_relation, only: :name assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json end end end rails-4.2.6/activerecord/test/cases/locking_test.rb000066400000000000000000000316711266740050600224140ustar00rootroot00000000000000require 'thread' require "cases/helper" require 'models/person' require 'models/job' require 'models/reader' require 'models/ship' require 'models/legacy_thing' require 'models/personal_legacy_thing' require 'models/reference' require 'models/string_key_object' require 'models/car' require 'models/bulb' require 'models/engine' require 'models/wheel' require 'models/treasure' class LockWithoutDefault < ActiveRecord::Base; end class LockWithCustomColumnWithoutDefault < ActiveRecord::Base self.table_name = :lock_without_defaults_cust self.column_defaults # to test @column_defaults caching. self.locking_column = :custom_lock_version end class ReadonlyNameShip < Ship attr_readonly :name end class OptimisticLockingTest < ActiveRecord::TestCase fixtures :people, :legacy_things, :references, :string_key_objects, :peoples_treasures def test_lock_version_is_incremented p1 = Person.find(1) assert_equal 0, p1.lock_version p1.first_name = 'anika2' p1.save! assert_equal 1, p1.lock_version end def test_non_integer_lock_existing s1 = StringKeyObject.find("record1") s2 = StringKeyObject.find("record1") assert_equal 0, s1.lock_version assert_equal 0, s2.lock_version s1.name = 'updated record' s1.save! assert_equal 1, s1.lock_version assert_equal 0, s2.lock_version s2.name = 'doubly updated record' assert_raise(ActiveRecord::StaleObjectError) { s2.save! } end def test_non_integer_lock_destroy s1 = StringKeyObject.find("record1") s2 = StringKeyObject.find("record1") assert_equal 0, s1.lock_version assert_equal 0, s2.lock_version s1.name = 'updated record' s1.save! assert_equal 1, s1.lock_version assert_equal 0, s2.lock_version assert_raise(ActiveRecord::StaleObjectError) { s2.destroy } assert s1.destroy assert s1.frozen? assert s1.destroyed? assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") } end def test_lock_existing p1 = Person.find(1) p2 = Person.find(1) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version p1.first_name = 'stu' p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version p2.first_name = 'sue' assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end # See Lighthouse ticket #1966 def test_lock_destroy p1 = Person.find(1) p2 = Person.find(1) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version p1.first_name = 'stu' p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } assert p1.destroy assert p1.frozen? assert p1.destroyed? assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } end def test_lock_repeating p1 = Person.find(1) p2 = Person.find(1) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version p1.first_name = 'stu' p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version p2.first_name = 'sue' assert_raise(ActiveRecord::StaleObjectError) { p2.save! } p2.first_name = 'sue2' assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end def test_lock_new p1 = Person.new(:first_name => 'anika') assert_equal 0, p1.lock_version p1.first_name = 'anika2' p1.save! p2 = Person.find(p1.id) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version p1.first_name = 'anika3' p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version p2.first_name = 'sue' assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end def test_lock_exception_record p1 = Person.new(:first_name => 'mira') assert_equal 0, p1.lock_version p1.first_name = 'mira2' p1.save! p2 = Person.find(p1.id) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version p1.first_name = 'mira3' p1.save! p2.first_name = 'sue' error = assert_raise(ActiveRecord::StaleObjectError) { p2.save! } assert_equal(error.record.object_id, p2.object_id) end def test_lock_new_with_nil p1 = Person.new(:first_name => 'anika') p1.save! p1.lock_version = nil # simulate bad fixture or column with no default p1.save! assert_equal 1, p1.lock_version end def test_touch_existing_lock p1 = Person.find(1) assert_equal 0, p1.lock_version p1.touch assert_equal 1, p1.lock_version end def test_lock_column_name_existing t1 = LegacyThing.find(1) t2 = LegacyThing.find(1) assert_equal 0, t1.version assert_equal 0, t2.version t1.tps_report_number = 700 t1.save! assert_equal 1, t1.version assert_equal 0, t2.version t2.tps_report_number = 800 assert_raise(ActiveRecord::StaleObjectError) { t2.save! } end def test_lock_column_is_mass_assignable p1 = Person.create(:first_name => 'bianca') assert_equal 0, p1.lock_version assert_equal p1.lock_version, Person.new(p1.attributes).lock_version p1.first_name = 'bianca2' p1.save! assert_equal 1, p1.lock_version assert_equal p1.lock_version, Person.new(p1.attributes).lock_version end def test_lock_without_default_sets_version_to_zero t1 = LockWithoutDefault.new assert_equal 0, t1.lock_version t1.save t1 = LockWithoutDefault.find(t1.id) assert_equal 0, t1.lock_version end def test_lock_with_custom_column_without_default_sets_version_to_zero t1 = LockWithCustomColumnWithoutDefault.new assert_equal 0, t1.custom_lock_version assert_nil t1.custom_lock_version_before_type_cast t1.save! t1.reload assert_equal 0, t1.custom_lock_version assert [0, "0"].include?(t1.custom_lock_version_before_type_cast) end def test_readonly_attributes assert_equal Set.new([ 'name' ]), ReadonlyNameShip.readonly_attributes s = ReadonlyNameShip.create(:name => "unchangeable name") s.reload assert_equal "unchangeable name", s.name s.update(name: "changed name") s.reload assert_equal "unchangeable name", s.name end def test_quote_table_name ref = references(:michael_magician) ref.favourite = !ref.favourite assert ref.save end # Useful for partial updates, don't only update the lock_version if there # is nothing else being updated. def test_update_without_attributes_does_not_only_update_lock_version assert_nothing_raised do p1 = Person.create!(:first_name => 'anika') lock_version = p1.lock_version p1.save p1.reload assert_equal lock_version, p1.lock_version end end def test_polymorphic_destroy_with_dependencies_and_lock_version car = Car.create! assert_difference 'car.wheels.count' do car.wheels << Wheel.create! end assert_difference 'car.wheels.count', -1 do car.destroy end assert car.destroyed? end def test_removing_has_and_belongs_to_many_associations_upon_destroy p = RichPerson.create! first_name: 'Jon' p.treasures.create! assert !p.treasures.empty? p.destroy assert p.treasures.empty? assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty? end def test_yaml_dumping_with_lock_column t1 = LockWithoutDefault.new t2 = YAML.load(YAML.dump(t1)) assert_equal t1.attributes, t2.attributes end end class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase fixtures :people, :legacy_things, :references # need to disable transactional fixtures, because otherwise the sqlite3 # adapter (at least) chokes when we try and change the schema in the middle # of a test (see test_increment_counter_*). self.use_transactional_fixtures = false { :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model| define_method("test_increment_counter_updates_#{name}") do counter_test model, 1 do |id| model.increment_counter :test_count, id end end define_method("test_decrement_counter_updates_#{name}") do counter_test model, -1 do |id| model.decrement_counter :test_count, id end end define_method("test_update_counters_updates_#{name}") do counter_test model, 1 do |id| model.update_counters id, :test_count => 1 end end end # See Lighthouse ticket #1966 def test_destroy_dependents # Establish dependent relationship between Person and PersonalLegacyThing add_counter_column_to(Person, 'personal_legacy_things_count') PersonalLegacyThing.reset_column_information # Make sure that counter incrementing doesn't cause problems p1 = Person.new(:first_name => 'fjord') p1.save! t = PersonalLegacyThing.new(:person => p1) t.save! p1.reload assert_equal 1, p1.personal_legacy_things_count assert p1.destroy assert_equal true, p1.frozen? assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) } assert_raises(ActiveRecord::RecordNotFound) { PersonalLegacyThing.find(t.id) } ensure remove_counter_column_from(Person, 'personal_legacy_things_count') PersonalLegacyThing.reset_column_information end private def add_counter_column_to(model, col='test_count') model.connection.add_column model.table_name, col, :integer, :null => false, :default => 0 model.reset_column_information end def remove_counter_column_from(model, col = :test_count) model.connection.remove_column model.table_name, col model.reset_column_information end def counter_test(model, expected_count) add_counter_column_to(model) object = model.first assert_equal 0, object.test_count assert_equal 0, object.send(model.locking_column) yield object.id object.reload assert_equal expected_count, object.test_count assert_equal 1, object.send(model.locking_column) ensure remove_counter_column_from(model) end end # TODO: test against the generated SQL since testing locking behavior itself # is so cumbersome. Will deadlock Ruby threads if the underlying db.execute # blocks, so separate script called by Kernel#system is needed. # (See exec vs. async_exec in the PostgreSQL adapter.) unless in_memory_db? class PessimisticLockingTest < ActiveRecord::TestCase self.use_transactional_fixtures = false fixtures :people, :readers def setup Person.connection_pool.clear_reloadable_connections! # Avoid introspection queries during tests. Person.columns; Reader.columns end # Test typical find. def test_sane_find_with_lock assert_nothing_raised do Person.transaction do Person.lock.find(1) end end end # PostgreSQL protests SELECT ... FOR UPDATE on an outer join. unless current_adapter?(:PostgreSQLAdapter) # Test locked eager find. def test_eager_find_with_lock assert_nothing_raised do Person.transaction do Person.includes(:readers).lock.find(1) end end end end # Locking a record reloads it. def test_sane_lock_method assert_nothing_raised do Person.transaction do person = Person.find 1 old, person.first_name = person.first_name, 'fooman' person.lock! assert_equal old, person.first_name end end end def test_with_lock_commits_transaction person = Person.find 1 person.with_lock do person.first_name = 'fooman' person.save! end assert_equal 'fooman', person.reload.first_name end def test_with_lock_rolls_back_transaction person = Person.find 1 old = person.first_name person.with_lock do person.first_name = 'fooman' person.save! raise 'oops' end rescue nil assert_equal old, person.reload.first_name end if current_adapter?(:PostgreSQLAdapter) def test_lock_sending_custom_lock_statement Person.transaction do person = Person.find(1) assert_sql(/LIMIT 1 FOR SHARE NOWAIT/) do person.lock!('FOR SHARE NOWAIT') end end end end if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) def test_no_locks_no_wait first, second = duel { Person.find 1 } assert first.end > second.end end protected def duel(zzz = 5) t0, t1, t2, t3 = nil, nil, nil, nil a = Thread.new do t0 = Time.now Person.transaction do yield sleep zzz # block thread 2 for zzz seconds end t1 = Time.now end b = Thread.new do sleep zzz / 2.0 # ensure thread 1 tx starts first t2 = Time.now Person.transaction { yield } t3 = Time.now end a.join b.join assert t1 > t0 + zzz assert t2 > t0 assert t3 > t2 [t0.to_f..t1.to_f, t2.to_f..t3.to_f] end end end end rails-4.2.6/activerecord/test/cases/log_subscriber_test.rb000066400000000000000000000070571266740050600237730ustar00rootroot00000000000000require "cases/helper" require "models/binary" require "models/developer" require "models/post" require "active_support/log_subscriber/test_helper" class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper include ActiveSupport::Logger::Severity class TestDebugLogSubscriber < ActiveRecord::LogSubscriber attr_reader :debugs def initialize @debugs = [] super end def debug message @debugs << message end end fixtures :posts def setup @old_logger = ActiveRecord::Base.logger Developer.primary_key super ActiveRecord::LogSubscriber.attach_to(:active_record) end def teardown super ActiveRecord::LogSubscriber.log_subscribers.pop ActiveRecord::Base.logger = @old_logger end def set_logger(logger) ActiveRecord::Base.logger = logger end def test_schema_statements_are_ignored event = Struct.new(:duration, :payload) logger = TestDebugLogSubscriber.new assert_equal 0, logger.debugs.length logger.sql(event.new(0, sql: 'hi mom!')) assert_equal 1, logger.debugs.length logger.sql(event.new(0, sql: 'hi mom!', name: 'foo')) assert_equal 2, logger.debugs.length logger.sql(event.new(0, sql: 'hi mom!', name: 'SCHEMA')) assert_equal 2, logger.debugs.length end def test_sql_statements_are_not_squeezed event = Struct.new(:duration, :payload) logger = TestDebugLogSubscriber.new logger.sql(event.new(0, sql: 'ruby rails')) assert_match(/ruby rails/, logger.debugs.first) end def test_ignore_binds_payload_with_nil_column event = Struct.new(:duration, :payload) logger = TestDebugLogSubscriber.new logger.sql(event.new(0, sql: 'hi mom!', binds: [[nil, 1]])) assert_equal 1, logger.debugs.length end def test_basic_query_logging Developer.all.load wait assert_equal 1, @logger.logged(:debug).size assert_match(/Developer Load/, @logger.logged(:debug).last) assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) end def test_exists_query_logging Developer.exists? 1 wait assert_equal 1, @logger.logged(:debug).size assert_match(/Developer Exists/, @logger.logged(:debug).last) assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) end def test_cached_queries ActiveRecord::Base.cache do Developer.all.load Developer.all.load end wait assert_equal 2, @logger.logged(:debug).size assert_match(/CACHE/, @logger.logged(:debug).last) assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) end def test_basic_query_doesnt_log_when_level_is_not_debug @logger.level = INFO Developer.all.load wait assert_equal 0, @logger.logged(:debug).size end def test_cached_queries_doesnt_log_when_level_is_not_debug @logger.level = INFO ActiveRecord::Base.cache do Developer.all.load Developer.all.load end wait assert_equal 0, @logger.logged(:debug).size end def test_initializes_runtime Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join end unless current_adapter?(:Mysql2Adapter) def test_binary_data_is_not_logged Binary.create(data: 'some binary data') wait assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join) end def test_nil_binary_data_is_logged binary = Binary.create(data: "") binary.update_attributes(data: nil) wait assert_match(//, @logger.logged(:debug).join) end end end rails-4.2.6/activerecord/test/cases/migration/000077500000000000000000000000001266740050600213635ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/migration/change_schema_test.rb000066400000000000000000000422141266740050600255170ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class Migration class ChangeSchemaTest < ActiveRecord::TestCase attr_reader :connection, :table_name def setup super @connection = ActiveRecord::Base.connection @table_name = :testings end teardown do connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil ActiveRecord::Base.clear_cache! end def test_create_table_without_id testing_table_with_only_foo_attribute do assert_equal connection.columns(:testings).size, 1 end end def test_add_column_with_primary_key_attribute testing_table_with_only_foo_attribute do connection.add_column :testings, :id, :primary_key assert_equal connection.columns(:testings).size, 2 end end def test_create_table_adds_id connection.create_table :testings do |t| t.column :foo, :string end assert_equal %w(id foo), connection.columns(:testings).map(&:name) end def test_create_table_with_not_null_column connection.create_table :testings do |t| t.column :foo, :string, :null => false end assert_raises(ActiveRecord::StatementInvalid) do connection.execute "insert into testings (foo) values (NULL)" end end def test_create_table_with_defaults # MySQL doesn't allow defaults on TEXT or BLOB columns. mysql = current_adapter?(:MysqlAdapter, :Mysql2Adapter) connection.create_table :testings do |t| t.column :one, :string, :default => "hello" t.column :two, :boolean, :default => true t.column :three, :boolean, :default => false t.column :four, :integer, :default => 1 t.column :five, :text, :default => "hello" unless mysql end columns = connection.columns(:testings) one = columns.detect { |c| c.name == "one" } two = columns.detect { |c| c.name == "two" } three = columns.detect { |c| c.name == "three" } four = columns.detect { |c| c.name == "four" } five = columns.detect { |c| c.name == "five" } unless mysql assert_equal "hello", one.default assert_equal true, two.type_cast_from_database(two.default) assert_equal false, three.type_cast_from_database(three.default) assert_equal '1', four.default assert_equal "hello", five.default unless mysql end if current_adapter?(:PostgreSQLAdapter) def test_add_column_with_array connection.create_table :testings connection.add_column :testings, :foo, :string, :array => true columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } assert array_column.array end def test_create_table_with_array_column connection.create_table :testings do |t| t.string :foo, :array => true end columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } assert array_column.array end end def test_create_table_with_bigint connection.create_table :testings do |t| t.bigint :eight_int end columns = connection.columns(:testings) eight = columns.detect { |c| c.name == "eight_int" } if current_adapter?(:OracleAdapter) assert_equal 'NUMBER(19)', eight.sql_type elsif current_adapter?(:SQLite3Adapter) assert_equal 'bigint', eight.sql_type else assert_equal :integer, eight.type assert_equal 8, eight.limit end ensure connection.drop_table :testings end def test_create_table_with_limits connection.create_table :testings do |t| t.column :foo, :string, :limit => 255 t.column :default_int, :integer t.column :one_int, :integer, :limit => 1 t.column :four_int, :integer, :limit => 4 t.column :eight_int, :integer, :limit => 8 end columns = connection.columns(:testings) foo = columns.detect { |c| c.name == "foo" } assert_equal 255, foo.limit default = columns.detect { |c| c.name == "default_int" } one = columns.detect { |c| c.name == "one_int" } four = columns.detect { |c| c.name == "four_int" } eight = columns.detect { |c| c.name == "eight_int" } if current_adapter?(:PostgreSQLAdapter) assert_equal 'integer', default.sql_type assert_equal 'smallint', one.sql_type assert_equal 'integer', four.sql_type assert_equal 'bigint', eight.sql_type elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_match 'int(11)', default.sql_type assert_match 'tinyint', one.sql_type assert_match 'int', four.sql_type assert_match 'bigint', eight.sql_type elsif current_adapter?(:OracleAdapter) assert_equal 'NUMBER(38)', default.sql_type assert_equal 'NUMBER(1)', one.sql_type assert_equal 'NUMBER(4)', four.sql_type assert_equal 'NUMBER(8)', eight.sql_type end end def test_create_table_with_primary_key_prefix_as_table_name_with_underscore ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore connection.create_table :testings do |t| t.column :foo, :string end assert_equal %w(testing_id foo), connection.columns(:testings).map(&:name) end def test_create_table_with_primary_key_prefix_as_table_name ActiveRecord::Base.primary_key_prefix_type = :table_name connection.create_table :testings do |t| t.column :foo, :string end assert_equal %w(testingid foo), connection.columns(:testings).map(&:name) end def test_create_table_raises_when_redefining_primary_key_column error = assert_raise(ArgumentError) do connection.create_table :testings do |t| t.column :id, :string end end assert_equal "you can't redefine the primary key column 'id'. To define a custom primary key, pass { id: false } to create_table.", error.message end def test_create_table_raises_when_redefining_custom_primary_key_column error = assert_raise(ArgumentError) do connection.create_table :testings, primary_key: :testing_id do |t| t.column :testing_id, :string end end assert_equal "you can't redefine the primary key column 'testing_id'. To define a custom primary key, pass { id: false } to create_table.", error.message end def test_create_table_with_timestamps_should_create_datetime_columns # FIXME: Remove the silence when we change the default `null` behavior ActiveSupport::Deprecation.silence do connection.create_table table_name do |t| t.timestamps end end created_columns = connection.columns(table_name) created_at_column = created_columns.detect {|c| c.name == 'created_at' } updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } assert created_at_column.null assert updated_at_column.null end def test_create_table_with_timestamps_should_create_datetime_columns_with_options connection.create_table table_name do |t| t.timestamps :null => false end created_columns = connection.columns(table_name) created_at_column = created_columns.detect {|c| c.name == 'created_at' } updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } assert !created_at_column.null assert !updated_at_column.null end def test_create_table_without_a_block connection.create_table table_name end # SQLite3 will not allow you to add a NOT NULL # column to a table without a default value. unless current_adapter?(:SQLite3Adapter) def test_add_column_not_null_without_default connection.create_table :testings do |t| t.column :foo, :string end connection.add_column :testings, :bar, :string, :null => false assert_raise(ActiveRecord::StatementInvalid) do connection.execute "insert into testings (foo, bar) values ('hello', NULL)" end end end def test_add_column_not_null_with_default connection.create_table :testings do |t| t.column :foo, :string end con = connection connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" assert_nothing_raised {connection.add_column :testings, :bar, :string, :null => false, :default => "default" } assert_raises(ActiveRecord::StatementInvalid) do connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" end end def test_add_column_with_timestamp_type connection.create_table :testings do |t| t.column :foo, :timestamp end klass = Class.new(ActiveRecord::Base) klass.table_name = 'testings' assert_equal :datetime, klass.columns_hash['foo'].type if current_adapter?(:PostgreSQLAdapter) assert_equal 'timestamp without time zone', klass.columns_hash['foo'].sql_type else assert_equal klass.connection.type_to_sql('datetime'), klass.columns_hash['foo'].sql_type end end def test_change_column_quotes_column_names connection.create_table :testings do |t| t.column :select, :string end connection.change_column :testings, :select, :string, :limit => 10 # Oracle needs primary key value from sequence if current_adapter?(:OracleAdapter) connection.execute "insert into testings (id, #{connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" else connection.execute "insert into testings (#{connection.quote_column_name('select')}) values ('7 chars')" end end def test_keeping_default_and_notnull_constraints_on_change connection.create_table :testings do |t| t.column :title, :string end person_klass = Class.new(ActiveRecord::Base) person_klass.table_name = 'testings' person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99 person_klass.reset_column_information assert_equal 99, person_klass.column_defaults["wealth"] assert_equal false, person_klass.columns_hash["wealth"].null # Oracle needs primary key value from sequence if current_adapter?(:OracleAdapter) assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} else assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} end # change column default to see that column doesn't lose its not null definition person_klass.connection.change_column_default "testings", "wealth", 100 person_klass.reset_column_information assert_equal 100, person_klass.column_defaults["wealth"] assert_equal false, person_klass.columns_hash["wealth"].null # rename column to see that column doesn't lose its not null and/or default definition person_klass.connection.rename_column "testings", "wealth", "money" person_klass.reset_column_information assert_nil person_klass.columns_hash["wealth"] assert_equal 100, person_klass.column_defaults["money"] assert_equal false, person_klass.columns_hash["money"].null # change column person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000 person_klass.reset_column_information assert_equal 1000, person_klass.column_defaults["money"] assert_equal false, person_klass.columns_hash["money"].null # change column, make it nullable and clear default person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil person_klass.reset_column_information assert_nil person_klass.columns_hash["money"].default assert_equal true, person_klass.columns_hash["money"].null # change_column_null, make it not nullable and set null values to a default value person_klass.connection.execute('UPDATE testings SET money = NULL') person_klass.connection.change_column_null "testings", "money", false, 2000 person_klass.reset_column_information assert_nil person_klass.columns_hash["money"].default assert_equal false, person_klass.columns_hash["money"].null assert_equal 2000, connection.select_values("SELECT money FROM testings").first.to_i end def test_change_column_null testing_table_with_only_foo_attribute do notnull_migration = Class.new(ActiveRecord::Migration) do def change change_column_null :testings, :foo, false end end notnull_migration.new.suppress_messages do notnull_migration.migrate(:up) assert_equal false, connection.columns(:testings).find{ |c| c.name == "foo"}.null notnull_migration.migrate(:down) assert connection.columns(:testings).find{ |c| c.name == "foo"}.null end end end def test_column_exists connection.create_table :testings do |t| t.column :foo, :string end assert connection.column_exists?(:testings, :foo) assert_not connection.column_exists?(:testings, :bar) end def test_column_exists_with_type connection.create_table :testings do |t| t.column :foo, :string t.column :bar, :decimal, :precision => 8, :scale => 2 end assert connection.column_exists?(:testings, :foo, :string) assert_not connection.column_exists?(:testings, :foo, :integer) assert connection.column_exists?(:testings, :bar, :decimal) assert_not connection.column_exists?(:testings, :bar, :integer) end def test_column_exists_with_definition connection.create_table :testings do |t| t.column :foo, :string, limit: 100 t.column :bar, :decimal, precision: 8, scale: 2 t.column :taggable_id, :integer, null: false t.column :taggable_type, :string, default: 'Photo' end assert connection.column_exists?(:testings, :foo, :string, limit: 100) assert_not connection.column_exists?(:testings, :foo, :string, limit: nil) assert connection.column_exists?(:testings, :bar, :decimal, precision: 8, scale: 2) assert_not connection.column_exists?(:testings, :bar, :decimal, precision: nil, scale: nil) assert connection.column_exists?(:testings, :taggable_id, :integer, null: false) assert_not connection.column_exists?(:testings, :taggable_id, :integer, null: true) assert connection.column_exists?(:testings, :taggable_type, :string, default: 'Photo') assert_not connection.column_exists?(:testings, :taggable_type, :string, default: nil) end def test_column_exists_on_table_with_no_options_parameter_supplied connection.create_table :testings do |t| t.string :foo end connection.change_table :testings do |t| assert t.column_exists?(:foo) assert !(t.column_exists?(:bar)) end end private def testing_table_with_only_foo_attribute connection.create_table :testings, :id => false do |t| t.column :foo, :string end yield end end if ActiveRecord::Base.connection.supports_foreign_keys? class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase self.use_transactional_fixtures = false setup do @connection = ActiveRecord::Base.connection @connection.create_table :trains @connection.create_table(:wagons) { |t| t.references :train } @connection.add_foreign_key :wagons, :trains end teardown do [:wagons, :trains].each do |table| @connection.drop_table(table) if @connection.table_exists?(table) end end def test_create_table_with_force_cascade_drops_dependent_objects skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE" if current_adapter?(:MysqlAdapter, :Mysql2Adapter) # can't re-create table referenced by foreign key assert_raises(ActiveRecord::StatementInvalid) do @connection.create_table :trains, force: true end # can recreate referenced table with force: :cascade @connection.create_table :trains, force: :cascade assert_equal [], @connection.foreign_keys(:wagons) end end end end end rails-4.2.6/activerecord/test/cases/migration/change_table_test.rb000066400000000000000000000162451266740050600253530ustar00rootroot00000000000000require "cases/migration/helper" require "minitest/mock" module ActiveRecord class Migration class TableTest < ActiveRecord::TestCase def setup @connection = Minitest::Mock.new end teardown do assert @connection.verify end def with_change_table yield ConnectionAdapters::Table.new(:delete_me, @connection) end def test_references_column_type_adds_id with_change_table do |t| @connection.expect :add_reference, nil, [:delete_me, :customer, {}] t.references :customer end end def test_remove_references_column_type_removes_id with_change_table do |t| @connection.expect :remove_reference, nil, [:delete_me, :customer, {}] t.remove_references :customer end end def test_add_belongs_to_works_like_add_references with_change_table do |t| @connection.expect :add_reference, nil, [:delete_me, :customer, {}] t.belongs_to :customer end end def test_remove_belongs_to_works_like_remove_references with_change_table do |t| @connection.expect :remove_reference, nil, [:delete_me, :customer, {}] t.remove_belongs_to :customer end end def test_references_column_type_with_polymorphic_adds_type with_change_table do |t| @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true] t.references :taggable, polymorphic: true end end def test_remove_references_column_type_with_polymorphic_removes_type with_change_table do |t| @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true] t.remove_references :taggable, polymorphic: true end end def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag with_change_table do |t| @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false] t.references :taggable, polymorphic: true, null: false end end def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag with_change_table do |t| @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false] t.remove_references :taggable, polymorphic: true, null: false end end def test_references_column_type_with_polymorphic_and_type with_change_table do |t| @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true, type: :string] t.references :taggable, polymorphic: true, type: :string end end def test_remove_references_column_type_with_polymorphic_and_type with_change_table do |t| @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true, type: :string] t.remove_references :taggable, polymorphic: true, type: :string end end def test_timestamps_creates_updated_at_and_created_at with_change_table do |t| @connection.expect :add_timestamps, nil, [:delete_me, null: true] t.timestamps null: true end end def test_remove_timestamps_creates_updated_at_and_created_at with_change_table do |t| @connection.expect :remove_timestamps, nil, [:delete_me, { null: true }] t.remove_timestamps({ null: true }) end end def test_integer_creates_integer_column with_change_table do |t| @connection.expect :add_column, nil, [:delete_me, :foo, :integer, {}] @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}] t.integer :foo, :bar end end def test_string_creates_string_column with_change_table do |t| @connection.expect :add_column, nil, [:delete_me, :foo, :string, {}] @connection.expect :add_column, nil, [:delete_me, :bar, :string, {}] t.string :foo, :bar end end def test_column_creates_column with_change_table do |t| @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}] t.column :bar, :integer end end def test_column_creates_column_with_options with_change_table do |t| @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {:null => false}] t.column :bar, :integer, :null => false end end def test_index_creates_index with_change_table do |t| @connection.expect :add_index, nil, [:delete_me, :bar, {}] t.index :bar end end def test_index_creates_index_with_options with_change_table do |t| @connection.expect :add_index, nil, [:delete_me, :bar, {:unique => true}] t.index :bar, :unique => true end end def test_index_exists with_change_table do |t| @connection.expect :index_exists?, nil, [:delete_me, :bar, {}] t.index_exists?(:bar) end end def test_index_exists_with_options with_change_table do |t| @connection.expect :index_exists?, nil, [:delete_me, :bar, {:unique => true}] t.index_exists?(:bar, :unique => true) end end def test_rename_index_renames_index with_change_table do |t| @connection.expect :rename_index, nil, [:delete_me, :bar, :baz] t.rename_index :bar, :baz end end def test_change_changes_column with_change_table do |t| @connection.expect :change_column, nil, [:delete_me, :bar, :string, {}] t.change :bar, :string end end def test_change_changes_column_with_options with_change_table do |t| @connection.expect :change_column, nil, [:delete_me, :bar, :string, {:null => true}] t.change :bar, :string, :null => true end end def test_change_default_changes_column with_change_table do |t| @connection.expect :change_column_default, nil, [:delete_me, :bar, :string] t.change_default :bar, :string end end def test_remove_drops_single_column with_change_table do |t| @connection.expect :remove_columns, nil, [:delete_me, :bar] t.remove :bar end end def test_remove_drops_multiple_columns with_change_table do |t| @connection.expect :remove_columns, nil, [:delete_me, :bar, :baz] t.remove :bar, :baz end end def test_remove_index_removes_index_with_options with_change_table do |t| @connection.expect :remove_index, nil, [:delete_me, {:unique => true}] t.remove_index :unique => true end end def test_rename_renames_column with_change_table do |t| @connection.expect :rename_column, nil, [:delete_me, :bar, :baz] t.rename :bar, :baz end end def test_table_name_set with_change_table do |t| assert_equal :delete_me, t.name end end end end end rails-4.2.6/activerecord/test/cases/migration/column_attributes_test.rb000066400000000000000000000163741266740050600265250ustar00rootroot00000000000000require "cases/migration/helper" module ActiveRecord class Migration class ColumnAttributesTest < ActiveRecord::TestCase include ActiveRecord::Migration::TestHelper self.use_transactional_fixtures = false def test_add_column_newline_default string = "foo\nbar" add_column 'test_models', 'command', :string, :default => string TestModel.reset_column_information assert_equal string, TestModel.new.command end def test_add_remove_single_field_using_string_arguments assert_no_column TestModel, :last_name add_column 'test_models', 'last_name', :string assert_column TestModel, :last_name remove_column 'test_models', 'last_name' assert_no_column TestModel, :last_name end def test_add_remove_single_field_using_symbol_arguments assert_no_column TestModel, :last_name add_column :test_models, :last_name, :string assert_column TestModel, :last_name remove_column :test_models, :last_name assert_no_column TestModel, :last_name end def test_add_column_without_limit # TODO: limit: nil should work with all adapters. skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:MysqlAdapter, :Mysql2Adapter) add_column :test_models, :description, :string, limit: nil TestModel.reset_column_information assert_nil TestModel.columns_hash["description"].limit end if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_unabstracted_database_dependent_types add_column :test_models, :intelligence_quotient, :tinyint TestModel.reset_column_information assert_match(/tinyint/, TestModel.columns_hash['intelligence_quotient'].sql_type) end end unless current_adapter?(:SQLite3Adapter) # We specifically do a manual INSERT here, and then test only the SELECT # functionality. This allows us to more easily catch INSERT being broken, # but SELECT actually working fine. def test_native_decimal_insert_manual_vs_automatic correct_value = '0012345678901234567890.0123456789'.to_d connection.add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10' # Do a manual insertion if current_adapter?(:OracleAdapter) connection.execute "insert into test_models (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)" elsif current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003 #before MySQL 5.0.3 decimals stored as strings connection.execute "insert into test_models (wealth) values ('12345678901234567890.0123456789')" elsif current_adapter?(:PostgreSQLAdapter) connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)" else connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)" end # SELECT row = TestModel.first assert_kind_of BigDecimal, row.wealth # If this assert fails, that means the SELECT is broken! unless current_adapter?(:SQLite3Adapter) assert_equal correct_value, row.wealth end # Reset to old state TestModel.delete_all # Now use the Rails insertion TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789") # SELECT row = TestModel.first assert_kind_of BigDecimal, row.wealth # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken! assert_equal correct_value, row.wealth end end def test_add_column_with_precision_and_scale connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 wealth_column = TestModel.columns_hash['wealth'] assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale end if current_adapter?(:SQLite3Adapter) def test_change_column_preserve_other_column_precision_and_scale connection.add_column 'test_models', 'last_name', :string connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 wealth_column = TestModel.columns_hash['wealth'] assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale connection.change_column 'test_models', 'last_name', :string, :null => false TestModel.reset_column_information wealth_column = TestModel.columns_hash['wealth'] assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale end end unless current_adapter?(:SQLite3Adapter) def test_native_types add_column "test_models", "first_name", :string add_column "test_models", "last_name", :string add_column "test_models", "bio", :text add_column "test_models", "age", :integer add_column "test_models", "height", :float add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10' add_column "test_models", "birthday", :datetime add_column "test_models", "favorite_day", :date add_column "test_models", "moment_of_truth", :datetime add_column "test_models", "male", :boolean TestModel.create :first_name => 'bob', :last_name => 'bobsen', :bio => "I was born ....", :age => 18, :height => 1.78, :wealth => BigDecimal.new("12345678901234567890.0123456789"), :birthday => 18.years.ago, :favorite_day => 10.days.ago, :moment_of_truth => "1782-10-10 21:40:18", :male => true bob = TestModel.first assert_equal 'bob', bob.first_name assert_equal 'bobsen', bob.last_name assert_equal "I was born ....", bob.bio assert_equal 18, bob.age # Test for 30 significant digits (beyond the 16 of float), 10 of them # after the decimal place. assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth assert_equal true, bob.male? assert_equal String, bob.first_name.class assert_equal String, bob.last_name.class assert_equal String, bob.bio.class assert_equal Fixnum, bob.age.class assert_equal Time, bob.birthday.class if current_adapter?(:OracleAdapter) # Oracle doesn't differentiate between date/time assert_equal Time, bob.favorite_day.class else assert_equal Date, bob.favorite_day.class end assert_instance_of TrueClass, bob.male? assert_kind_of BigDecimal, bob.wealth end end if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) def test_out_of_range_limit_should_raise assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 } unless current_adapter?(:PostgreSQLAdapter) assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, :limit => 0xfffffffff } end end end end end end rails-4.2.6/activerecord/test/cases/migration/column_positioning_test.rb000066400000000000000000000035541266740050600266750ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class Migration class ColumnPositioningTest < ActiveRecord::TestCase attr_reader :connection, :table_name alias :conn :connection def setup super @connection = ActiveRecord::Base.connection connection.create_table :testings, :id => false do |t| t.column :first, :integer t.column :second, :integer t.column :third, :integer end end teardown do connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil end if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_column_positioning assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } end def test_add_column_with_positioning conn.add_column :testings, :new_col, :integer assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } end def test_add_column_with_positioning_first conn.add_column :testings, :new_col, :integer, :first => true assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } end def test_add_column_with_positioning_after conn.add_column :testings, :new_col, :integer, :after => :first assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } end def test_change_column_with_positioning conn.change_column :testings, :second, :integer, :first => true assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } conn.change_column :testings, :second, :integer, :after => :third assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } end end end end end rails-4.2.6/activerecord/test/cases/migration/columns_test.rb000066400000000000000000000271771266740050600244450ustar00rootroot00000000000000require "cases/migration/helper" module ActiveRecord class Migration class ColumnsTest < ActiveRecord::TestCase include ActiveRecord::Migration::TestHelper self.use_transactional_fixtures = false # FIXME: this is more of an integration test with AR::Base and the # schema modifications. Maybe we should move this? def test_add_rename add_column "test_models", "girlfriend", :string TestModel.reset_column_information TestModel.create :girlfriend => 'bobette' rename_column "test_models", "girlfriend", "exgirlfriend" TestModel.reset_column_information bob = TestModel.first assert_equal "bobette", bob.exgirlfriend end # FIXME: another integration test. We should decouple this from the # AR::Base implementation. def test_rename_column_using_symbol_arguments add_column :test_models, :first_name, :string TestModel.create :first_name => 'foo' rename_column :test_models, :first_name, :nick_name TestModel.reset_column_information assert TestModel.column_names.include?("nick_name") assert_equal ['foo'], TestModel.all.map(&:nick_name) end # FIXME: another integration test. We should decouple this from the # AR::Base implementation. def test_rename_column add_column "test_models", "first_name", "string" TestModel.create :first_name => 'foo' rename_column "test_models", "first_name", "nick_name" TestModel.reset_column_information assert TestModel.column_names.include?("nick_name") assert_equal ['foo'], TestModel.all.map(&:nick_name) end def test_rename_column_preserves_default_value_not_null add_column 'test_models', 'salary', :integer, :default => 70000 default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default assert_equal '70000', default_before rename_column "test_models", "salary", "annual_salary" assert TestModel.column_names.include?("annual_salary") default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default assert_equal '70000', default_after end if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_mysql_rename_column_preserves_auto_increment rename_column "test_models", "id", "id_test" assert_equal "auto_increment", connection.columns("test_models").find { |c| c.name == "id_test" }.extra TestModel.reset_column_information ensure rename_column "test_models", "id_test", "id" end end def test_rename_nonexistent_column exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) ActiveRecord::StatementInvalid else ActiveRecord::ActiveRecordError end assert_raise(exception) do rename_column "test_models", "nonexistent", "should_fail" end end def test_rename_column_with_sql_reserved_word add_column 'test_models', 'first_name', :string rename_column "test_models", "first_name", "group" assert TestModel.column_names.include?("group") end def test_rename_column_with_an_index add_column "test_models", :hat_name, :string add_index :test_models, :hat_name assert_equal 1, connection.indexes('test_models').size rename_column "test_models", "hat_name", "name" assert_equal ['index_test_models_on_name'], connection.indexes('test_models').map(&:name) end def test_rename_column_with_multi_column_index add_column "test_models", :hat_size, :integer add_column "test_models", :hat_style, :string, limit: 100 add_index "test_models", ["hat_style", "hat_size"], unique: true rename_column "test_models", "hat_size", 'size' if current_adapter? :OracleAdapter assert_equal ['i_test_models_hat_style_size'], connection.indexes('test_models').map(&:name) else assert_equal ['index_test_models_on_hat_style_and_size'], connection.indexes('test_models').map(&:name) end rename_column "test_models", "hat_style", 'style' if current_adapter? :OracleAdapter assert_equal ['i_test_models_style_size'], connection.indexes('test_models').map(&:name) else assert_equal ['index_test_models_on_style_and_size'], connection.indexes('test_models').map(&:name) end end def test_rename_column_does_not_rename_custom_named_index add_column "test_models", :hat_name, :string add_index :test_models, :hat_name, :name => 'idx_hat_name' assert_equal 1, connection.indexes('test_models').size rename_column "test_models", "hat_name", "name" assert_equal ['idx_hat_name'], connection.indexes('test_models').map(&:name) end def test_remove_column_with_index add_column "test_models", :hat_name, :string add_index :test_models, :hat_name assert_equal 1, connection.indexes('test_models').size remove_column("test_models", "hat_name") assert_equal 0, connection.indexes('test_models').size end def test_remove_column_with_multi_column_index add_column "test_models", :hat_size, :integer add_column "test_models", :hat_style, :string, :limit => 100 add_index "test_models", ["hat_style", "hat_size"], :unique => true assert_equal 1, connection.indexes('test_models').size remove_column("test_models", "hat_size") # Every database and/or database adapter has their own behavior # if it drops the multi-column index when any of the indexed columns dropped by remove_column. if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) assert_equal [], connection.indexes('test_models').map(&:name) else assert_equal ['index_test_models_on_hat_style_and_hat_size'], connection.indexes('test_models').map(&:name) end end def test_change_type_of_not_null_column change_column "test_models", "updated_at", :datetime, :null => false change_column "test_models", "updated_at", :datetime, :null => false TestModel.reset_column_information assert_equal false, TestModel.columns_hash['updated_at'].null ensure change_column "test_models", "updated_at", :datetime, :null => true end def test_change_column_nullability add_column "test_models", "funny", :boolean assert TestModel.columns_hash["funny"].null, "Column 'funny' must initially allow nulls" change_column "test_models", "funny", :boolean, :null => false, :default => true TestModel.reset_column_information assert_not TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point" change_column "test_models", "funny", :boolean, :null => true TestModel.reset_column_information assert TestModel.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point" end def test_change_column add_column 'test_models', 'age', :integer add_column 'test_models', 'approved', :boolean, :default => true old_columns = connection.columns(TestModel.table_name) assert old_columns.find { |c| c.name == 'age' && c.type == :integer } change_column "test_models", "age", :string new_columns = connection.columns(TestModel.table_name) assert_not new_columns.find { |c| c.name == 'age' and c.type == :integer } assert new_columns.find { |c| c.name == 'age' and c.type == :string } old_columns = connection.columns(TestModel.table_name) assert old_columns.find { |c| default = c.type_cast_from_database(c.default) c.name == 'approved' && c.type == :boolean && default == true } change_column :test_models, :approved, :boolean, :default => false new_columns = connection.columns(TestModel.table_name) assert_not new_columns.find { |c| default = c.type_cast_from_database(c.default) c.name == 'approved' and c.type == :boolean and default == true } assert new_columns.find { |c| default = c.type_cast_from_database(c.default) c.name == 'approved' and c.type == :boolean and default == false } change_column :test_models, :approved, :boolean, :default => true end def test_change_column_with_nil_default add_column "test_models", "contributor", :boolean, :default => true assert TestModel.new.contributor? change_column "test_models", "contributor", :boolean, :default => nil TestModel.reset_column_information assert_not TestModel.new.contributor? assert_nil TestModel.new.contributor end def test_change_column_with_new_default add_column "test_models", "administrator", :boolean, :default => true assert TestModel.new.administrator? change_column "test_models", "administrator", :boolean, :default => false TestModel.reset_column_information assert_not TestModel.new.administrator? end def test_change_column_with_custom_index_name add_column "test_models", "category", :string add_index :test_models, :category, name: 'test_models_categories_idx' assert_equal ['test_models_categories_idx'], connection.indexes('test_models').map(&:name) change_column "test_models", "category", :string, null: false, default: 'article' assert_equal ['test_models_categories_idx'], connection.indexes('test_models').map(&:name) end def test_change_column_with_long_index_name table_name_prefix = 'test_models_' long_index_name = table_name_prefix + ('x' * (connection.allowed_index_name_length - table_name_prefix.length)) add_column "test_models", "category", :string add_index :test_models, :category, name: long_index_name change_column "test_models", "category", :string, null: false, default: 'article' assert_equal [long_index_name], connection.indexes('test_models').map(&:name) end def test_change_column_default add_column "test_models", "first_name", :string connection.change_column_default "test_models", "first_name", "Tester" assert_equal "Tester", TestModel.new.first_name end def test_change_column_default_to_null add_column "test_models", "first_name", :string connection.change_column_default "test_models", "first_name", nil assert_nil TestModel.new.first_name end def test_remove_column_no_second_parameter_raises_exception assert_raise(ArgumentError) { connection.remove_column("funny") } end def test_removing_and_renaming_column_preserves_custom_primary_key connection.create_table "my_table", primary_key: "my_table_id", force: true do |t| t.integer "col_one" t.string "col_two", limit: 128, null: false end remove_column("my_table", "col_two") rename_column("my_table", "col_one", "col_three") assert_equal 'my_table_id', connection.primary_key('my_table') ensure connection.drop_table(:my_table) rescue nil end def test_column_with_index connection.create_table "my_table", force: true do |t| t.string :item_number, index: true end assert connection.index_exists?("my_table", :item_number, name: :index_my_table_on_item_number) ensure connection.drop_table(:my_table) rescue nil end end end end rails-4.2.6/activerecord/test/cases/migration/command_recorder_test.rb000066400000000000000000000263751266740050600262670ustar00rootroot00000000000000require "cases/helper" module ActiveRecord class Migration class CommandRecorderTest < ActiveRecord::TestCase def setup connection = ActiveRecord::Base.connection @recorder = CommandRecorder.new(connection) end def test_respond_to_delegates recorder = CommandRecorder.new(Class.new { def america; end }.new) assert recorder.respond_to?(:america) end def test_send_calls_super assert_raises(NoMethodError) do @recorder.send(:non_existing_method, :horses) end end def test_send_delegates_to_record recorder = CommandRecorder.new(Class.new { def create_table(name); end }.new) assert recorder.respond_to?(:create_table), 'respond_to? create_table' recorder.send(:create_table, :horses) assert_equal [[:create_table, [:horses], nil]], recorder.commands end def test_unknown_commands_delegate recorder = CommandRecorder.new(stub(:foo => 'bar')) assert_equal 'bar', recorder.foo end def test_inverse_of_raise_exception_on_unknown_commands assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.inverse_of :execute, ['some sql'] end end def test_irreversible_commands_raise_exception assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.revert{ @recorder.execute 'some sql' } end end def test_record @recorder.record :create_table, [:system_settings] assert_equal 1, @recorder.commands.length end def test_inverted_commands_are_reversed @recorder.revert do @recorder.record :create_table, [:hello] @recorder.record :create_table, [:world] end tables = @recorder.commands.map{|_cmd, args, _block| args} assert_equal [[:world], [:hello]], tables end def test_revert_order block = Proc.new{|t| t.string :name } @recorder.instance_eval do create_table("apples", &block) revert do create_table("bananas", &block) revert do create_table("clementines", &block) create_table("dates") end create_table("elderberries") end revert do create_table("figs", &block) create_table("grapes") end end assert_equal [[:create_table, ["apples"], block], [:drop_table, ["elderberries"], nil], [:create_table, ["clementines"], block], [:create_table, ["dates"], nil], [:drop_table, ["bananas"], block], [:drop_table, ["grapes"], nil], [:drop_table, ["figs"], block]], @recorder.commands end def test_invert_change_table @recorder.revert do @recorder.change_table :fruits do |t| t.string :name t.rename :kind, :cultivar end end assert_equal [ [:rename_column, [:fruits, :cultivar, :kind]], [:remove_column, [:fruits, :name, :string, {}], nil], ], @recorder.commands assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.revert do @recorder.change_table :fruits do |t| t.remove :kind end end end end def test_invert_create_table @recorder.revert do @recorder.record :create_table, [:system_settings] end drop_table = @recorder.commands.first assert_equal [:drop_table, [:system_settings], nil], drop_table end def test_invert_create_table_with_options_and_block block = Proc.new{} drop_table = @recorder.inverse_of :create_table, [:people_reminders, id: false], &block assert_equal [:drop_table, [:people_reminders, id: false], block], drop_table end def test_invert_drop_table block = Proc.new{} create_table = @recorder.inverse_of :drop_table, [:people_reminders, id: false], &block assert_equal [:create_table, [:people_reminders, id: false], block], create_table end def test_invert_drop_table_without_a_block_nor_option assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.inverse_of :drop_table, [:people_reminders] end end def test_invert_create_join_table drop_join_table = @recorder.inverse_of :create_join_table, [:musics, :artists] assert_equal [:drop_join_table, [:musics, :artists], nil], drop_join_table end def test_invert_create_join_table_with_table_name drop_join_table = @recorder.inverse_of :create_join_table, [:musics, :artists, table_name: :catalog] assert_equal [:drop_join_table, [:musics, :artists, table_name: :catalog], nil], drop_join_table end def test_invert_drop_join_table block = Proc.new{} create_join_table = @recorder.inverse_of :drop_join_table, [:musics, :artists, table_name: :catalog], &block assert_equal [:create_join_table, [:musics, :artists, table_name: :catalog], block], create_join_table end def test_invert_rename_table rename = @recorder.inverse_of :rename_table, [:old, :new] assert_equal [:rename_table, [:new, :old]], rename end def test_invert_add_column remove = @recorder.inverse_of :add_column, [:table, :column, :type, {}] assert_equal [:remove_column, [:table, :column, :type, {}], nil], remove end def test_invert_change_column assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.inverse_of :change_column, [:table, :column, :type, {}] end end def test_invert_change_column_default assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.inverse_of :change_column_default, [:table, :column, 'default_value'] end end def test_invert_change_column_null add = @recorder.inverse_of :change_column_null, [:table, :column, true] assert_equal [:change_column_null, [:table, :column, false]], add end def test_invert_remove_column add = @recorder.inverse_of :remove_column, [:table, :column, :type, {}] assert_equal [:add_column, [:table, :column, :type, {}], nil], add end def test_invert_remove_column_without_type assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.inverse_of :remove_column, [:table, :column] end end def test_invert_rename_column rename = @recorder.inverse_of :rename_column, [:table, :old, :new] assert_equal [:rename_column, [:table, :new, :old]], rename end def test_invert_add_index remove = @recorder.inverse_of :add_index, [:table, [:one, :two]] assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove end def test_invert_add_index_with_name remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"] assert_equal [:remove_index, [:table, {name: "new_index"}]], remove end def test_invert_add_index_with_no_options remove = @recorder.inverse_of :add_index, [:table, [:one, :two]] assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove end def test_invert_remove_index add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], options: true}] assert_equal [:add_index, [:table, [:one, :two], options: true]], add end def test_invert_remove_index_with_name add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], name: "new_index"}] assert_equal [:add_index, [:table, [:one, :two], name: "new_index"]], add end def test_invert_remove_index_with_no_special_options add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two]}] assert_equal [:add_index, [:table, [:one, :two], {}]], add end def test_invert_remove_index_with_no_column assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.inverse_of :remove_index, [:table, name: "new_index"] end end def test_invert_rename_index rename = @recorder.inverse_of :rename_index, [:table, :old, :new] assert_equal [:rename_index, [:table, :new, :old]], rename end def test_invert_add_timestamps remove = @recorder.inverse_of :add_timestamps, [:table] assert_equal [:remove_timestamps, [:table], nil], remove end def test_invert_remove_timestamps add = @recorder.inverse_of :remove_timestamps, [:table, { null: true }] assert_equal [:add_timestamps, [:table, {null: true }], nil], add end def test_invert_add_reference remove = @recorder.inverse_of :add_reference, [:table, :taggable, { polymorphic: true }] assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }], nil], remove end def test_invert_add_belongs_to_alias remove = @recorder.inverse_of :add_belongs_to, [:table, :user] assert_equal [:remove_reference, [:table, :user], nil], remove end def test_invert_remove_reference add = @recorder.inverse_of :remove_reference, [:table, :taggable, { polymorphic: true }] assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }], nil], add end def test_invert_remove_reference_with_index_and_foreign_key add = @recorder.inverse_of :remove_reference, [:table, :taggable, { index: true, foreign_key: true }] assert_equal [:add_reference, [:table, :taggable, { index: true, foreign_key: true }], nil], add end def test_invert_remove_belongs_to_alias add = @recorder.inverse_of :remove_belongs_to, [:table, :user] assert_equal [:add_reference, [:table, :user], nil], add end def test_invert_enable_extension disable = @recorder.inverse_of :enable_extension, ['uuid-ossp'] assert_equal [:disable_extension, ['uuid-ossp'], nil], disable end def test_invert_disable_extension enable = @recorder.inverse_of :disable_extension, ['uuid-ossp'] assert_equal [:enable_extension, ['uuid-ossp'], nil], enable end def test_invert_add_foreign_key enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people] assert_equal [:remove_foreign_key, [:dogs, :people]], enable end def test_invert_add_foreign_key_with_column enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people, column: "owner_id"] assert_equal [:remove_foreign_key, [:dogs, column: "owner_id"]], enable end def test_invert_add_foreign_key_with_column_and_name enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people, column: "owner_id", name: "fk"] assert_equal [:remove_foreign_key, [:dogs, name: "fk"]], enable end def test_remove_foreign_key_is_irreversible assert_raises ActiveRecord::IrreversibleMigration do @recorder.inverse_of :remove_foreign_key, [:dogs, column: "owner_id"] end assert_raises ActiveRecord::IrreversibleMigration do @recorder.inverse_of :remove_foreign_key, [:dogs, name: "fk"] end end end end end rails-4.2.6/activerecord/test/cases/migration/create_join_table_test.rb000066400000000000000000000114661266740050600264100ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class Migration class CreateJoinTableTest < ActiveRecord::TestCase attr_reader :connection def setup super @connection = ActiveRecord::Base.connection end teardown do %w(artists_musics musics_videos catalog).each do |table_name| connection.drop_table table_name if connection.tables.include?(table_name) end end def test_create_join_table connection.create_join_table :artists, :musics assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort end def test_create_join_table_set_not_null_by_default connection.create_join_table :artists, :musics assert_equal [false, false], connection.columns(:artists_musics).map(&:null) end def test_create_join_table_with_strings connection.create_join_table 'artists', 'musics' assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort end def test_create_join_table_with_symbol_and_string connection.create_join_table :artists, 'musics' assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort end def test_create_join_table_with_the_proper_order connection.create_join_table :videos, :musics assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort end def test_create_join_table_with_the_table_name connection.create_join_table :artists, :musics, table_name: :catalog assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort end def test_create_join_table_with_the_table_name_as_string connection.create_join_table :artists, :musics, table_name: 'catalog' assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort end def test_create_join_table_with_column_options connection.create_join_table :artists, :musics, column_options: {null: true} assert_equal [true, true], connection.columns(:artists_musics).map(&:null) end def test_create_join_table_without_indexes connection.create_join_table :artists, :musics assert connection.indexes(:artists_musics).blank? end def test_create_join_table_with_index connection.create_join_table :artists, :musics do |t| t.index [:artist_id, :music_id] end assert_equal [%w(artist_id music_id)], connection.indexes(:artists_musics).map(&:columns) end def test_drop_join_table connection.create_join_table :artists, :musics connection.drop_join_table :artists, :musics assert !connection.tables.include?('artists_musics') end def test_drop_join_table_with_strings connection.create_join_table :artists, :musics connection.drop_join_table 'artists', 'musics' assert !connection.tables.include?('artists_musics') end def test_drop_join_table_with_the_proper_order connection.create_join_table :videos, :musics connection.drop_join_table :videos, :musics assert !connection.tables.include?('musics_videos') end def test_drop_join_table_with_the_table_name connection.create_join_table :artists, :musics, table_name: :catalog connection.drop_join_table :artists, :musics, table_name: :catalog assert !connection.tables.include?('catalog') end def test_drop_join_table_with_the_table_name_as_string connection.create_join_table :artists, :musics, table_name: 'catalog' connection.drop_join_table :artists, :musics, table_name: 'catalog' assert !connection.tables.include?('catalog') end def test_drop_join_table_with_column_options connection.create_join_table :artists, :musics, column_options: {null: true} connection.drop_join_table :artists, :musics, column_options: {null: true} assert !connection.tables.include?('artists_musics') end def test_create_and_drop_join_table_with_common_prefix with_table_cleanup do connection.create_join_table 'audio_artists', 'audio_musics' assert_includes connection.tables, 'audio_artists_musics' connection.drop_join_table 'audio_artists', 'audio_musics' assert !connection.tables.include?('audio_artists_musics'), "Should have dropped join table, but didn't" end end private def with_table_cleanup tables_before = connection.tables yield ensure tables_after = connection.tables - tables_before tables_after.each do |table| connection.execute "DROP TABLE #{table}" end end end end end rails-4.2.6/activerecord/test/cases/migration/foreign_key_test.rb000066400000000000000000000227631266740050600252620ustar00rootroot00000000000000require 'cases/helper' require 'support/ddl_helper' require 'support/schema_dumping_helper' if ActiveRecord::Base.connection.supports_foreign_keys? module ActiveRecord class Migration class ForeignKeyTest < ActiveRecord::TestCase include DdlHelper include SchemaDumpingHelper class Rocket < ActiveRecord::Base end class Astronaut < ActiveRecord::Base end setup do @connection = ActiveRecord::Base.connection @connection.create_table "rockets", force: true do |t| t.string :name end @connection.create_table "astronauts", force: true do |t| t.string :name t.references :rocket end end teardown do if defined?(@connection) @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts' @connection.drop_table "rockets" if @connection.table_exists? 'rockets' end end def test_foreign_keys foreign_keys = @connection.foreign_keys("fk_test_has_fk") assert_equal 1, foreign_keys.size fk = foreign_keys.first assert_equal "fk_test_has_fk", fk.from_table assert_equal "fk_test_has_pk", fk.to_table assert_equal "fk_id", fk.column assert_equal "pk_id", fk.primary_key assert_equal "fk_name", fk.name end def test_add_foreign_key_inferes_column @connection.add_foreign_key :astronauts, :rockets foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size fk = foreign_keys.first assert_equal "astronauts", fk.from_table assert_equal "rockets", fk.to_table assert_equal "rocket_id", fk.column assert_equal "id", fk.primary_key assert_equal("fk_rails_78146ddd2e", fk.name) end def test_add_foreign_key_with_column @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id" foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size fk = foreign_keys.first assert_equal "astronauts", fk.from_table assert_equal "rockets", fk.to_table assert_equal "rocket_id", fk.column assert_equal "id", fk.primary_key assert_equal("fk_rails_78146ddd2e", fk.name) end def test_add_foreign_key_with_non_standard_primary_key with_example_table @connection, "space_shuttles", "pk integer PRIMARY KEY" do @connection.add_foreign_key(:astronauts, :space_shuttles, column: "rocket_id", primary_key: "pk", name: "custom_pk") foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size fk = foreign_keys.first assert_equal "astronauts", fk.from_table assert_equal "space_shuttles", fk.to_table assert_equal "pk", fk.primary_key @connection.remove_foreign_key :astronauts, name: "custom_pk" end end def test_add_on_delete_restrict_foreign_key @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :restrict foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size fk = foreign_keys.first if current_adapter?(:MysqlAdapter, :Mysql2Adapter) # ON DELETE RESTRICT is the default on MySQL assert_equal nil, fk.on_delete else assert_equal :restrict, fk.on_delete end end def test_add_on_delete_cascade_foreign_key @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :cascade foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size fk = foreign_keys.first assert_equal :cascade, fk.on_delete end def test_add_on_delete_nullify_foreign_key @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size fk = foreign_keys.first assert_equal :nullify, fk.on_delete end def test_on_update_and_on_delete_raises_with_invalid_values assert_raises ArgumentError do @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :invalid end assert_raises ArgumentError do @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :invalid end end def test_add_foreign_key_with_on_update @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :nullify foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size fk = foreign_keys.first assert_equal :nullify, fk.on_update end def test_remove_foreign_key_inferes_column @connection.add_foreign_key :astronauts, :rockets assert_equal 1, @connection.foreign_keys("astronauts").size @connection.remove_foreign_key :astronauts, :rockets assert_equal [], @connection.foreign_keys("astronauts") end def test_remove_foreign_key_by_column @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id" assert_equal 1, @connection.foreign_keys("astronauts").size @connection.remove_foreign_key :astronauts, column: "rocket_id" assert_equal [], @connection.foreign_keys("astronauts") end def test_remove_foreign_key_by_symbol_column @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id assert_equal 1, @connection.foreign_keys("astronauts").size @connection.remove_foreign_key :astronauts, column: :rocket_id assert_equal [], @connection.foreign_keys("astronauts") end def test_remove_foreign_key_by_name @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk" assert_equal 1, @connection.foreign_keys("astronauts").size @connection.remove_foreign_key :astronauts, name: "fancy_named_fk" assert_equal [], @connection.foreign_keys("astronauts") end def test_remove_foreign_non_existing_foreign_key_raises assert_raises ArgumentError do @connection.remove_foreign_key :astronauts, :rockets end end def test_schema_dumping @connection.add_foreign_key :astronauts, :rockets output = dump_table_schema "astronauts" assert_match %r{\s+add_foreign_key "astronauts", "rockets"$}, output end def test_schema_dumping_with_options output = dump_table_schema "fk_test_has_fk" assert_match %r{\s+add_foreign_key "fk_test_has_fk", "fk_test_has_pk", column: "fk_id", primary_key: "pk_id", name: "fk_name"$}, output end def test_schema_dumping_on_delete_and_on_update_options @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify, on_update: :cascade output = dump_table_schema "astronauts" assert_match %r{\s+add_foreign_key "astronauts",.+on_update: :cascade,.+on_delete: :nullify$}, output end class CreateCitiesAndHousesMigration < ActiveRecord::Migration def change create_table("cities") { |t| } create_table("houses") do |t| t.column :city_id, :integer end add_foreign_key :houses, :cities, column: "city_id" end end def test_add_foreign_key_is_reversible migration = CreateCitiesAndHousesMigration.new silence_stream($stdout) { migration.migrate(:up) } assert_equal 1, @connection.foreign_keys("houses").size ensure silence_stream($stdout) { migration.migrate(:down) } end class CreateSchoolsAndClassesMigration < ActiveRecord::Migration def change create_table(:schools) create_table(:classes) do |t| t.column :school_id, :integer end add_foreign_key :classes, :schools end end def test_add_foreign_key_with_prefix ActiveRecord::Base.table_name_prefix = 'p_' migration = CreateSchoolsAndClassesMigration.new silence_stream($stdout) { migration.migrate(:up) } assert_equal 1, @connection.foreign_keys("p_classes").size ensure silence_stream($stdout) { migration.migrate(:down) } ActiveRecord::Base.table_name_prefix = nil end def test_add_foreign_key_with_suffix ActiveRecord::Base.table_name_suffix = '_s' migration = CreateSchoolsAndClassesMigration.new silence_stream($stdout) { migration.migrate(:up) } assert_equal 1, @connection.foreign_keys("classes_s").size ensure silence_stream($stdout) { migration.migrate(:down) } ActiveRecord::Base.table_name_suffix = nil end end end end else module ActiveRecord class Migration class NoForeignKeySupportTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection end def test_add_foreign_key_should_be_noop @connection.add_foreign_key :clubs, :categories end def test_remove_foreign_key_should_be_noop @connection.remove_foreign_key :clubs, :categories end def test_foreign_keys_should_raise_not_implemented assert_raises NotImplementedError do @connection.foreign_keys("clubs") end end end end end end rails-4.2.6/activerecord/test/cases/migration/helper.rb000066400000000000000000000017651266740050600232000ustar00rootroot00000000000000require "cases/helper" module ActiveRecord class Migration class << self; attr_accessor :message_count; end self.message_count = 0 module TestHelper attr_reader :connection, :table_name CONNECTION_METHODS = %w[add_column remove_column rename_column add_index change_column rename_table column_exists? index_exists? add_reference add_belongs_to remove_reference remove_references remove_belongs_to] class TestModel < ActiveRecord::Base self.table_name = :test_models end def setup super @connection = ActiveRecord::Base.connection connection.create_table :test_models do |t| t.timestamps null: true end TestModel.reset_column_information end def teardown super TestModel.reset_table_name TestModel.reset_sequence_name connection.drop_table :test_models rescue nil end private delegate(*CONNECTION_METHODS, to: :connection) end end end rails-4.2.6/activerecord/test/cases/migration/index_test.rb000066400000000000000000000206111266740050600240560ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class Migration class IndexTest < ActiveRecord::TestCase attr_reader :connection, :table_name def setup super @connection = ActiveRecord::Base.connection @table_name = :testings connection.create_table table_name do |t| t.column :foo, :string, :limit => 100 t.column :bar, :string, :limit => 100 t.string :first_name t.string :last_name, :limit => 100 t.string :key, :limit => 100 t.boolean :administrator end end teardown do connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil end def test_rename_index # keep the names short to make Oracle and similar behave connection.add_index(table_name, [:foo], :name => 'old_idx') connection.rename_index(table_name, 'old_idx', 'new_idx') # if the adapter doesn't support the indexes call, pick defaults that let the test pass assert_not connection.index_name_exists?(table_name, 'old_idx', false) assert connection.index_name_exists?(table_name, 'new_idx', true) end def test_rename_index_too_long too_long_index_name = good_index_name + 'x' # keep the names short to make Oracle and similar behave connection.add_index(table_name, [:foo], :name => 'old_idx') e = assert_raises(ArgumentError) { connection.rename_index(table_name, 'old_idx', too_long_index_name) } assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message) # if the adapter doesn't support the indexes call, pick defaults that let the test pass assert connection.index_name_exists?(table_name, 'old_idx', false) end def test_double_add_index connection.add_index(table_name, [:foo], :name => 'some_idx') assert_raises(ArgumentError) { connection.add_index(table_name, [:foo], :name => 'some_idx') } end def test_remove_nonexistent_index assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") } end def test_add_index_works_with_long_index_names connection.add_index(table_name, "foo", name: good_index_name) assert connection.index_name_exists?(table_name, good_index_name, false) connection.remove_index(table_name, name: good_index_name) end def test_add_index_does_not_accept_too_long_index_names too_long_index_name = good_index_name + 'x' e = assert_raises(ArgumentError) { connection.add_index(table_name, "foo", name: too_long_index_name) } assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message) assert_not connection.index_name_exists?(table_name, too_long_index_name, false) connection.add_index(table_name, "foo", :name => good_index_name) end def test_internal_index_with_name_matching_database_limit good_index_name = 'x' * connection.index_name_length connection.add_index(table_name, "foo", name: good_index_name, internal: true) assert connection.index_name_exists?(table_name, good_index_name, false) connection.remove_index(table_name, name: good_index_name) end def test_index_symbol_names connection.add_index table_name, :foo, :name => :symbol_index_name assert connection.index_exists?(table_name, :foo, :name => :symbol_index_name) connection.remove_index table_name, :name => :symbol_index_name assert_not connection.index_exists?(table_name, :foo, :name => :symbol_index_name) end def test_index_exists connection.add_index :testings, :foo assert connection.index_exists?(:testings, :foo) assert !connection.index_exists?(:testings, :bar) end def test_index_exists_on_multiple_columns connection.add_index :testings, [:foo, :bar] assert connection.index_exists?(:testings, [:foo, :bar]) end def test_index_exists_with_custom_name_checks_columns connection.add_index :testings, [:foo, :bar], name: "my_index" assert connection.index_exists?(:testings, [:foo, :bar], name: "my_index") assert_not connection.index_exists?(:testings, [:foo], name: "my_index") end def test_valid_index_options assert_raise ArgumentError do connection.add_index :testings, :foo, unqiue: true end end def test_unique_index_exists connection.add_index :testings, :foo, :unique => true assert connection.index_exists?(:testings, :foo, :unique => true) end def test_named_index_exists connection.add_index :testings, :foo, :name => "custom_index_name" assert connection.index_exists?(:testings, :foo, :name => "custom_index_name") end def test_add_index_attribute_length_limit connection.add_index :testings, [:foo, :bar], :length => {:foo => 10, :bar => nil} assert connection.index_exists?(:testings, [:foo, :bar]) end def test_add_index connection.add_index("testings", "last_name") connection.remove_index("testings", "last_name") connection.add_index("testings", ["last_name", "first_name"]) connection.remove_index("testings", :column => ["last_name", "first_name"]) # Oracle adapter cannot have specified index name larger than 30 characters # Oracle adapter is shortening index name when just column list is given unless current_adapter?(:OracleAdapter) connection.add_index("testings", ["last_name", "first_name"]) connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name) connection.add_index("testings", ["last_name", "first_name"]) connection.remove_index("testings", "last_name_and_first_name") end connection.add_index("testings", ["last_name", "first_name"]) connection.remove_index("testings", ["last_name", "first_name"]) connection.add_index("testings", ["last_name"], :length => 10) connection.remove_index("testings", "last_name") connection.add_index("testings", ["last_name"], :length => {:last_name => 10}) connection.remove_index("testings", ["last_name"]) connection.add_index("testings", ["last_name", "first_name"], :length => 10) connection.remove_index("testings", ["last_name", "first_name"]) connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) connection.remove_index("testings", ["last_name", "first_name"]) connection.add_index("testings", ["key"], :name => "key_idx", :unique => true) connection.remove_index("testings", :name => "key_idx", :unique => true) connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin") connection.remove_index("testings", :name => "named_admin") # Selected adapters support index sort order if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) connection.add_index("testings", ["last_name"], :order => {:last_name => :desc}) connection.remove_index("testings", ["last_name"]) connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc}) connection.remove_index("testings", ["last_name", "first_name"]) connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc}) connection.remove_index("testings", ["last_name", "first_name"]) connection.add_index("testings", ["last_name", "first_name"], :order => :desc) connection.remove_index("testings", ["last_name", "first_name"]) end end if current_adapter?(:PostgreSQLAdapter) def test_add_partial_index connection.add_index("testings", "last_name", :where => "first_name = 'john doe'") assert connection.index_exists?("testings", "last_name") connection.remove_index("testings", "last_name") assert !connection.index_exists?("testings", "last_name") end end private def good_index_name 'x' * connection.allowed_index_name_length end end end end rails-4.2.6/activerecord/test/cases/migration/logger_test.rb000066400000000000000000000017221266740050600242300ustar00rootroot00000000000000require "cases/helper" module ActiveRecord class Migration class LoggerTest < ActiveRecord::TestCase # MySQL can't roll back ddl changes self.use_transactional_fixtures = false Migration = Struct.new(:name, :version) do def disable_ddl_transaction; false end def migrate direction # do nothing end end def setup super ActiveRecord::SchemaMigration.create_table ActiveRecord::SchemaMigration.delete_all end teardown do ActiveRecord::SchemaMigration.drop_table end def test_migration_should_be_run_without_logger previous_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = nil migrations = [Migration.new('a', 1), Migration.new('b', 2), Migration.new('c', 3)] ActiveRecord::Migrator.new(:up, migrations).migrate ensure ActiveRecord::Base.logger = previous_logger end end end end rails-4.2.6/activerecord/test/cases/migration/pending_migrations_test.rb000066400000000000000000000025401266740050600266300ustar00rootroot00000000000000require 'cases/helper' require "minitest/mock" module ActiveRecord class Migration class PendingMigrationsTest < ActiveRecord::TestCase def setup super @connection = Minitest::Mock.new @app = Minitest::Mock.new conn = @connection @pending = Class.new(CheckPending) { define_method(:connection) { conn } }.new(@app) @pending.instance_variable_set :@last_check, -1 # Force checking end def teardown assert @connection.verify assert @app.verify super end def test_errors_if_pending @connection.expect :supports_migrations?, true ActiveRecord::Migrator.stub :needs_migration?, true do assert_raise ActiveRecord::PendingMigrationError do @pending.call(nil) end end end def test_checks_if_supported @connection.expect :supports_migrations?, true @app.expect :call, nil, [:foo] ActiveRecord::Migrator.stub :needs_migration?, false do @pending.call(:foo) end end def test_doesnt_check_if_unsupported @connection.expect :supports_migrations?, false @app.expect :call, nil, [:foo] ActiveRecord::Migrator.stub :needs_migration?, true do @pending.call(:foo) end end end end end rails-4.2.6/activerecord/test/cases/migration/references_foreign_key_test.rb000066400000000000000000000135231266740050600274550ustar00rootroot00000000000000require 'cases/helper' if ActiveRecord::Base.connection.supports_foreign_keys? module ActiveRecord class Migration class ReferencesForeignKeyTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection @connection.create_table(:testing_parents, force: true) end teardown do @connection.drop_table("testings") if @connection.table_exists? "testings" @connection.drop_table("testing_parents") if @connection.table_exists? "testing_parents" end test "foreign keys can be created with the table" do @connection.create_table :testings do |t| t.references :testing_parent, foreign_key: true end fk = @connection.foreign_keys("testings").first assert_equal "testings", fk.from_table assert_equal "testing_parents", fk.to_table end test "no foreign key is created by default" do @connection.create_table :testings do |t| t.references :testing_parent end assert_equal [], @connection.foreign_keys("testings") end test "options hash can be passed" do @connection.change_table :testing_parents do |t| t.integer :other_id t.index :other_id, unique: true end @connection.create_table :testings do |t| t.references :testing_parent, foreign_key: { primary_key: :other_id } end fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" } assert_equal "other_id", fk.primary_key end test "foreign keys cannot be added to polymorphic relations when creating the table" do @connection.create_table :testings do |t| assert_raises(ArgumentError) do t.references :testing_parent, polymorphic: true, foreign_key: true end end end test "foreign keys can be created while changing the table" do @connection.create_table :testings @connection.change_table :testings do |t| t.references :testing_parent, foreign_key: true end fk = @connection.foreign_keys("testings").first assert_equal "testings", fk.from_table assert_equal "testing_parents", fk.to_table end test "foreign keys are not added by default when changing the table" do @connection.create_table :testings @connection.change_table :testings do |t| t.references :testing_parent end assert_equal [], @connection.foreign_keys("testings") end test "foreign keys accept options when changing the table" do @connection.change_table :testing_parents do |t| t.integer :other_id t.index :other_id, unique: true end @connection.create_table :testings @connection.change_table :testings do |t| t.references :testing_parent, foreign_key: { primary_key: :other_id } end fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" } assert_equal "other_id", fk.primary_key end test "foreign keys cannot be added to polymorphic relations when changing the table" do @connection.create_table :testings @connection.change_table :testings do |t| assert_raises(ArgumentError) do t.references :testing_parent, polymorphic: true, foreign_key: true end end end test "foreign key column can be removed" do @connection.create_table :testings do |t| t.references :testing_parent, index: true, foreign_key: true end assert_difference "@connection.foreign_keys('testings').size", -1 do @connection.remove_reference :testings, :testing_parent, foreign_key: true end end test "foreign key methods respect pluralize_table_names" do begin original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names ActiveRecord::Base.pluralize_table_names = false @connection.create_table :testing @connection.change_table :testing_parents do |t| t.references :testing, foreign_key: true end fk = @connection.foreign_keys("testing_parents").first assert_equal "testing_parents", fk.from_table assert_equal "testing", fk.to_table assert_difference "@connection.foreign_keys('testing_parents').size", -1 do @connection.remove_reference :testing_parents, :testing, foreign_key: true end ensure ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names @connection.drop_table "testing", if_exists: true end end test "multiple foreign keys can be added to the same table" do @connection.create_table :testings do |t| t.integer :col_1 t.integer :col_2 t.foreign_key :testing_parents, column: :col_1 t.foreign_key :testing_parents, column: :col_2 end fks = @connection.foreign_keys("testings").sort_by(&:column) fk_definitions = fks.map {|fk| [fk.from_table, fk.to_table, fk.column] } assert_equal([["testings", "testing_parents", "col_1"], ["testings", "testing_parents", "col_2"]], fk_definitions) end end end end else class ReferencesWithoutForeignKeySupportTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection @connection.create_table(:testing_parents, force: true) end teardown do @connection.drop_table("testings", if_exists: true) @connection.drop_table("testing_parents", if_exists: true) end test "ignores foreign keys defined with the table" do @connection.create_table :testings do |t| t.references :testing_parent, foreign_key: true end assert_includes @connection.tables, "testings" end end end rails-4.2.6/activerecord/test/cases/migration/references_index_test.rb000066400000000000000000000064051266740050600262640ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class Migration class ReferencesIndexTest < ActiveRecord::TestCase attr_reader :connection, :table_name def setup super @connection = ActiveRecord::Base.connection @table_name = :testings end teardown do connection.drop_table :testings rescue nil end def test_creates_index connection.create_table table_name do |t| t.references :foo, :index => true end assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) end def test_does_not_create_index connection.create_table table_name do |t| t.references :foo end assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) end def test_does_not_create_index_explicit connection.create_table table_name do |t| t.references :foo, :index => false end assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) end def test_creates_index_with_options connection.create_table table_name do |t| t.references :foo, :index => {:name => :index_testings_on_yo_momma} t.references :bar, :index => {:unique => true} end assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma) assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true) end unless current_adapter? :OracleAdapter def test_creates_polymorphic_index connection.create_table table_name do |t| t.references :foo, :polymorphic => true, :index => true end assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo_type_and_foo_id) end end def test_creates_index_for_existing_table connection.create_table table_name connection.change_table table_name do |t| t.references :foo, :index => true end assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) end def test_does_not_create_index_for_existing_table connection.create_table table_name connection.change_table table_name do |t| t.references :foo end assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) end def test_does_not_create_index_for_existing_table_explicit connection.create_table table_name connection.change_table table_name do |t| t.references :foo, :index => false end assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) end unless current_adapter? :OracleAdapter def test_creates_polymorphic_index_for_existing_table connection.create_table table_name connection.change_table table_name do |t| t.references :foo, :polymorphic => true, :index => true end assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo_type_and_foo_id) end end end end end rails-4.2.6/activerecord/test/cases/migration/references_statements_test.rb000066400000000000000000000072221266740050600273420ustar00rootroot00000000000000require "cases/migration/helper" module ActiveRecord class Migration class ReferencesStatementsTest < ActiveRecord::TestCase include ActiveRecord::Migration::TestHelper self.use_transactional_fixtures = false def setup super @table_name = :test_models add_column table_name, :supplier_id, :integer add_index table_name, :supplier_id end def test_creates_reference_id_column add_reference table_name, :user assert column_exists?(table_name, :user_id, :integer) end def test_does_not_create_reference_type_column add_reference table_name, :taggable assert_not column_exists?(table_name, :taggable_type, :string) end def test_creates_reference_type_column add_reference table_name, :taggable, polymorphic: true assert column_exists?(table_name, :taggable_type, :string) end def test_creates_reference_id_index add_reference table_name, :user, index: true assert index_exists?(table_name, :user_id) end def test_does_not_create_reference_id_index add_reference table_name, :user assert_not index_exists?(table_name, :user_id) end def test_creates_polymorphic_index add_reference table_name, :taggable, polymorphic: true, index: true assert index_exists?(table_name, [:taggable_type, :taggable_id]) end def test_creates_reference_type_column_with_default add_reference table_name, :taggable, polymorphic: { default: 'Photo' }, index: true assert column_exists?(table_name, :taggable_type, :string, default: 'Photo') end def test_creates_named_index add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id' } assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id') end def test_creates_reference_id_with_specified_type add_reference table_name, :user, type: :string assert column_exists?(table_name, :user_id, :string) end def test_deletes_reference_id_column remove_reference table_name, :supplier assert_not column_exists?(table_name, :supplier_id, :integer) end def test_deletes_reference_id_index remove_reference table_name, :supplier assert_not index_exists?(table_name, :supplier_id) end def test_does_not_delete_reference_type_column with_polymorphic_column do remove_reference table_name, :supplier assert_not column_exists?(table_name, :supplier_id, :integer) assert column_exists?(table_name, :supplier_type, :string) end end def test_deletes_reference_type_column with_polymorphic_column do remove_reference table_name, :supplier, polymorphic: true assert_not column_exists?(table_name, :supplier_type, :string) end end def test_deletes_polymorphic_index with_polymorphic_column do remove_reference table_name, :supplier, polymorphic: true assert_not index_exists?(table_name, [:supplier_id, :supplier_type]) end end def test_add_belongs_to_alias add_belongs_to table_name, :user assert column_exists?(table_name, :user_id, :integer) end def test_remove_belongs_to_alias remove_belongs_to table_name, :supplier assert_not column_exists?(table_name, :supplier_id, :integer) end private def with_polymorphic_column add_column table_name, :supplier_type, :string add_index table_name, [:supplier_id, :supplier_type] yield end end end end rails-4.2.6/activerecord/test/cases/migration/rename_table_test.rb000066400000000000000000000067641266740050600254020ustar00rootroot00000000000000require "cases/migration/helper" module ActiveRecord class Migration class RenameTableTest < ActiveRecord::TestCase include ActiveRecord::Migration::TestHelper self.use_transactional_fixtures = false def setup super add_column 'test_models', :url, :string remove_column 'test_models', :created_at remove_column 'test_models', :updated_at end def teardown rename_table :octopi, :test_models if connection.table_exists? :octopi super end if current_adapter?(:SQLite3Adapter) def test_rename_table_for_sqlite_should_work_with_reserved_words renamed = false add_column :test_models, :url, :string connection.rename_table :references, :old_references connection.rename_table :test_models, :references renamed = true # Using explicit id in insert for compatibility across all databases connection.execute "INSERT INTO 'references' (url, created_at, updated_at) VALUES ('http://rubyonrails.com', 0, 0)" assert_equal 'http://rubyonrails.com', connection.select_value("SELECT url FROM 'references' WHERE id=1") ensure return unless renamed connection.rename_table :references, :test_models connection.rename_table :old_references, :references end end def test_rename_table rename_table :test_models, :octopi connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") end def test_rename_table_with_an_index add_index :test_models, :url rename_table :test_models, :octopi connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") index = connection.indexes(:octopi).first assert index.columns.include?("url") assert_equal 'index_octopi_on_url', index.name end def test_rename_table_does_not_rename_custom_named_index add_index :test_models, :url, name: 'special_url_idx' rename_table :test_models, :octopi assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name) end if current_adapter?(:PostgreSQLAdapter) def test_rename_table_for_postgresql_should_also_rename_default_sequence rename_table :test_models, :octopi pk, seq = connection.pk_and_sequence_for('octopi') assert_equal ConnectionAdapters::PostgreSQL::Name.new("public", "octopi_#{pk}_seq"), seq end def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences enable_extension!('uuid-ossp', connection) connection.create_table :cats, id: :uuid assert_nothing_raised { rename_table :cats, :felines } assert connection.table_exists? :felines ensure disable_extension!('uuid-ossp', connection) connection.drop_table :cats if connection.table_exists? :cats connection.drop_table :felines if connection.table_exists? :felines end end end end end rails-4.2.6/activerecord/test/cases/migration/table_and_index_test.rb000066400000000000000000000020401266740050600260430ustar00rootroot00000000000000require "cases/helper" module ActiveRecord class Migration class TableAndIndexTest < ActiveRecord::TestCase def test_add_schema_info_respects_prefix_and_suffix conn = ActiveRecord::Base.connection conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters ActiveRecord::Base.table_name_prefix = 'p_' ActiveRecord::Base.table_name_suffix = '_s' conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) conn.initialize_schema_migrations_table assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] ensure ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" end end end end rails-4.2.6/activerecord/test/cases/migration_test.rb000066400000000000000000001002511266740050600227460ustar00rootroot00000000000000require "cases/helper" require "cases/migration/helper" require 'bigdecimal/util' require 'models/person' require 'models/topic' require 'models/developer' require 'models/computer' require MIGRATIONS_ROOT + "/valid/2_we_need_reminders" require MIGRATIONS_ROOT + "/rename/1_we_need_things" require MIGRATIONS_ROOT + "/rename/2_rename_things" require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers" class BigNumber < ActiveRecord::Base unless current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) attribute :value_of_e, Type::Integer.new end attribute :my_house_population, Type::Integer.new end class Reminder < ActiveRecord::Base; end class Thing < ActiveRecord::Base; end class MigrationTest < ActiveRecord::TestCase self.use_transactional_fixtures = false fixtures :people def setup super %w(reminders people_reminders prefix_reminders_suffix p_things_s).each do |table| Reminder.connection.drop_table(table) rescue nil end Reminder.reset_column_information @verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false ActiveRecord::Base.connection.schema_cache.clear! end teardown do ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" ActiveRecord::Base.connection.initialize_schema_migrations_table ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table| Thing.connection.drop_table(table) rescue nil end Thing.reset_column_information %w(reminders people_reminders prefix_reminders_suffix).each do |table| Reminder.connection.drop_table(table) rescue nil end Reminder.reset_table_name Reminder.reset_column_information %w(last_name key bio age height wealth birthday favorite_day moment_of_truth male administrator funny).each do |column| Person.connection.remove_column('people', column) rescue nil end Person.connection.remove_column("people", "first_name") rescue nil Person.connection.remove_column("people", "middle_name") rescue nil Person.connection.add_column("people", "first_name", :string) Person.reset_column_information ActiveRecord::Migration.verbose = @verbose_was end def test_migrator_versions migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths ActiveRecord::Migrator.migrations_paths = migrations_path ActiveRecord::Migrator.up(migrations_path) assert_equal 3, ActiveRecord::Migrator.current_version assert_equal 3, ActiveRecord::Migrator.last_version assert_equal false, ActiveRecord::Migrator.needs_migration? ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid") assert_equal 0, ActiveRecord::Migrator.current_version assert_equal 3, ActiveRecord::Migrator.last_version assert_equal true, ActiveRecord::Migrator.needs_migration? ActiveRecord::SchemaMigration.create!(:version => ActiveRecord::Migrator.last_version) assert_equal true, ActiveRecord::Migrator.needs_migration? ensure ActiveRecord::Migrator.migrations_paths = old_path end def test_migration_detection_without_schema_migration_table ActiveRecord::Base.connection.drop_table('schema_migrations') if ActiveRecord::Base.connection.table_exists?('schema_migrations') migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths ActiveRecord::Migrator.migrations_paths = migrations_path assert_equal true, ActiveRecord::Migrator.needs_migration? ensure ActiveRecord::Migrator.migrations_paths = old_path end def test_any_migrations old_path = ActiveRecord::Migrator.migrations_paths ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/valid" assert ActiveRecord::Migrator.any_migrations? ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/empty" assert_not ActiveRecord::Migrator.any_migrations? ensure ActiveRecord::Migrator.migrations_paths = old_path end def test_migration_version ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) end def test_create_table_with_force_true_does_not_drop_nonexisting_table if Person.connection.table_exists?(:testings2) Person.connection.drop_table :testings2 end # using a copy as we need the drop_table method to # continue to work for the ensure block of the test temp_conn = Person.connection.dup assert_not_equal temp_conn, Person.connection temp_conn.create_table :testings2, :force => true do |t| t.column :foo, :string end ensure Person.connection.drop_table :testings2 rescue nil end def connection ActiveRecord::Base.connection end def test_migration_instance_has_connection migration = Class.new(ActiveRecord::Migration).new assert_equal connection, migration.connection end def test_method_missing_delegates_to_connection migration = Class.new(ActiveRecord::Migration) { def connection Class.new { def create_table; "hi mom!"; end }.new end }.new assert_equal "hi mom!", migration.method_missing(:create_table) end def test_add_table_with_decimals Person.connection.drop_table :big_numbers rescue nil assert !BigNumber.table_exists? GiveMeBigNumbers.up assert BigNumber.create( :bank_balance => 1586.43, :big_bank_balance => BigDecimal("1000234000567.95"), :world_population => 6000000000, :my_house_population => 3, :value_of_e => BigDecimal("2.7182818284590452353602875") ) b = BigNumber.first assert_not_nil b assert_not_nil b.bank_balance assert_not_nil b.big_bank_balance assert_not_nil b.world_population assert_not_nil b.my_house_population assert_not_nil b.value_of_e # TODO: set world_population >= 2**62 to cover 64-bit platforms and test # is_a?(Bignum) assert_kind_of Integer, b.world_population assert_equal 6000000000, b.world_population assert_kind_of Fixnum, b.my_house_population assert_equal 3, b.my_house_population assert_kind_of BigDecimal, b.bank_balance assert_equal BigDecimal("1586.43"), b.bank_balance assert_kind_of BigDecimal, b.big_bank_balance assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with # precision/scale explicitly left out. By the SQL standard, numbers # assigned to this field should be truncated but that's seldom respected. if current_adapter?(:PostgreSQLAdapter) # - PostgreSQL changes the SQL spec on columns declared simply as # "decimal" to something more useful: instead of being given a scale # of 0, they take on the compile-time limit for precision and scale, # so the following should succeed unless you have used really wacky # compilation options # - SQLite2 has the default behavior of preserving all data sent in, # so this happens there too assert_kind_of BigDecimal, b.value_of_e assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e elsif current_adapter?(:SQLite3Adapter) # - SQLite3 stores a float, in violation of SQL assert_kind_of BigDecimal, b.value_of_e assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001 else # - SQL standard is an integer assert_kind_of Fixnum, b.value_of_e assert_equal 2, b.value_of_e end GiveMeBigNumbers.down assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first } end def test_filtering_migrations assert_no_column Person, :last_name assert !Reminder.table_exists? name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" } ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter) assert_column Person, :last_name assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter) assert_no_column Person, :last_name assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } end class MockMigration < ActiveRecord::Migration attr_reader :went_up, :went_down def initialize @went_up = false @went_down = false end def up @went_up = true super end def down @went_down = true super end end def test_instance_based_migration_up migration = MockMigration.new assert !migration.went_up, 'have not gone up' assert !migration.went_down, 'have not gone down' migration.migrate :up assert migration.went_up, 'have gone up' assert !migration.went_down, 'have not gone down' end def test_instance_based_migration_down migration = MockMigration.new assert !migration.went_up, 'have not gone up' assert !migration.went_down, 'have not gone down' migration.migrate :down assert !migration.went_up, 'have gone up' assert migration.went_down, 'have not gone down' end if ActiveRecord::Base.connection.supports_ddl_transactions? def test_migrator_one_up_with_exception_and_rollback assert_no_column Person, :last_name migration = Class.new(ActiveRecord::Migration) { def version; 100 end def migrate(x) add_column "people", "last_name", :string raise 'Something broke' end }.new migrator = ActiveRecord::Migrator.new(:up, [migration], 100) e = assert_raise(StandardError) { migrator.migrate } assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message assert_no_column Person, :last_name, "On error, the Migrator should revert schema changes but it did not." end def test_migrator_one_up_with_exception_and_rollback_using_run assert_no_column Person, :last_name migration = Class.new(ActiveRecord::Migration) { def version; 100 end def migrate(x) add_column "people", "last_name", :string raise 'Something broke' end }.new migrator = ActiveRecord::Migrator.new(:up, [migration], 100) e = assert_raise(StandardError) { migrator.run } assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message assert_no_column Person, :last_name, "On error, the Migrator should revert schema changes but it did not." end def test_migration_without_transaction assert_no_column Person, :last_name migration = Class.new(ActiveRecord::Migration) { self.disable_ddl_transaction! def version; 101 end def migrate(x) add_column "people", "last_name", :string raise 'Something broke' end }.new migrator = ActiveRecord::Migrator.new(:up, [migration], 101) e = assert_raise(StandardError) { migrator.migrate } assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message assert_column Person, :last_name, "without ddl transactions, the Migrator should not rollback on error but it did." ensure Person.reset_column_information if Person.column_names.include?('last_name') Person.connection.remove_column('people', 'last_name') end end end def test_schema_migrations_table_name original_schema_migrations_table_name = ActiveRecord::Migrator.schema_migrations_table_name assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" Reminder.reset_table_name assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name ActiveRecord::Base.schema_migrations_table_name = "changed" Reminder.reset_table_name assert_equal "prefix_changed_suffix", ActiveRecord::Migrator.schema_migrations_table_name ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" Reminder.reset_table_name assert_equal "changed", ActiveRecord::Migrator.schema_migrations_table_name ensure ActiveRecord::Base.schema_migrations_table_name = original_schema_migrations_table_name Reminder.reset_table_name end def test_proper_table_name_on_migration reminder_class = new_isolated_reminder_class migration = ActiveRecord::Migration.new assert_equal "table", migration.proper_table_name('table') assert_equal "table", migration.proper_table_name(:table) assert_equal "reminders", migration.proper_table_name(reminder_class) reminder_class.reset_table_name assert_equal reminder_class.table_name, migration.proper_table_name(reminder_class) # Use the model's own prefix/suffix if a model is given ActiveRecord::Base.table_name_prefix = "ARprefix_" ActiveRecord::Base.table_name_suffix = "_ARsuffix" reminder_class.table_name_prefix = 'prefix_' reminder_class.table_name_suffix = '_suffix' reminder_class.reset_table_name assert_equal "prefix_reminders_suffix", migration.proper_table_name(reminder_class) reminder_class.table_name_prefix = '' reminder_class.table_name_suffix = '' reminder_class.reset_table_name # Use AR::Base's prefix/suffix if string or symbol is given ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" reminder_class.reset_table_name assert_equal "prefix_table_suffix", migration.proper_table_name('table', migration.table_name_options) assert_equal "prefix_table_suffix", migration.proper_table_name(:table, migration.table_name_options) end def test_rename_table_with_prefix_and_suffix assert !Thing.table_exists? ActiveRecord::Base.table_name_prefix = 'p_' ActiveRecord::Base.table_name_suffix = '_s' Thing.reset_table_name Thing.reset_sequence_name WeNeedThings.up assert Thing.create("content" => "hello world") assert_equal "hello world", Thing.first.content RenameThings.up Thing.table_name = "p_awesome_things_s" assert_equal "hello world", Thing.first.content ensure Thing.reset_table_name Thing.reset_sequence_name end def test_add_drop_table_with_prefix_and_suffix assert !Reminder.table_exists? ActiveRecord::Base.table_name_prefix = 'prefix_' ActiveRecord::Base.table_name_suffix = '_suffix' Reminder.reset_table_name Reminder.reset_sequence_name WeNeedReminders.up assert Reminder.create("content" => "hello world", "remind_at" => Time.now) assert_equal "hello world", Reminder.first.content WeNeedReminders.down assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } ensure Reminder.reset_sequence_name end def test_create_table_with_binary_column Person.connection.drop_table :binary_testings rescue nil assert_nothing_raised { Person.connection.create_table :binary_testings do |t| t.column "data", :binary, :null => false end } columns = Person.connection.columns(:binary_testings) data_column = columns.detect { |c| c.name == "data" } assert_nil data_column.default Person.connection.drop_table :binary_testings rescue nil end unless mysql_enforcing_gtid_consistency? def test_create_table_with_query Person.connection.drop_table :table_from_query_testings rescue nil Person.connection.create_table(:person, force: true) Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person" columns = Person.connection.columns(:table_from_query_testings) assert_equal 1, columns.length assert_equal "id", columns.first.name Person.connection.drop_table :table_from_query_testings rescue nil end def test_create_table_with_query_from_relation Person.connection.drop_table :table_from_query_testings rescue nil Person.connection.create_table(:person, force: true) Person.connection.create_table :table_from_query_testings, as: Person.select(:id) columns = Person.connection.columns(:table_from_query_testings) assert_equal 1, columns.length assert_equal "id", columns.first.name Person.connection.drop_table :table_from_query_testings rescue nil end end if current_adapter? :OracleAdapter def test_create_table_with_custom_sequence_name # table name is 29 chars, the standard sequence name will # be 33 chars and should be shortened assert_nothing_raised do begin Person.connection.create_table :table_with_name_thats_just_ok do |t| t.column :foo, :string, :null => false end ensure Person.connection.drop_table :table_with_name_thats_just_ok rescue nil end end # should be all good w/ a custom sequence name assert_nothing_raised do begin Person.connection.create_table :table_with_name_thats_just_ok, :sequence_name => 'suitably_short_seq' do |t| t.column :foo, :string, :null => false end Person.connection.execute("select suitably_short_seq.nextval from dual") ensure Person.connection.drop_table :table_with_name_thats_just_ok, :sequence_name => 'suitably_short_seq' rescue nil end end # confirm the custom sequence got dropped assert_raise(ActiveRecord::StatementInvalid) do Person.connection.execute("select suitably_short_seq.nextval from dual") end end end if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) def test_out_of_range_limit_should_raise Person.connection.drop_table :test_limits rescue nil assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do Person.connection.create_table :test_integer_limits, :force => true do |t| t.column :bigone, :integer, :limit => 10 end end unless current_adapter?(:PostgreSQLAdapter) assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do Person.connection.create_table :test_text_limits, :force => true do |t| t.column :bigtext, :text, :limit => 0xfffffffff end end end Person.connection.drop_table :test_limits rescue nil end end protected # This is needed to isolate class_attribute assignments like `table_name_prefix` # for each test case. def new_isolated_reminder_class Class.new(Reminder) { def self.name; "Reminder"; end def self.base_class; self; end } end end class ReservedWordsMigrationTest < ActiveRecord::TestCase def test_drop_index_from_table_named_values connection = Person.connection connection.create_table :values, :force => true do |t| t.integer :value end assert_nothing_raised do connection.add_index :values, :value connection.remove_index :values, :column => :value end connection.drop_table :values rescue nil end end class ExplicitlyNamedIndexMigrationTest < ActiveRecord::TestCase def test_drop_index_by_name connection = Person.connection connection.create_table :values, force: true do |t| t.integer :value end assert_nothing_raised ArgumentError do connection.add_index :values, :value, name: 'a_different_name' connection.remove_index :values, column: :value, name: 'a_different_name' end connection.drop_table :values rescue nil end end if ActiveRecord::Base.connection.supports_bulk_alter? class BulkAlterTableMigrationsTest < ActiveRecord::TestCase def setup @connection = Person.connection @connection.create_table(:delete_me, :force => true) {|t| } Person.reset_column_information Person.reset_sequence_name end teardown do Person.connection.drop_table(:delete_me) rescue nil end def test_adding_multiple_columns assert_queries(1) do with_bulk_change_table do |t| t.column :name, :string t.string :qualification, :experience t.integer :age, :default => 0 t.date :birthdate t.timestamps null: true end end assert_equal 8, columns.size [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type } assert_equal '0', column(:age).default end def test_removing_columns with_bulk_change_table do |t| t.string :qualification, :experience end [:qualification, :experience].each {|c| assert column(c) } assert_queries(1) do with_bulk_change_table do |t| t.remove :qualification, :experience t.string :qualification_experience end end [:qualification, :experience].each {|c| assert ! column(c) } assert column(:qualification_experience) end def test_adding_indexes with_bulk_change_table do |t| t.string :username t.string :name t.integer :age end # Adding an index fires a query every time to check if an index already exists or not assert_queries(3) do with_bulk_change_table do |t| t.index :username, :unique => true, :name => :awesome_username_index t.index [:name, :age] end end assert_equal 2, indexes.size name_age_index = index(:index_delete_me_on_name_and_age) assert_equal ['name', 'age'].sort, name_age_index.columns.sort assert ! name_age_index.unique assert index(:awesome_username_index).unique end def test_removing_index with_bulk_change_table do |t| t.string :name t.index :name end assert index(:index_delete_me_on_name) assert_queries(3) do with_bulk_change_table do |t| t.remove_index :name t.index :name, :name => :new_name_index, :unique => true end end assert ! index(:index_delete_me_on_name) new_name_index = index(:new_name_index) assert new_name_index.unique end def test_changing_columns with_bulk_change_table do |t| t.string :name t.date :birthdate end assert ! column(:name).default assert_equal :date, column(:birthdate).type # One query for columns (delete_me table) # One query for primary key (delete_me table) # One query to do the bulk change assert_queries(3, :ignore_none => true) do with_bulk_change_table do |t| t.change :name, :string, :default => 'NONAME' t.change :birthdate, :datetime end end assert_equal 'NONAME', column(:name).default assert_equal :datetime, column(:birthdate).type end protected def with_bulk_change_table # Reset columns/indexes cache as we're changing the table @columns = @indexes = nil Person.connection.change_table(:delete_me, :bulk => true) do |t| yield t end end def column(name) columns.detect {|c| c.name == name.to_s } end def columns @columns ||= Person.connection.columns('delete_me') end def index(name) indexes.detect {|i| i.name == name.to_s } end def indexes @indexes ||= Person.connection.indexes('delete_me') end end # AlterTableMigrationsTest end class CopyMigrationsTest < ActiveRecord::TestCase def setup end def clear ActiveRecord::Base.timestamped_migrations = true to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations File.delete(*to_delete) end def test_copying_migrations_without_timestamps ActiveRecord::Base.timestamped_migrations = false @migrations_path = MIGRATIONS_ROOT + "/valid" @existing_migrations = Dir[@migrations_path + "/*.rb"] copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) assert File.exist?(@migrations_path + "/4_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/5_people_have_descriptions.bukkits.rb") assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied.map(&:filename) expected = "# This migration comes from bukkits (originally 1)" assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[0].chomp files_count = Dir[@migrations_path + "/*.rb"].length copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? ensure clear end def test_copying_migrations_without_timestamps_from_2_sources ActiveRecord::Base.timestamped_migrations = false @migrations_path = MIGRATIONS_ROOT + "/valid" @existing_migrations = Dir[@migrations_path + "/*.rb"] sources = {} sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy" sources[:omg] = MIGRATIONS_ROOT + "/to_copy2" ActiveRecord::Migration.copy(@migrations_path, sources) assert File.exist?(@migrations_path + "/4_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/5_people_have_descriptions.bukkits.rb") assert File.exist?(@migrations_path + "/6_create_articles.omg.rb") assert File.exist?(@migrations_path + "/7_create_comments.omg.rb") files_count = Dir[@migrations_path + "/*.rb"].length ActiveRecord::Migration.copy(@migrations_path, sources) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length ensure clear end def test_copying_migrations_with_timestamps @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" @existing_migrations = Dir[@migrations_path + "/*.rb"] travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb", @migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb"] assert_equal expected, copied.map(&:filename) files_count = Dir[@migrations_path + "/*.rb"].length copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? end ensure clear end def test_copying_migrations_with_timestamps_from_2_sources @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" @existing_migrations = Dir[@migrations_path + "/*.rb"] sources = {} sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2" travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do copied = ActiveRecord::Migration.copy(@migrations_path, sources) assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101012_create_articles.omg.rb") assert File.exist?(@migrations_path + "/20100726101013_create_comments.omg.rb") assert_equal 4, copied.length files_count = Dir[@migrations_path + "/*.rb"].length ActiveRecord::Migration.copy(@migrations_path, sources) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length end ensure clear end def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" @existing_migrations = Dir[@migrations_path + "/*.rb"] travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert File.exist?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb") files_count = Dir[@migrations_path + "/*.rb"].length copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? end ensure clear end def test_copying_migrations_preserving_magic_comments ActiveRecord::Base.timestamped_migrations = false @migrations_path = MIGRATIONS_ROOT + "/valid" @existing_migrations = Dir[@migrations_path + "/*.rb"] copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/magic"}) assert File.exist?(@migrations_path + "/4_currencies_have_symbols.bukkits.rb") assert_equal [@migrations_path + "/4_currencies_have_symbols.bukkits.rb"], copied.map(&:filename) expected = "# coding: ISO-8859-15\n# This migration comes from bukkits (originally 1)" assert_equal expected, IO.readlines(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")[0..1].join.chomp files_count = Dir[@migrations_path + "/*.rb"].length copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/magic"}) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? ensure clear end def test_skipping_migrations @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" @existing_migrations = Dir[@migrations_path + "/*.rb"] sources = {} sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision" skipped = [] on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) assert_equal 2, copied.length assert_equal 1, skipped.length assert_equal ["omg PeopleHaveHobbies"], skipped ensure clear end def test_skip_is_not_called_if_migrations_are_from_the_same_plugin @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" @existing_migrations = Dir[@migrations_path + "/*.rb"] sources = {} sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" skipped = [] on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) assert_equal 2, copied.length assert_equal 0, skipped.length ensure clear end def test_copying_migrations_to_non_existing_directory @migrations_path = MIGRATIONS_ROOT + "/non_existing" @existing_migrations = [] travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") assert_equal 2, copied.length end ensure clear Dir.delete(@migrations_path) end def test_copying_migrations_to_empty_directory @migrations_path = MIGRATIONS_ROOT + "/empty" @existing_migrations = [] travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") assert_equal 2, copied.length end ensure clear end def test_check_pending_with_stdlib_logger old, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ::Logger.new($stdout) quietly do assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new {}).call({}) } end ensure ActiveRecord::Base.logger = old end private def quietly silence_stream(STDOUT) do silence_stream(STDERR) do yield end end end end rails-4.2.6/activerecord/test/cases/migrator_test.rb000066400000000000000000000266621266740050600226160ustar00rootroot00000000000000require "cases/helper" require "cases/migration/helper" class MigratorTest < ActiveRecord::TestCase self.use_transactional_fixtures = false # Use this class to sense if migrations have gone # up or down. class Sensor < ActiveRecord::Migration attr_reader :went_up, :went_down def initialize name = self.class.name, version = nil super @went_up = false @went_down = false end def up; @went_up = true; end def down; @went_down = true; end end def setup super ActiveRecord::SchemaMigration.create_table ActiveRecord::SchemaMigration.delete_all rescue nil @verbose_was = ActiveRecord::Migration.verbose ActiveRecord::Migration.message_count = 0 ActiveRecord::Migration.class_eval do undef :puts def puts(*) ActiveRecord::Migration.message_count += 1 end end end teardown do ActiveRecord::SchemaMigration.delete_all rescue nil ActiveRecord::Migration.verbose = @verbose_was ActiveRecord::Migration.class_eval do undef :puts def puts(*) super end end end def test_migrator_with_duplicate_names assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do list = [ActiveRecord::Migration.new('Chunky'), ActiveRecord::Migration.new('Chunky')] ActiveRecord::Migrator.new(:up, list) end end def test_migrator_with_duplicate_versions assert_raises(ActiveRecord::DuplicateMigrationVersionError) do list = [ActiveRecord::Migration.new('Foo', 1), ActiveRecord::Migration.new('Bar', 1)] ActiveRecord::Migrator.new(:up, list) end end def test_migrator_with_missing_version_numbers assert_raises(ActiveRecord::UnknownMigrationVersionError) do list = [ActiveRecord::Migration.new('Foo', 1), ActiveRecord::Migration.new('Bar', 2)] ActiveRecord::Migrator.new(:up, list, 3).run end end def test_finds_migrations migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid") [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| assert_equal migrations[i].version, pair.first assert_equal migrations[i].name, pair.last end end def test_finds_migrations_in_subdirectories migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories") [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| assert_equal migrations[i].version, pair.first assert_equal migrations[i].name, pair.last end end def test_finds_migrations_from_two_directories directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps'] migrations = ActiveRecord::Migrator.migrations directories [[20090101010101, "PeopleHaveHobbies"], [20090101010202, "PeopleHaveDescriptions"], [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"], [20100201010101, "ValidWithTimestampsWeNeedReminders"], [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i| assert_equal pair.first, migrations[i].version assert_equal pair.last, migrations[i].name end end def test_finds_migrations_in_numbered_directory migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + '/10_urban'] assert_equal 9, migrations[0].version assert_equal 'AddExpressions', migrations[0].name end def test_relative_migrations list = Dir.chdir(MIGRATIONS_ROOT) do ActiveRecord::Migrator.migrations("valid") end migration_proxy = list.find { |item| item.name == 'ValidPeopleHaveLastNames' } assert migration_proxy, 'should find pending migration' end def test_finds_pending_migrations ActiveRecord::SchemaMigration.create!(:version => '1') migration_list = [ActiveRecord::Migration.new('foo', 1), ActiveRecord::Migration.new('bar', 3)] migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations assert_equal 1, migrations.size assert_equal migration_list.last, migrations.first end def test_migrator_interleaved_migrations pass_one = [Sensor.new('One', 1)] ActiveRecord::Migrator.new(:up, pass_one).migrate assert pass_one.first.went_up assert_not pass_one.first.went_down pass_two = [Sensor.new('One', 1), Sensor.new('Three', 3)] ActiveRecord::Migrator.new(:up, pass_two).migrate assert_not pass_two[0].went_up assert pass_two[1].went_up assert pass_two.all? { |x| !x.went_down } pass_three = [Sensor.new('One', 1), Sensor.new('Two', 2), Sensor.new('Three', 3)] ActiveRecord::Migrator.new(:down, pass_three).migrate assert pass_three[0].went_down assert_not pass_three[1].went_down assert pass_three[2].went_down end def test_up_calls_up migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)] ActiveRecord::Migrator.new(:up, migrations).migrate assert migrations.all? { |m| m.went_up } assert migrations.all? { |m| !m.went_down } assert_equal 2, ActiveRecord::Migrator.current_version end def test_down_calls_down test_up_calls_up migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)] ActiveRecord::Migrator.new(:down, migrations).migrate assert migrations.all? { |m| !m.went_up } assert migrations.all? { |m| m.went_down } assert_equal 0, ActiveRecord::Migrator.current_version end def test_current_version ActiveRecord::SchemaMigration.create!(:version => '1000') assert_equal 1000, ActiveRecord::Migrator.current_version end def test_migrator_one_up calls, migrations = sensors(3) ActiveRecord::Migrator.new(:up, migrations, 1).migrate assert_equal [[:up, 1]], calls calls.clear ActiveRecord::Migrator.new(:up, migrations, 2).migrate assert_equal [[:up, 2]], calls end def test_migrator_one_down calls, migrations = sensors(3) ActiveRecord::Migrator.new(:up, migrations).migrate assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls calls.clear ActiveRecord::Migrator.new(:down, migrations, 1).migrate assert_equal [[:down, 3], [:down, 2]], calls end def test_migrator_one_up_one_down calls, migrations = sensors(3) ActiveRecord::Migrator.new(:up, migrations, 1).migrate assert_equal [[:up, 1]], calls calls.clear ActiveRecord::Migrator.new(:down, migrations, 0).migrate assert_equal [[:down, 1]], calls end def test_migrator_double_up calls, migrations = sensors(3) assert_equal(0, ActiveRecord::Migrator.current_version) ActiveRecord::Migrator.new(:up, migrations, 1).migrate assert_equal [[:up, 1]], calls calls.clear ActiveRecord::Migrator.new(:up, migrations, 1).migrate assert_equal [], calls end def test_migrator_double_down calls, migrations = sensors(3) assert_equal(0, ActiveRecord::Migrator.current_version) ActiveRecord::Migrator.new(:up, migrations, 1).run assert_equal [[:up, 1]], calls calls.clear ActiveRecord::Migrator.new(:down, migrations, 1).run assert_equal [[:down, 1]], calls calls.clear ActiveRecord::Migrator.new(:down, migrations, 1).run assert_equal [], calls assert_equal(0, ActiveRecord::Migrator.current_version) end def test_migrator_verbosity _, migrations = sensors(3) ActiveRecord::Migrator.new(:up, migrations, 1).migrate assert_not_equal 0, ActiveRecord::Migration.message_count ActiveRecord::Migration.message_count = 0 ActiveRecord::Migrator.new(:down, migrations, 0).migrate assert_not_equal 0, ActiveRecord::Migration.message_count end def test_migrator_verbosity_off _, migrations = sensors(3) ActiveRecord::Migration.message_count = 0 ActiveRecord::Migration.verbose = false ActiveRecord::Migrator.new(:up, migrations, 1).migrate assert_equal 0, ActiveRecord::Migration.message_count ActiveRecord::Migrator.new(:down, migrations, 0).migrate assert_equal 0, ActiveRecord::Migration.message_count end def test_target_version_zero_should_run_only_once calls, migrations = sensors(3) # migrate up to 1 ActiveRecord::Migrator.new(:up, migrations, 1).migrate assert_equal [[:up, 1]], calls calls.clear # migrate down to 0 ActiveRecord::Migrator.new(:down, migrations, 0).migrate assert_equal [[:down, 1]], calls calls.clear # migrate down to 0 again ActiveRecord::Migrator.new(:down, migrations, 0).migrate assert_equal [], calls end def test_migrator_going_down_due_to_version_target calls, migrator = migrator_class(3) migrator.up("valid", 1) assert_equal [[:up, 1]], calls calls.clear migrator.migrate("valid", 0) assert_equal [[:down, 1]], calls calls.clear migrator.migrate("valid") assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls end def test_migrator_rollback _, migrator = migrator_class(3) migrator.migrate("valid") assert_equal(3, ActiveRecord::Migrator.current_version) migrator.rollback("valid") assert_equal(2, ActiveRecord::Migrator.current_version) migrator.rollback("valid") assert_equal(1, ActiveRecord::Migrator.current_version) migrator.rollback("valid") assert_equal(0, ActiveRecord::Migrator.current_version) migrator.rollback("valid") assert_equal(0, ActiveRecord::Migrator.current_version) end def test_migrator_db_has_no_schema_migrations_table _, migrator = migrator_class(3) ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") assert_not ActiveRecord::Base.connection.table_exists?('schema_migrations') migrator.migrate("valid", 1) assert ActiveRecord::Base.connection.table_exists?('schema_migrations') end def test_migrator_forward _, migrator = migrator_class(3) migrator.migrate("/valid", 1) assert_equal(1, ActiveRecord::Migrator.current_version) migrator.forward("/valid", 2) assert_equal(3, ActiveRecord::Migrator.current_version) migrator.forward("/valid") assert_equal(3, ActiveRecord::Migrator.current_version) end def test_only_loads_pending_migrations # migrate up to 1 ActiveRecord::SchemaMigration.create!(:version => '1') calls, migrator = migrator_class(3) migrator.migrate("valid", nil) assert_equal [[:up, 2], [:up, 3]], calls end def test_get_all_versions _, migrator = migrator_class(3) migrator.migrate("valid") assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions) migrator.rollback("valid") assert_equal([1,2], ActiveRecord::Migrator.get_all_versions) migrator.rollback("valid") assert_equal([1], ActiveRecord::Migrator.get_all_versions) migrator.rollback("valid") assert_equal([], ActiveRecord::Migrator.get_all_versions) end private def m(name, version) x = Sensor.new name, version x.extend(Module.new { define_method(:up) { yield(:up, x); super() } define_method(:down) { yield(:down, x); super() } }) if block_given? end def sensors(count) calls = [] migrations = count.times.map { |i| m(nil, i + 1) { |c,migration| calls << [c, migration.version] } } [calls, migrations] end def migrator_class(count) calls, migrations = sensors(count) migrator = Class.new(ActiveRecord::Migrator).extend(Module.new { define_method(:migrations) { |paths| migrations } }) [calls, migrator] end end rails-4.2.6/activerecord/test/cases/mixin_test.rb000066400000000000000000000026301266740050600221030ustar00rootroot00000000000000require "cases/helper" class Mixin < ActiveRecord::Base end class TouchTest < ActiveRecord::TestCase fixtures :mixins setup do travel_to Time.now end teardown do travel_back end def test_update stamped = Mixin.new assert_nil stamped.updated_at assert_nil stamped.created_at stamped.save assert_equal Time.now, stamped.updated_at assert_equal Time.now, stamped.created_at end def test_create obj = Mixin.create assert_equal Time.now, obj.updated_at assert_equal Time.now, obj.created_at end def test_many_updates stamped = Mixin.new assert_nil stamped.updated_at assert_nil stamped.created_at stamped.save assert_equal Time.now, stamped.created_at assert_equal Time.now, stamped.updated_at old_updated_at = stamped.updated_at travel 5.minutes do stamped.lft_will_change! stamped.save assert_equal Time.now, stamped.updated_at assert_equal old_updated_at, stamped.created_at end end def test_create_turned_off Mixin.record_timestamps = false mixin = Mixin.new assert_nil mixin.updated_at mixin.save assert_nil mixin.updated_at # Make sure Mixin.record_timestamps gets reset, even if this test fails, # so that other tests do not fail because Mixin.record_timestamps == false rescue Exception => e raise e ensure Mixin.record_timestamps = true end end rails-4.2.6/activerecord/test/cases/modules_test.rb000066400000000000000000000211731266740050600224320ustar00rootroot00000000000000require "cases/helper" require 'models/company_in_module' require 'models/shop' require 'models/developer' require 'models/computer' class ModulesTest < ActiveRecord::TestCase fixtures :accounts, :companies, :projects, :developers, :collections, :products, :variants def setup # need to make sure Object::Firm and Object::Client are not defined, # so that constantize will not be able to cheat when having to load namespaced classes @undefined_consts = {} [:Firm, :Client].each do |const| @undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const) end ActiveRecord::Base.store_full_sti_class = false end teardown do # reinstate the constants that we undefined in the setup @undefined_consts.each do |constant, value| Object.send :const_set, constant, value unless value.nil? end ActiveRecord::Base.store_full_sti_class = true end def test_module_spanning_associations firm = MyApplication::Business::Firm.first assert !firm.clients.empty?, "Firm should have clients" assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name" end def test_module_spanning_has_and_belongs_to_many_associations project = MyApplication::Business::Project.first project.developers << MyApplication::Business::Developer.create("name" => "John") assert_equal "John", project.developers.last.name end def test_associations_spanning_cross_modules account = MyApplication::Billing::Account.all.merge!(:order => 'id').first assert_kind_of MyApplication::Business::Firm, account.firm assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_qualified_billing_firm assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_unqualified_billing_firm end def test_find_account_and_include_company account = MyApplication::Billing::Account.all.merge!(:includes => :firm).find(1) assert_kind_of MyApplication::Business::Firm, account.firm end def test_table_name assert_equal 'accounts', MyApplication::Billing::Account.table_name, 'table_name for ActiveRecord model in module' assert_equal 'companies', MyApplication::Business::Client.table_name, 'table_name for ActiveRecord model subclass' assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model' end def test_assign_ids firm = MyApplication::Business::Firm.first assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do firm.client_ids = [MyApplication::Business::Client.first.id] end end # need to add an eager loading condition to force the eager loading model into # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 def test_eager_loading_in_modules clients = [] assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do clients << MyApplication::Business::Client.references(:accounts).merge!(:includes => {:firm => :account}, :where => 'accounts.id IS NOT NULL').find(3) clients << MyApplication::Business::Client.includes(:firm => :account).find(3) end clients.each do |client| assert_no_queries do assert_not_nil(client.firm.account) end end end def test_module_table_name_prefix assert_equal 'prefixed_companies', MyApplication::Business::Prefixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_prefix' assert_equal 'prefixed_companies', MyApplication::Business::Prefixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_prefix' assert_equal 'companies', MyApplication::Business::Prefixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_prefix should not be prefixed' end def test_module_table_name_prefix_with_global_prefix classes = [ MyApplication::Business::Company, MyApplication::Business::Firm, MyApplication::Business::Client, MyApplication::Business::Client::Contact, MyApplication::Business::Developer, MyApplication::Business::Project, MyApplication::Business::Prefixed::Company, MyApplication::Business::Prefixed::Nested::Company, MyApplication::Billing::Account ] ActiveRecord::Base.table_name_prefix = 'global_' classes.each(&:reset_table_name) assert_equal 'global_companies', MyApplication::Business::Company.table_name, 'inferred table_name for ActiveRecord model in module without table_name_prefix' assert_equal 'prefixed_companies', MyApplication::Business::Prefixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_prefix' assert_equal 'prefixed_companies', MyApplication::Business::Prefixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_prefix' assert_equal 'companies', MyApplication::Business::Prefixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_prefix should not be prefixed' ensure ActiveRecord::Base.table_name_prefix = '' classes.each(&:reset_table_name) end def test_module_table_name_suffix assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_suffix' assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_suffix' assert_equal 'companies', MyApplication::Business::Suffixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_suffix should not be suffixed' end def test_module_table_name_suffix_with_global_suffix classes = [ MyApplication::Business::Company, MyApplication::Business::Firm, MyApplication::Business::Client, MyApplication::Business::Client::Contact, MyApplication::Business::Developer, MyApplication::Business::Project, MyApplication::Business::Suffixed::Company, MyApplication::Business::Suffixed::Nested::Company, MyApplication::Billing::Account ] ActiveRecord::Base.table_name_suffix = '_global' classes.each(&:reset_table_name) assert_equal 'companies_global', MyApplication::Business::Company.table_name, 'inferred table_name for ActiveRecord model in module without table_name_suffix' assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_suffix' assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_suffix' assert_equal 'companies', MyApplication::Business::Suffixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_suffix should not be suffixed' ensure ActiveRecord::Base.table_name_suffix = '' classes.each(&:reset_table_name) end def test_compute_type_can_infer_class_name_of_sibling_inside_module old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true assert_equal MyApplication::Business::Firm, MyApplication::Business::Client.send(:compute_type, "Firm") ensure ActiveRecord::Base.store_full_sti_class = old end def test_nested_models_should_not_raise_exception_when_using_delete_all_dependency_on_association old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true collection = Shop::Collection.first assert !collection.products.empty?, "Collection should have products" assert_nothing_raised { collection.destroy } ensure ActiveRecord::Base.store_full_sti_class = old end def test_nested_models_should_not_raise_exception_when_using_nullify_dependency_on_association old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true product = Shop::Product.first assert !product.variants.empty?, "Product should have variants" assert_nothing_raised { product.destroy } ensure ActiveRecord::Base.store_full_sti_class = old end end rails-4.2.6/activerecord/test/cases/multiparameter_attributes_test.rb000066400000000000000000000331021266740050600262560ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/customer' class MultiParameterAttributeTest < ActiveRecord::TestCase fixtures :topics def test_multiparameter_attributes_on_date attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" } topic = Topic.find(1) topic.attributes = attributes # note that extra #to_date call allows test to pass for Oracle, which # treats dates/times the same assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date end def test_multiparameter_attributes_on_date_with_empty_year attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" } topic = Topic.find(1) topic.attributes = attributes # note that extra #to_date call allows test to pass for Oracle, which # treats dates/times the same assert_nil topic.last_read end def test_multiparameter_attributes_on_date_with_empty_month attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" } topic = Topic.find(1) topic.attributes = attributes # note that extra #to_date call allows test to pass for Oracle, which # treats dates/times the same assert_nil topic.last_read end def test_multiparameter_attributes_on_date_with_empty_day attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes # note that extra #to_date call allows test to pass for Oracle, which # treats dates/times the same assert_nil topic.last_read end def test_multiparameter_attributes_on_date_with_empty_day_and_year attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes # note that extra #to_date call allows test to pass for Oracle, which # treats dates/times the same assert_nil topic.last_read end def test_multiparameter_attributes_on_date_with_empty_day_and_month attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes # note that extra #to_date call allows test to pass for Oracle, which # treats dates/times the same assert_nil topic.last_read end def test_multiparameter_attributes_on_date_with_empty_year_and_month attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" } topic = Topic.find(1) topic.attributes = attributes # note that extra #to_date call allows test to pass for Oracle, which # treats dates/times the same assert_nil topic.last_read end def test_multiparameter_attributes_on_date_with_all_empty attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes assert_nil topic.last_read end def test_multiparameter_attributes_on_time with_timezone_config default: :local do attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on end end def test_multiparameter_attributes_on_time_with_no_date ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do attributes = { "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" } topic = Topic.find(1) topic.attributes = attributes end assert_equal("written_on", ex.errors[0].attribute) end def test_multiparameter_attributes_on_time_with_invalid_time_params ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64", } topic = Topic.find(1) topic.attributes = attributes end assert_equal("written_on", ex.errors[0].attribute) end def test_multiparameter_attributes_on_time_with_old_date attributes = { "written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" } topic = Topic.find(1) topic.attributes = attributes # testing against to_s(:db) representation because either a Time or a DateTime might be returned, depending on platform assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db) end def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do attributes = { "written_on(4i)" => "16", "written_on(5i)" => "24" } topic = Topic.find(1) topic.attributes = attributes end assert_equal("written_on", ex.errors[0].attribute) end def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do attributes = { "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02" } topic = Topic.find(1) topic.attributes = attributes end assert_equal("written_on", ex.errors[0].attribute) end def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing with_timezone_config default: :local do attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12", "written_on(5i)" => "12", "written_on(6i)" => "02" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on end end def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank attributes = { "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "", "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02" } topic = Topic.find(1) topic.attributes = attributes assert_nil topic.written_on end def test_multiparameter_attributes_on_time_will_ignore_date_if_empty attributes = { "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "", "written_on(4i)" => "16", "written_on(5i)" => "24" } topic = Topic.find(1) topic.attributes = attributes assert_nil topic.written_on end def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty attributes = { "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "", "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02" } topic = Topic.find(1) topic.attributes = attributes assert_nil topic.written_on end def test_multiparameter_attributes_on_time_with_utc with_timezone_config default: :utc do attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on end end def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time assert_equal Time.zone, topic.written_on.time_zone end end def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false with_timezone_config default: :local, aware_attributes: false, zone: -28800 do attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on assert_equal false, topic.written_on.respond_to?(:time_zone) end end def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do Topic.skip_time_zone_conversion_for_attributes = [:written_on] attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on assert_equal false, topic.written_on.respond_to?(:time_zone) end ensure Topic.skip_time_zone_conversion_for_attributes = [] end # Oracle does not have a TIME datatype. unless current_adapter?(:OracleAdapter) def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do attributes = { "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1", "bonus_time(4i)" => "16", "bonus_time(5i)" => "24" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time assert topic.bonus_time.utc? end end end def test_multiparameter_attributes_on_time_with_empty_seconds with_timezone_config default: :local do attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "" } topic = Topic.find(1) topic.attributes = attributes assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on end end unless current_adapter? :OracleAdapter def test_multiparameter_attributes_setting_time_attribute topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" ) assert_equal 1, topic.bonus_time.hour assert_equal 5, topic.bonus_time.min end end def test_multiparameter_attributes_setting_date_attribute topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11" ) assert_equal 1952, topic.written_on.year assert_equal 3, topic.written_on.month assert_equal 11, topic.written_on.day end def test_multiparameter_attributes_setting_date_and_time_attribute topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11", "written_on(4i)" => "13", "written_on(5i)" => "55") assert_equal 1952, topic.written_on.year assert_equal 3, topic.written_on.month assert_equal 11, topic.written_on.day assert_equal 13, topic.written_on.hour assert_equal 55, topic.written_on.min end def test_multiparameter_attributes_setting_time_but_not_date_on_date_field assert_raise( ActiveRecord::MultiparameterAssignmentErrors ) do Topic.new( "written_on(4i)" => "13", "written_on(5i)" => "55" ) end end def test_multiparameter_assignment_of_aggregation customer = Customer.new address = Address.new("The Street", "The City", "The Country") attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country } customer.attributes = attributes assert_equal address, customer.address end def test_multiparameter_assignment_of_aggregation_out_of_order customer = Customer.new address = Address.new("The Street", "The City", "The Country") attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street } customer.attributes = attributes assert_equal address, customer.address end def test_multiparameter_assignment_of_aggregation_with_missing_values ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do customer = Customer.new address = Address.new("The Street", "The City", "The Country") attributes = { "address(2)" => address.city, "address(3)" => address.country } customer.attributes = attributes end assert_equal("address", ex.errors[0].attribute) end def test_multiparameter_assignment_of_aggregation_with_blank_values customer = Customer.new address = Address.new("The Street", "The City", "The Country") attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country } customer.attributes = attributes assert_equal Address.new(nil, "The City", "The Country"), customer.address end def test_multiparameter_assignment_of_aggregation_with_large_index ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do customer = Customer.new address = Address.new("The Street", "The City", "The Country") attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country } customer.attributes = attributes end assert_equal("address", ex.errors[0].attribute) end end rails-4.2.6/activerecord/test/cases/multiple_db_test.rb000066400000000000000000000060531266740050600232620ustar00rootroot00000000000000require "cases/helper" require 'models/entrant' require 'models/bird' require 'models/course' class MultipleDbTest < ActiveRecord::TestCase self.use_transactional_fixtures = false def setup @courses = create_fixtures("courses") { Course.retrieve_connection } @colleges = create_fixtures("colleges") { College.retrieve_connection } @entrants = create_fixtures("entrants") end def test_connected assert_not_nil Entrant.connection assert_not_nil Course.connection end def test_proper_connection assert_not_equal(Entrant.connection, Course.connection) assert_equal(Entrant.connection, Entrant.retrieve_connection) assert_equal(Course.connection, Course.retrieve_connection) assert_equal(ActiveRecord::Base.connection, Entrant.connection) end def test_find c1 = Course.find(1) assert_equal "Ruby Development", c1.name c2 = Course.find(2) assert_equal "Java Development", c2.name e1 = Entrant.find(1) assert_equal "Ruby Developer", e1.name e2 = Entrant.find(2) assert_equal "Ruby Guru", e2.name e3 = Entrant.find(3) assert_equal "Java Lover", e3.name end def test_associations c1 = Course.find(1) assert_equal 2, c1.entrants.count e1 = Entrant.find(1) assert_equal e1.course.id, c1.id c2 = Course.find(2) assert_equal 1, c2.entrants.count e3 = Entrant.find(3) assert_equal e3.course.id, c2.id end def test_course_connection_should_survive_dependency_reload assert Course.connection ActiveSupport::Dependencies.clear Object.send(:remove_const, :Course) require_dependency 'models/course' assert Course.connection end def test_transactions_across_databases c1 = Course.find(1) e1 = Entrant.find(1) begin Course.transaction do Entrant.transaction do c1.name = "Typo" e1.name = "Typo" c1.save e1.save raise "No I messed up." end end rescue # Yup caught it end assert_equal "Typo", c1.name assert_equal "Typo", e1.name assert_equal "Ruby Development", Course.find(1).name assert_equal "Ruby Developer", Entrant.find(1).name end def test_arel_table_engines assert_not_equal Entrant.arel_engine, Bird.arel_engine assert_not_equal Entrant.arel_engine, Course.arel_engine end def test_connection assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection end unless in_memory_db? def test_count_on_custom_connection ActiveRecord::Base.remove_connection assert_equal 1, College.count ensure ActiveRecord::Base.establish_connection :arunit end def test_associations_should_work_when_model_has_no_connection begin ActiveRecord::Base.remove_connection assert_nothing_raised ActiveRecord::ConnectionNotEstablished do College.first.courses.first end ensure ActiveRecord::Base.establish_connection :arunit end end end end rails-4.2.6/activerecord/test/cases/nested_attributes_test.rb000066400000000000000000001217301266740050600245120ustar00rootroot00000000000000require "cases/helper" require "models/pirate" require "models/ship" require "models/ship_part" require "models/bird" require "models/parrot" require "models/treasure" require "models/man" require "models/interest" require "models/owner" require "models/pet" require 'active_support/hash_with_indifferent_access' class TestNestedAttributesInGeneral < ActiveRecord::TestCase teardown do Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end def test_base_should_have_an_empty_nested_attributes_options assert_equal Hash.new, ActiveRecord::Base.nested_attributes_options end def test_should_add_a_proc_to_nested_attributes_options assert_equal ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC, Pirate.nested_attributes_options[:birds_with_reject_all_blank][:reject_if] [:parrots, :birds].each do |name| assert_instance_of Proc, Pirate.nested_attributes_options[name][:reject_if] end end def test_should_not_build_a_new_record_using_reject_all_even_if_destroy_is_given pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => '', :_destroy => '0'}] pirate.save! assert pirate.birds_with_reject_all_blank.empty? end def test_should_not_build_a_new_record_if_reject_all_blank_returns_false pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}] pirate.save! assert pirate.birds_with_reject_all_blank.empty? end def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") pirate.birds_with_reject_all_blank_attributes = [{:name => 'Tweetie', :color => ''}] pirate.save! assert_equal 1, pirate.birds_with_reject_all_blank.count assert_equal 'Tweetie', pirate.birds_with_reject_all_blank.first.name end def test_should_raise_an_ArgumentError_for_non_existing_associations exception = assert_raise ArgumentError do Pirate.accepts_nested_attributes_for :honesty end assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message end def test_should_disable_allow_destroy_by_default Pirate.accepts_nested_attributes_for :ship pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") ship = pirate.create_ship(name: 'Nights Dirty Lightning') pirate.update(ship_attributes: { '_destroy' => true, :id => ship.id }) assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.ship.reload } end def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction ship = Ship.create!(:name => 'Nights Dirty Lightning') assert !ship._destroy ship.mark_for_destruction assert ship._destroy end def test_reject_if_method_without_arguments Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record? pirate = Pirate.new(:catchphrase => "Stop wastin' me time") pirate.ship_attributes = { :name => 'Black Pearl' } assert_no_difference('Ship.count') { pirate.save! } end def test_reject_if_method_with_arguments Pirate.accepts_nested_attributes_for :ship, :reject_if => :reject_empty_ships_on_create pirate = Pirate.new(:catchphrase => "Stop wastin' me time") pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true } assert_no_difference('Ship.count') { pirate.save! } # pirate.reject_empty_ships_on_create returns false for saved pirate records # in the previous step note that pirate gets saved but ship fails pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true } assert_difference('Ship.count') { pirate.save! } end def test_reject_if_with_indifferent_keys Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:name].blank? } pirate = Pirate.new(:catchphrase => "Stop wastin' me time") pirate.ship_attributes = { :name => 'Hello Pearl' } assert_difference('Ship.count') { pirate.save! } end def test_reject_if_with_a_proc_which_returns_true_always_for_has_one Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| true } pirate = Pirate.new(catchphrase: "Stop wastin' me time") ship = pirate.create_ship(name: 's1') pirate.update({ship_attributes: { name: 's2', id: ship.id } }) assert_equal 's1', ship.reload.name end def test_reuse_already_built_new_record pirate = Pirate.new ship_built_first = pirate.build_ship pirate.ship_attributes = { name: 'Ship 1' } assert_equal ship_built_first.object_id, pirate.ship.object_id end def test_do_not_allow_assigning_foreign_key_when_reusing_existing_new_record pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") pirate.build_ship pirate.ship_attributes = { name: 'Ship 1', pirate_id: pirate.id + 1 } assert_equal pirate.id, pirate.ship.pirate_id end def test_reject_if_with_a_proc_which_returns_true_always_for_has_many Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true } man = Man.create(name: "John") interest = man.interests.create(topic: 'photography') man.update({interests_attributes: { topic: 'gardening', id: interest.id } }) assert_equal 'photography', interest.reload.topic end def test_destroy_works_independent_of_reject_if Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }, :allow_destroy => true man = Man.create(name: "Jon") interest = man.interests.create(topic: 'the ladies') man.update({interests_attributes: { _destroy: "1", id: interest.id } }) assert man.reload.interests.empty? end def test_reject_if_is_not_short_circuited_if_allow_destroy_is_false Pirate.accepts_nested_attributes_for :ship, reject_if: ->(a) { a[:name] == "The Golden Hind" }, allow_destroy: false pirate = Pirate.create!(catchphrase: "Stop wastin' me time", ship_attributes: { name: "White Pearl", _destroy: "1" }) assert_equal "White Pearl", pirate.reload.ship.name pirate.update!(ship_attributes: { id: pirate.ship.id, name: "The Golden Hind", _destroy: "1" }) assert_equal "White Pearl", pirate.reload.ship.name pirate.update!(ship_attributes: { id: pirate.ship.id, name: "Black Pearl", _destroy: "1" }) assert_equal "Black Pearl", pirate.reload.ship.name end def test_has_many_association_updating_a_single_record Man.accepts_nested_attributes_for(:interests) man = Man.create(name: 'John') interest = man.interests.create(topic: 'photography') man.update({interests_attributes: {topic: 'gardening', id: interest.id}}) assert_equal 'gardening', interest.reload.topic end def test_reject_if_with_blank_nested_attributes_id # When using a select list to choose an existing 'ship' id, with include_blank: true Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:id].blank? } pirate = Pirate.new(:catchphrase => "Stop wastin' me time") pirate.ship_attributes = { :id => "" } assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.save! } end def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record Man.accepts_nested_attributes_for(:interests) man = Man.create(:name => "John") interest = man.interests.create :topic => 'gardening' man = Man.find man.id man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}] assert_equal man.interests.first.topic, man.interests[0].topic end def test_allows_class_to_override_setter_and_call_super mean_pirate_class = Class.new(Pirate) do accepts_nested_attributes_for :parrot def parrot_attributes=(attrs) super(attrs.merge(:color => "blue")) end end mean_pirate = mean_pirate_class.new mean_pirate.parrot_attributes = { :name => "James" } assert_equal "James", mean_pirate.parrot.name assert_equal "blue", mean_pirate.parrot.color end def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses Pirate.accepts_nested_attributes_for(:parrot) mean_pirate_class = Class.new(Pirate) do accepts_nested_attributes_for :parrot end mean_pirate = mean_pirate_class.new mean_pirate.parrot_attributes = { :name => "James" } assert_equal "James", mean_pirate.parrot.name end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def setup @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to exception = assert_raise ArgumentError do Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"}) end assert_equal "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?", exception.message end def test_should_define_an_attribute_writer_method_for_the_association assert_respond_to @pirate, :ship_attributes= end def test_should_build_a_new_record_if_there_is_no_id @ship.destroy @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' } assert !@pirate.ship.persisted? assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy @ship.destroy @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' } assert_nil @pirate.ship end def test_should_not_build_a_new_record_if_a_reject_if_proc_returns_false @ship.destroy @pirate.reload.ship_attributes = {} assert_nil @pirate.ship end def test_should_replace_an_existing_record_if_there_is_no_id @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' } assert !@pirate.ship.persisted? assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name assert_equal 'Nights Dirty Lightning', @ship.name end def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' } assert_equal @ship, @pirate.ship assert_equal 'Nights Dirty Lightning', @pirate.ship.name end def test_should_modify_an_existing_record_if_there_is_a_matching_id @pirate.reload.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' } assert_equal @ship, @pirate.ship assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record exception = assert_raise ActiveRecord::RecordNotFound do @pirate.ship_attributes = { :id => 1234567890 } end assert_equal "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message end def test_should_take_a_hash_with_string_keys_and_update_the_associated_model @pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' } assert_equal @ship, @pirate.ship assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id @ship.stubs(:id).returns('ABC1X') @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' } assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @pirate.ship.destroy [1, '1', true, 'true'].each do |truth| ship = @pirate.reload.create_ship(name: 'Mister Pablo') @pirate.update(ship_attributes: { id: ship.id, _destroy: truth }) assert_nil @pirate.reload.ship assert_raise(ActiveRecord::RecordNotFound) { Ship.find(ship.id) } end end def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy [nil, '0', 0, 'false', false].each do |not_truth| @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: not_truth }) assert_equal @ship, @pirate.reload.ship end end def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? } @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' }) assert_equal @ship, @pirate.reload.ship Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end def test_should_also_work_with_a_HashWithIndifferentAccess @pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(:id => @ship.id, :name => 'Davy Jones Gold Dagger') assert @pirate.ship.persisted? assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end def test_should_work_with_update_as_well @pirate.update({ catchphrase: 'Arr', ship_attributes: { id: @ship.id, name: 'Mister Pablo' } }) @pirate.reload assert_equal 'Arr', @pirate.catchphrase assert_equal 'Mister Pablo', @pirate.ship.name end def test_should_not_destroy_the_associated_model_until_the_parent_is_saved @pirate.attributes = { :ship_attributes => { :id => @ship.id, :_destroy => '1' } } assert !@pirate.ship.destroyed? assert @pirate.ship.marked_for_destruction? @pirate.save assert @pirate.ship.destroyed? assert_nil @pirate.reload.ship end def test_should_automatically_enable_autosave_on_the_association assert Pirate.reflect_on_association(:ship).options[:autosave] end def test_should_accept_update_only_option @pirate.update(update_only_ship_attributes: { id: @pirate.ship.id, name: 'Mayflower' }) end def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true @ship.delete @pirate.reload.update(update_only_ship_attributes: { name: 'Mayflower' }) assert_not_nil @pirate.ship end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @ship.delete @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') @pirate.update(update_only_ship_attributes: { name: 'Mayflower' }) assert_equal 'Mayflower', @ship.reload.name assert_equal @ship, @pirate.reload.ship end def test_should_update_existing_when_update_only_is_true_and_id_is_given @ship.delete @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id }) assert_equal 'Mayflower', @ship.reload.name assert_equal @ship, @pirate.reload.ship end def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true @ship.delete @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id, _destroy: true }) assert_nil @pirate.reload.ship assert_raise(ActiveRecord::RecordNotFound) { Ship.find(@ship.id) } Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => false end end class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def setup @ship = Ship.new(:name => 'Nights Dirty Lightning') @pirate = @ship.build_pirate(:catchphrase => 'Aye') @ship.save! end def test_should_define_an_attribute_writer_method_for_the_association assert_respond_to @ship, :pirate_attributes= end def test_should_build_a_new_record_if_there_is_no_id @pirate.destroy @ship.reload.pirate_attributes = { :catchphrase => 'Arr' } assert !@ship.pirate.persisted? assert_equal 'Arr', @ship.pirate.catchphrase end def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy @pirate.destroy @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' } assert_nil @ship.pirate end def test_should_not_build_a_new_record_if_a_reject_if_proc_returns_false @pirate.destroy @ship.reload.pirate_attributes = {} assert_nil @ship.pirate end def test_should_replace_an_existing_record_if_there_is_no_id @ship.reload.pirate_attributes = { :catchphrase => 'Arr' } assert !@ship.pirate.persisted? assert_equal 'Arr', @ship.pirate.catchphrase assert_equal 'Aye', @pirate.catchphrase end def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' } assert_equal @pirate, @ship.pirate assert_equal 'Aye', @ship.pirate.catchphrase end def test_should_modify_an_existing_record_if_there_is_a_matching_id @ship.reload.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' } assert_equal @pirate, @ship.pirate assert_equal 'Arr', @ship.pirate.catchphrase end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record exception = assert_raise ActiveRecord::RecordNotFound do @ship.pirate_attributes = { :id => 1234567890 } end assert_equal "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}", exception.message end def test_should_take_a_hash_with_string_keys_and_update_the_associated_model @ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' } assert_equal @pirate, @ship.pirate assert_equal 'Arr', @ship.pirate.catchphrase end def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id @pirate.stubs(:id).returns('ABC1X') @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' } assert_equal 'Arr', @ship.pirate.catchphrase end def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @ship.pirate.destroy [1, '1', true, 'true'].each do |truth| pirate = @ship.reload.create_pirate(catchphrase: 'Arr') @ship.update(pirate_attributes: { id: pirate.id, _destroy: truth }) assert_raise(ActiveRecord::RecordNotFound) { pirate.reload } end end def test_should_unset_association_when_an_existing_record_is_destroyed original_pirate_id = @ship.pirate.id @ship.update! pirate_attributes: { id: @ship.pirate.id, _destroy: true } assert_empty Pirate.where(id: original_pirate_id) assert_nil @ship.pirate_id assert_nil @ship.pirate @ship.reload assert_empty Pirate.where(id: original_pirate_id) assert_nil @ship.pirate_id assert_nil @ship.pirate end def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy [nil, '0', 0, 'false', false].each do |not_truth| @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: not_truth }) assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } end end def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? } @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' }) assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } ensure Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end def test_should_work_with_update_as_well @ship.update({ name: 'Mister Pablo', pirate_attributes: { catchphrase: 'Arr' } }) @ship.reload assert_equal 'Mister Pablo', @ship.name assert_equal 'Arr', @ship.pirate.catchphrase end def test_should_not_destroy_the_associated_model_until_the_parent_is_saved pirate = @ship.pirate @ship.attributes = { :pirate_attributes => { :id => pirate.id, '_destroy' => true } } assert_nothing_raised(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) } @ship.save assert_raise(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) } end def test_should_automatically_enable_autosave_on_the_association assert Ship.reflect_on_association(:pirate).options[:autosave] end def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true @pirate.delete @ship.reload.attributes = { :update_only_pirate_attributes => { :catchphrase => 'Arr' } } assert !@ship.update_only_pirate.persisted? end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @pirate.delete @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr' }) assert_equal 'Arr', @pirate.reload.catchphrase assert_equal @pirate, @ship.reload.update_only_pirate end def test_should_update_existing_when_update_only_is_true_and_id_is_given @pirate.delete @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id }) assert_equal 'Arr', @pirate.reload.catchphrase assert_equal @pirate, @ship.reload.update_only_pirate end def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true @pirate.delete @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id, _destroy: true }) assert_raise(ActiveRecord::RecordNotFound) { @pirate.reload } Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => false end end module NestedAttributesOnACollectionAssociationTests def test_should_define_an_attribute_writer_method_for_the_association assert_respond_to @pirate, association_setter end def test_should_save_only_one_association_on_create pirate = Pirate.create!({ :catchphrase => 'Arr', association_getter => { 'foo' => { :name => 'Grace OMalley' } } }) assert_equal 1, pirate.reload.send(@association_name).count end def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models @alternate_params[association_getter].stringify_keys! @pirate.update @alternate_params assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name] end def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models @pirate.send(association_setter, @alternate_params[association_getter].values) @pirate.save assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name] end def test_should_also_work_with_a_HashWithIndifferentAccess @pirate.send(association_setter, ActiveSupport::HashWithIndifferentAccess.new('foo' => ActiveSupport::HashWithIndifferentAccess.new(:id => @child_1.id, :name => 'Grace OMalley'))) @pirate.save assert_equal 'Grace OMalley', @child_1.reload.name end def test_should_take_a_hash_and_assign_the_attributes_to_the_associated_models @pirate.attributes = @alternate_params assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name end def test_should_not_load_association_when_updating_existing_records @pirate.reload @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }]) assert ! @pirate.send(@association_name).loaded? @pirate.save assert ! @pirate.send(@association_name).loaded? assert_equal 'Grace OMalley', @child_1.reload.name end def test_should_not_overwrite_unsaved_updates_when_loading_association @pirate.reload @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }]) assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name end def test_should_preserve_order_when_not_overwriting_unsaved_updates @pirate.reload @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }]) assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id end def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates @pirate.reload record = @pirate.class.reflect_on_association(@association_name).klass.new(name: 'Grace OMalley') @pirate.send(@association_name) << record record.save! @pirate.send(@association_name).last.update!(name: 'Polly') assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name end def test_should_not_remove_scheduled_destroys_when_loading_association @pirate.reload @pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }]) assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction? end def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models @child_1.stubs(:id).returns('ABC1X') @child_2.stubs(:id).returns('ABC2X') @pirate.attributes = { association_getter => [ { :id => @child_1.id, :name => 'Grace OMalley' }, { :id => @child_2.id, :name => 'Privateers Greed' } ] } assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record exception = assert_raise ActiveRecord::RecordNotFound do @pirate.attributes = { association_getter => [{ :id => 1234567890 }] } end assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message end def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing @pirate.send(@association_name).destroy_all @pirate.reload.attributes = { association_getter => { 'foo' => { :name => 'Grace OMalley' }, 'bar' => { :name => 'Privateers Greed' }} } assert !@pirate.send(@association_name).first.persisted? assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name assert !@pirate.send(@association_name).last.persisted? assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name end def test_should_not_assign_destroy_key_to_a_record assert_nothing_raised ActiveRecord::UnknownAttributeError do @pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }}) end end def test_should_ignore_new_associated_records_with_truthy_destroy_attribute @pirate.send(@association_name).destroy_all @pirate.reload.attributes = { association_getter => { 'foo' => { :name => 'Grace OMalley' }, 'bar' => { :name => 'Privateers Greed', '_destroy' => '1' } } } assert_equal 1, @pirate.send(@association_name).length assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name end def test_should_ignore_new_associated_records_if_a_reject_if_proc_returns_false @alternate_params[association_getter]['baz'] = {} assert_no_difference("@pirate.send(@association_name).count") do @pirate.attributes = @alternate_params end end def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models attributes = {} attributes['123726353'] = { :name => 'Grace OMalley' } attributes['2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353 @pirate.send(association_setter, attributes) assert_equal ['Posideons Killer', 'Killer bandita Dionne', 'Privateers Greed', 'Grace OMalley'].to_set, @pirate.send(@association_name).map(&:name).to_set end def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) } assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) } exception = assert_raise ArgumentError do @pirate.send(association_setter, "foo") end assert_equal 'Hash or Array expected, got String ("foo")', exception.message end def test_should_work_with_update_as_well @pirate.update(catchphrase: 'Arr', association_getter => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }}) assert_equal 'Grace OMalley', @child_1.reload.name end def test_should_update_existing_records_and_add_new_ones_that_have_no_id @alternate_params[association_getter]['baz'] = { name: 'Buccaneers Servant' } assert_difference('@pirate.send(@association_name).count', +1) do @pirate.update @alternate_params end assert_equal ['Grace OMalley', 'Privateers Greed', 'Buccaneers Servant'].to_set, @pirate.reload.send(@association_name).map(&:name).to_set end def test_should_be_possible_to_destroy_a_record ['1', 1, 'true', true].each do |true_variable| record = @pirate.reload.send(@association_name).create!(:name => 'Grace OMalley') @pirate.send(association_setter, @alternate_params[association_getter].merge('baz' => { :id => record.id, '_destroy' => true_variable }) ) assert_difference('@pirate.send(@association_name).count', -1) do @pirate.save end end end def test_should_not_destroy_the_associated_model_with_a_non_truthy_argument [nil, '', '0', 0, 'false', false].each do |false_variable| @alternate_params[association_getter]['foo']['_destroy'] = false_variable assert_no_difference('@pirate.send(@association_name).count') do @pirate.update(@alternate_params) end end end def test_should_not_destroy_the_associated_model_until_the_parent_is_saved assert_no_difference('@pirate.send(@association_name).count') do @pirate.send(association_setter, @alternate_params[association_getter].merge('baz' => { :id => @child_1.id, '_destroy' => true })) end assert_difference('@pirate.send(@association_name).count', -1) { @pirate.save } end def test_should_automatically_enable_autosave_on_the_association assert Pirate.reflect_on_association(@association_name).options[:autosave] end def test_validate_presence_of_parent_works_with_inverse_of Man.accepts_nested_attributes_for(:interests) assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of] assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of] repair_validations(Interest) do Interest.validates_presence_of(:man) assert_difference 'Man.count' do assert_difference 'Interest.count', 2 do man = Man.create!(:name => 'John', :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) assert_equal 2, man.interests.count end end end end def test_can_use_symbols_as_object_identifier @pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } } assert_nothing_raised(NoMethodError) { @pirate.save! } end def test_numeric_column_changes_from_zero_to_no_empty_string Man.accepts_nested_attributes_for(:interests) repair_validations(Interest) do Interest.validates_numericality_of(:zine_id) man = Man.create(name: 'John') interest = man.interests.create(topic: 'bar', zine_id: 0) assert interest.save assert !man.update({interests_attributes: { id: interest.id, zine_id: 'foo' }}) end end private def association_setter @association_setter ||= "#{@association_name}_attributes=".to_sym end def association_getter @association_getter ||= "#{@association_name}_attributes".to_sym end end class TestNestedAttributesOnAHasManyAssociation < ActiveRecord::TestCase def setup @association_type = :has_many @association_name = :birds @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") @pirate.birds.create!(:name => 'Posideons Killer') @pirate.birds.create!(:name => 'Killer bandita Dionne') @child_1, @child_2 = @pirate.birds @alternate_params = { :birds_attributes => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }, 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' } } } end include NestedAttributesOnACollectionAssociationTests end class TestNestedAttributesOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase def setup @association_type = :has_and_belongs_to_many @association_name = :parrots @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") @pirate.parrots.create!(:name => 'Posideons Killer') @pirate.parrots.create!(:name => 'Killer bandita Dionne') @child_1, @child_2 = @pirate.parrots @alternate_params = { :parrots_attributes => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }, 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' } } } end include NestedAttributesOnACollectionAssociationTests end module NestedAttributesLimitTests def teardown Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end def test_limit_with_less_records @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Big Big Love' } } } assert_difference('Parrot.count') { @pirate.save! } end def test_limit_with_number_exact_records @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, 'bar' => { :name => 'Blown Away' } } } assert_difference('Parrot.count', 2) { @pirate.save! } end def test_limit_with_exceeding_records assert_raises(ActiveRecord::NestedAttributes::TooManyRecords) do @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, 'bar' => { :name => 'Blown Away' }, 'car' => { :name => 'The Happening' }} } end end end class TestNestedAttributesLimitNumeric < ActiveRecord::TestCase def setup Pirate.accepts_nested_attributes_for :parrots, :limit => 2 @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") end include NestedAttributesLimitTests end class TestNestedAttributesLimitSymbol < ActiveRecord::TestCase def setup Pirate.accepts_nested_attributes_for :parrots, :limit => :parrots_limit @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?", :parrots_limit => 2) end include NestedAttributesLimitTests end class TestNestedAttributesLimitProc < ActiveRecord::TestCase def setup Pirate.accepts_nested_attributes_for :parrots, :limit => proc { 2 } @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") end include NestedAttributesLimitTests end class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase fixtures :owners, :pets def setup Owner.accepts_nested_attributes_for :pets, :allow_destroy => true @owner = owners(:ashley) @pet1, @pet2 = pets(:chew), pets(:mochi) @params = { :pets_attributes => { '0' => { :id => @pet1.id, :name => 'Foo' }, '1' => { :id => @pet2.id, :name => 'Bar' } } } end def test_should_update_existing_records_with_non_standard_primary_key @owner.update(@params) assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name) end def test_attr_accessor_of_child_should_be_value_provided_during_update @owner = owners(:ashley) @pet1 = pets(:chew) attributes = {:pets_attributes => { "1"=> { :id => @pet1.id, :name => "Foo2", :current_user => "John", :_destroy=>true }}} @owner.update(attributes) assert_equal 'John', Pet.after_destroy_output end end class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.create!(:catchphrase => "My baby takes tha mornin' train!") @ship = @pirate.create_ship(:name => "The good ship Dollypop") @part = @ship.parts.create!(:name => "Mast") @trinket = @part.trinkets.create!(:name => "Necklace") end test "when great-grandchild changed in memory, saving parent should save great-grandchild" do @trinket.name = "changed" @pirate.save assert_equal "changed", @trinket.reload.name end test "when great-grandchild changed via attributes, saving parent should save great-grandchild" do @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]}} @pirate.save assert_equal "changed", @trinket.reload.name end test "when great-grandchild marked_for_destruction via attributes, saving parent should destroy great-grandchild" do @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]}} assert_difference('@part.trinkets.count', -1) { @pirate.save } end test "when great-grandchild added via attributes, saving parent should create great-grandchild" do @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]}} assert_difference('@part.trinkets.count', 1) { @pirate.save } end test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do @trinket.name = "changed" Ship.create!(:pirate => @pirate, :name => "The Black Rock") ShipPart.create!(:ship => @ship, :name => "Stern") assert_no_queries { @pirate.valid? } end end class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup @ship = Ship.create!(:name => "The good ship Dollypop") @part = @ship.parts.create!(:name => "Mast") @trinket = @part.trinkets.create!(:name => "Necklace") end test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}] assert_equal 1, @ship.association(:parts).target.size assert_equal 'Deck', @ship.parts[0].name end test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do @ship.parts_attributes=[{:id => @part.id,:trinkets_attributes =>[{:id => @trinket.id, :name => 'Ruby'}]}] assert_equal 1, @ship.association(:parts).target.size assert_equal 'Mast', @ship.parts[0].name assert_no_difference("@ship.parts[0].association(:trinkets).target.size") do @ship.parts[0].association(:trinkets).target.size end assert_equal 'Ruby', @ship.parts[0].trinkets[0].name @ship.save assert_equal 'Ruby', @ship.parts[0].trinkets[0].name end test "when grandchild changed in memory, saving parent should save grandchild" do @trinket.name = "changed" @ship.save assert_equal "changed", @trinket.reload.name end test "when grandchild changed via attributes, saving parent should save grandchild" do @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]} @ship.save assert_equal "changed", @trinket.reload.name end test "when grandchild marked_for_destruction via attributes, saving parent should destroy grandchild" do @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]} assert_difference('@part.trinkets.count', -1) { @ship.save } end test "when grandchild added via attributes, saving parent should create grandchild" do @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]} assert_difference('@part.trinkets.count', 1) { @ship.save } end test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do @trinket.name = "changed" Ship.create!(:name => "The Black Rock") ShipPart.create!(:ship => @ship, :name => "Stern") assert_no_queries { @ship.valid? } end test "circular references do not perform unnecessary queries" do ship = Ship.new(name: "The Black Rock") part = ship.parts.build(name: "Stern") ship.treasures.build(looter: part) assert_queries 3 do ship.save! end end test "nested singular associations are validated" do part = ShipPart.new(name: "Stern", ship_attributes: { name: nil }) assert_not part.valid? assert_equal ["Ship name can't be blank"], part.errors.full_messages end end rails-4.2.6/activerecord/test/cases/nested_attributes_with_callbacks_test.rb000066400000000000000000000113141266740050600275400ustar00rootroot00000000000000require "cases/helper" require "models/pirate" require "models/bird" class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase Pirate.has_many(:birds_with_add_load, :class_name => "Bird", :before_add => proc { |p,b| @@add_callback_called << b p.birds_with_add_load.to_a }) Pirate.has_many(:birds_with_add, :class_name => "Bird", :before_add => proc { |p,b| @@add_callback_called << b }) Pirate.accepts_nested_attributes_for(:birds_with_add_load, :birds_with_add, :allow_destroy => true) def setup @@add_callback_called = [] @pirate = Pirate.new.tap do |pirate| pirate.catchphrase = "Don't call me!" pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}] pirate.save! end @birds = @pirate.birds.to_a end def bird_to_update @birds[0] end def bird_to_destroy @birds[1] end def existing_birds_attributes @birds.map do |bird| bird.attributes.slice("id","name") end end def new_birds @pirate.birds_with_add.to_a - @birds end def new_bird_attributes [{'name' => "New Bird"}] end def destroy_bird_attributes [{'id' => bird_to_destroy.id.to_s, "_destroy" => true}] end def update_new_and_destroy_bird_attributes [{'id' => @birds[0].id.to_s, 'name' => 'New Name'}, {'name' => "New Bird"}, {'id' => bird_to_destroy.id.to_s, "_destroy" => true}] end # Characterizing when :before_add callback is called test ":before_add called for new bird when not loaded" do assert_not @pirate.birds_with_add.loaded? @pirate.birds_with_add_attributes = new_bird_attributes assert_new_bird_with_callback_called end test ":before_add called for new bird when loaded" do @pirate.birds_with_add.load_target @pirate.birds_with_add_attributes = new_bird_attributes assert_new_bird_with_callback_called end def assert_new_bird_with_callback_called assert_equal(1, new_birds.size) assert_equal(new_birds, @@add_callback_called) end test ":before_add not called for identical assignment when not loaded" do assert_not @pirate.birds_with_add.loaded? @pirate.birds_with_add_attributes = existing_birds_attributes assert_callbacks_not_called end test ":before_add not called for identical assignment when loaded" do @pirate.birds_with_add.load_target @pirate.birds_with_add_attributes = existing_birds_attributes assert_callbacks_not_called end test ":before_add not called for destroy assignment when not loaded" do assert_not @pirate.birds_with_add.loaded? @pirate.birds_with_add_attributes = destroy_bird_attributes assert_callbacks_not_called end test ":before_add not called for deletion assignment when loaded" do @pirate.birds_with_add.load_target @pirate.birds_with_add_attributes = destroy_bird_attributes assert_callbacks_not_called end def assert_callbacks_not_called assert_empty new_birds assert_empty @@add_callback_called end # Ensuring that the records in the association target are updated, # whether the association is loaded before or not test "Assignment updates records in target when not loaded" do assert_not @pirate.birds_with_add.loaded? @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes assert_assignment_affects_records_in_target(:birds_with_add) end test "Assignment updates records in target when loaded" do @pirate.birds_with_add.load_target @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes assert_assignment_affects_records_in_target(:birds_with_add) end test("Assignment updates records in target when not loaded" + " and callback loads target") do assert_not @pirate.birds_with_add_load.loaded? @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes assert_assignment_affects_records_in_target(:birds_with_add_load) end test("Assignment updates records in target when loaded" + " and callback loads target") do @pirate.birds_with_add_load.load_target @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes assert_assignment_affects_records_in_target(:birds_with_add_load) end def assert_assignment_affects_records_in_target(association_name) association = @pirate.send(association_name) assert association.detect {|b| b == bird_to_update }.name_changed?, 'Update record not updated' assert association.detect {|b| b == bird_to_destroy }.marked_for_destruction?, 'Destroy record not marked for destruction' end end rails-4.2.6/activerecord/test/cases/persistence_test.rb000066400000000000000000000700561266740050600233120ustar00rootroot00000000000000require "cases/helper" require 'models/aircraft' require 'models/post' require 'models/comment' require 'models/author' require 'models/topic' require 'models/reply' require 'models/category' require 'models/company' require 'models/developer' require 'models/computer' require 'models/project' require 'models/minimalistic' require 'models/warehouse_thing' require 'models/parrot' require 'models/minivan' require 'models/owner' require 'models/person' require 'models/pet' require 'models/toy' require 'rexml/document' class PersistenceTest < ActiveRecord::TestCase fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :author_addresses, :categorizations, :categories, :posts, :minivans, :pets, :toys # Oracle UPDATE does not support ORDER BY unless current_adapter?(:OracleAdapter) def test_update_all_ignores_order_without_limit_from_association author = authors(:david) assert_nothing_raised do assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ]) end end def test_update_all_doesnt_ignore_order assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error test_update_with_order_succeeds = lambda do |order| begin Author.order(order).update_all('id = id + 1') rescue ActiveRecord::ActiveRecordError false end end if test_update_with_order_succeeds.call('id DESC') assert !test_update_with_order_succeeds.call('id ASC') # test that this wasn't a fluke and using an incorrect order results in an exception else # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\Z/i) do test_update_with_order_succeeds.call('id DESC') end end end def test_update_all_with_order_and_limit_updates_subset_only author = authors(:david) assert_nothing_raised do assert_equal 1, author.posts_sorted_by_id_limited.size assert_equal 2, author.posts_sorted_by_id_limited.limit(2).to_a.size assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ]) assert_equal "bulk update!", posts(:welcome).body assert_not_equal "bulk update!", posts(:thinking).body end end end def test_update_many topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } updated = Topic.update(topic_data.keys, topic_data.values) assert_equal 2, updated.size assert_equal "1 updated", Topic.find(1).content assert_equal "2 updated", Topic.find(2).content end def test_delete_all assert Topic.count > 0 assert_equal Topic.count, Topic.delete_all end def test_delete_all_with_joins_and_where_part_is_hash where_args = {:toys => {:name => 'Bone'}} count = Pet.joins(:toys).where(where_args).count assert_equal count, 1 assert_equal count, Pet.joins(:toys).where(where_args).delete_all end def test_delete_all_with_joins_and_where_part_is_not_hash where_args = ['toys.name = ?', 'Bone'] count = Pet.joins(:toys).where(where_args).count assert_equal count, 1 assert_equal count, Pet.joins(:toys).where(where_args).delete_all end def test_increment_attribute assert_equal 50, accounts(:signals37).credit_limit accounts(:signals37).increment! :credit_limit assert_equal 51, accounts(:signals37, :reload).credit_limit accounts(:signals37).increment(:credit_limit).increment!(:credit_limit) assert_equal 53, accounts(:signals37, :reload).credit_limit end def test_increment_nil_attribute assert_nil topics(:first).parent_id topics(:first).increment! :parent_id assert_equal 1, topics(:first).parent_id end def test_increment_attribute_by assert_equal 50, accounts(:signals37).credit_limit accounts(:signals37).increment! :credit_limit, 5 assert_equal 55, accounts(:signals37, :reload).credit_limit accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3) assert_equal 59, accounts(:signals37, :reload).credit_limit end def test_destroy_all conditions = "author_name = 'Mary'" topics_by_mary = Topic.all.merge!(:where => conditions, :order => 'id').to_a assert ! topics_by_mary.empty? assert_difference('Topic.count', -topics_by_mary.size) do destroyed = Topic.destroy_all(conditions).sort_by(&:id) assert_equal topics_by_mary, destroyed assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen" end end def test_destroy_many clients = Client.all.merge!(:order => 'id').find([2, 3]) assert_difference('Client.count', -2) do destroyed = Client.destroy([2, 3]).sort_by(&:id) assert_equal clients, destroyed assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen" end end def test_becomes assert_kind_of Reply, topics(:first).becomes(Reply) assert_equal "The First Topic", topics(:first).becomes(Reply).title end def test_becomes_includes_errors company = Company.new(:name => nil) assert !company.valid? original_errors = company.errors client = company.becomes(Client) assert_equal original_errors, client.errors end def test_dupd_becomes_persists_changes_from_the_original original = topics(:first) copy = original.dup.becomes(Reply) copy.save! assert_equal "The First Topic", Topic.find(copy.id).title end def test_becomes_includes_changed_attributes company = Company.new(name: "37signals") client = company.becomes(Client) assert_equal "37signals", client.name assert_equal %w{name}, client.changed end def test_delete_many original_count = Topic.count Topic.delete(deleting = [1, 2]) assert_equal original_count - deleting.size, Topic.count end def test_decrement_attribute assert_equal 50, accounts(:signals37).credit_limit accounts(:signals37).decrement!(:credit_limit) assert_equal 49, accounts(:signals37, :reload).credit_limit accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit) assert_equal 47, accounts(:signals37, :reload).credit_limit end def test_decrement_attribute_by assert_equal 50, accounts(:signals37).credit_limit accounts(:signals37).decrement! :credit_limit, 5 assert_equal 45, accounts(:signals37, :reload).credit_limit accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3) assert_equal 41, accounts(:signals37, :reload).credit_limit end def test_create topic = Topic.new topic.title = "New Topic" topic.save topic_reloaded = Topic.find(topic.id) assert_equal("New Topic", topic_reloaded.title) end def test_save! topic = Topic.new(:title => "New Topic") assert topic.save! reply = WrongReply.new assert_raise(ActiveRecord::RecordInvalid) { reply.save! } end def test_save_null_string_attributes topic = Topic.find(1) topic.attributes = { "title" => "null", "author_name" => "null" } topic.save! topic.reload assert_equal("null", topic.title) assert_equal("null", topic.author_name) end def test_save_nil_string_attributes topic = Topic.find(1) topic.title = nil topic.save! topic.reload assert_nil topic.title end def test_save_for_record_with_only_primary_key minimalistic = Minimalistic.new assert_nothing_raised { minimalistic.save } end def test_save_for_record_with_only_primary_key_that_is_provided assert_nothing_raised { Minimalistic.create!(:id => 2) } end def test_save_with_duping_of_destroyed_object developer = Developer.first developer.destroy new_developer = developer.dup new_developer.save assert new_developer.persisted? assert_not new_developer.destroyed? end def test_create_many topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) assert_equal 2, topics.size assert_equal "first", topics.first.title end def test_create_columns_not_equal_attributes topic = Topic.instantiate( 'title' => 'Another New Topic', 'does_not_exist' => 'test' ) assert_nothing_raised { topic.save } end def test_create_through_factory_with_block topic = Topic.create("title" => "New Topic") do |t| t.author_name = "David" end assert_equal("New Topic", topic.title) assert_equal("David", topic.author_name) end def test_create_many_through_factory_with_block topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) do |t| t.author_name = "David" end assert_equal 2, topics.size topic1, topic2 = Topic.find(topics[0].id), Topic.find(topics[1].id) assert_equal "first", topic1.title assert_equal "David", topic1.author_name assert_equal "second", topic2.title assert_equal "David", topic2.author_name end def test_update_object topic = Topic.new topic.title = "Another New Topic" topic.written_on = "2003-12-12 23:23:00" topic.save topic_reloaded = Topic.find(topic.id) assert_equal("Another New Topic", topic_reloaded.title) topic_reloaded.title = "Updated topic" topic_reloaded.save topic_reloaded_again = Topic.find(topic.id) assert_equal("Updated topic", topic_reloaded_again.title) end def test_update_columns_not_equal_attributes topic = Topic.new topic.title = "Still another topic" topic.save topic_reloaded = Topic.instantiate(topic.attributes.merge('does_not_exist' => 'test')) topic_reloaded.title = 'A New Topic' assert_nothing_raised { topic_reloaded.save } end def test_update_for_record_with_only_primary_key minimalistic = minimalistics(:first) assert_nothing_raised { minimalistic.save } end def test_update_sti_type assert_instance_of Reply, topics(:second) topic = topics(:second).becomes!(Topic) assert_instance_of Topic, topic topic.save! assert_instance_of Topic, Topic.find(topic.id) end def test_preserve_original_sti_type reply = topics(:second) assert_equal "Reply", reply.type topic = reply.becomes(Topic) assert_equal "Reply", reply.type assert_instance_of Topic, topic assert_equal "Reply", topic.type end def test_update_sti_subclass_type assert_instance_of Topic, topics(:first) reply = topics(:first).becomes!(Reply) assert_instance_of Reply, reply reply.save! assert_instance_of Reply, Reply.find(reply.id) end def test_update_after_create klass = Class.new(Topic) do def self.name; 'Topic'; end after_create do update_attribute("author_name", "David") end end topic = klass.new topic.title = "Another New Topic" topic.save topic_reloaded = Topic.find(topic.id) assert_equal("Another New Topic", topic_reloaded.title) assert_equal("David", topic_reloaded.author_name) end def test_delete topic = Topic.find(1) assert_equal topic, topic.delete, 'topic.delete did not return self' assert topic.frozen?, 'topic not frozen after delete' assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } end def test_delete_doesnt_run_callbacks Topic.find(1).delete assert_not_nil Topic.find(2) end def test_destroy topic = Topic.find(1) assert_equal topic, topic.destroy, 'topic.destroy did not return self' assert topic.frozen?, 'topic not frozen after destroy' assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } end def test_destroy! topic = Topic.find(1) assert_equal topic, topic.destroy!, 'topic.destroy! did not return self' assert topic.frozen?, 'topic not frozen after destroy!' assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } end def test_record_not_found_exception assert_raise(ActiveRecord::RecordNotFound) { Topic.find(99999) } end def test_update_all assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'") assert_equal "bulk updated!", Topic.find(1).content assert_equal "bulk updated!", Topic.find(2).content assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!']) assert_equal "bulk updated again!", Topic.find(1).content assert_equal "bulk updated again!", Topic.find(2).content assert_equal Topic.count, Topic.update_all(['content = ?', nil]) assert_nil Topic.find(1).content end def test_update_all_with_hash assert_not_nil Topic.find(1).last_read assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil) assert_equal "bulk updated with hash!", Topic.find(1).content assert_equal "bulk updated with hash!", Topic.find(2).content assert_nil Topic.find(1).last_read assert_nil Topic.find(2).last_read end def test_update_all_with_non_standard_table_name assert_equal 1, WarehouseThing.where(id: 1).update_all(['value = ?', 0]) assert_equal 0, WarehouseThing.find(1).value end def test_delete_new_record client = Client.new client.delete assert client.frozen? end def test_delete_record_with_associations client = Client.find(3) client.delete assert client.frozen? assert_kind_of Firm, client.firm assert_raise(RuntimeError) { client.name = "something else" } end def test_destroy_new_record client = Client.new client.destroy assert client.frozen? end def test_destroy_record_with_associations client = Client.find(3) client.destroy assert client.frozen? assert_kind_of Firm, client.firm assert_raise(RuntimeError) { client.name = "something else" } end def test_update_attribute assert !Topic.find(1).approved? Topic.find(1).update_attribute("approved", true) assert Topic.find(1).approved? Topic.find(1).update_attribute(:approved, false) assert !Topic.find(1).approved? end def test_update_attribute_for_readonly_attribute minivan = Minivan.find('m1') assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') } end def test_update_attribute_with_one_updated t = Topic.first t.update_attribute(:title, 'super_title') assert_equal 'super_title', t.title assert !t.changed?, "topic should not have changed" assert !t.title_changed?, "title should not have changed" assert_nil t.title_change, 'title change should be nil' t.reload assert_equal 'super_title', t.title end def test_update_attribute_for_updated_at_on developer = Developer.find(1) prev_month = Time.now.prev_month.change(usec: 0) developer.update_attribute(:updated_at, prev_month) assert_equal prev_month, developer.updated_at developer.update_attribute(:salary, 80001) assert_not_equal prev_month, developer.updated_at developer.reload assert_not_equal prev_month, developer.updated_at end def test_update_column topic = Topic.find(1) topic.update_column("approved", true) assert topic.approved? topic.reload assert topic.approved? topic.update_column(:approved, false) assert !topic.approved? topic.reload assert !topic.approved? end def test_update_column_should_not_use_setter_method dev = Developer.find(1) dev.instance_eval { def salary=(value); write_attribute(:salary, value * 2); end } dev.update_column(:salary, 80000) assert_equal 80000, dev.salary dev.reload assert_equal 80000, dev.salary end def test_update_column_should_raise_exception_if_new_record topic = Topic.new assert_raises(ActiveRecord::ActiveRecordError) { topic.update_column("approved", false) } end def test_update_column_should_not_leave_the_object_dirty topic = Topic.find(1) topic.update_column("content", "--- Have a nice day\n...\n") topic.reload topic.update_column(:content, "--- You too\n...\n") assert_equal [], topic.changed topic.reload topic.update_column("content", "--- Have a nice day\n...\n") assert_equal [], topic.changed end def test_update_column_with_model_having_primary_key_other_than_id minivan = Minivan.find('m1') new_name = 'sebavan' minivan.update_column(:name, new_name) assert_equal new_name, minivan.name end def test_update_column_for_readonly_attribute minivan = Minivan.find('m1') prev_color = minivan.color assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_column(:color, 'black') } assert_equal prev_color, minivan.color end def test_update_column_should_not_modify_updated_at developer = Developer.find(1) prev_month = Time.now.prev_month.change(usec: 0) developer.update_column(:updated_at, prev_month) assert_equal prev_month, developer.updated_at developer.update_column(:salary, 80001) assert_equal prev_month, developer.updated_at developer.reload assert_equal prev_month.to_i, developer.updated_at.to_i end def test_update_column_with_one_changed_and_one_updated t = Topic.order('id').limit(1).first author_name = t.author_name t.author_name = 'John' t.update_column(:title, 'super_title') assert_equal 'John', t.author_name assert_equal 'super_title', t.title assert t.changed?, "topic should have changed" assert t.author_name_changed?, "author_name should have changed" t.reload assert_equal author_name, t.author_name assert_equal 'super_title', t.title end def test_update_column_with_default_scope developer = DeveloperCalledDavid.first developer.name = 'John' developer.save! assert developer.update_column(:name, 'Will'), 'did not update record due to default scope' end def test_update_columns topic = Topic.find(1) topic.update_columns({ "approved" => true, title: "Sebastian Topic" }) assert topic.approved? assert_equal "Sebastian Topic", topic.title topic.reload assert topic.approved? assert_equal "Sebastian Topic", topic.title end def test_update_columns_should_not_use_setter_method dev = Developer.find(1) dev.instance_eval { def salary=(value); write_attribute(:salary, value * 2); end } dev.update_columns(salary: 80000) assert_equal 80000, dev.salary dev.reload assert_equal 80000, dev.salary end def test_update_columns_should_raise_exception_if_new_record topic = Topic.new assert_raises(ActiveRecord::ActiveRecordError) { topic.update_columns({ approved: false }) } end def test_update_columns_should_not_leave_the_object_dirty topic = Topic.find(1) topic.update({ "content" => "--- Have a nice day\n...\n", :author_name => "Jose" }) topic.reload topic.update_columns({ content: "--- You too\n...\n", "author_name" => "Sebastian" }) assert_equal [], topic.changed topic.reload topic.update_columns({ content: "--- Have a nice day\n...\n", author_name: "Jose" }) assert_equal [], topic.changed end def test_update_columns_with_model_having_primary_key_other_than_id minivan = Minivan.find('m1') new_name = 'sebavan' minivan.update_columns(name: new_name) assert_equal new_name, minivan.name end def test_update_columns_with_one_readonly_attribute minivan = Minivan.find('m1') prev_color = minivan.color prev_name = minivan.name assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_columns({ name: "My old minivan", color: 'black' }) } assert_equal prev_color, minivan.color assert_equal prev_name, minivan.name minivan.reload assert_equal prev_color, minivan.color assert_equal prev_name, minivan.name end def test_update_columns_should_not_modify_updated_at developer = Developer.find(1) prev_month = Time.now.prev_month.change(usec: 0) developer.update_columns(updated_at: prev_month) assert_equal prev_month, developer.updated_at developer.update_columns(salary: 80000) assert_equal prev_month, developer.updated_at assert_equal 80000, developer.salary developer.reload assert_equal prev_month.to_i, developer.updated_at.to_i assert_equal 80000, developer.salary end def test_update_columns_with_one_changed_and_one_updated t = Topic.order('id').limit(1).first author_name = t.author_name t.author_name = 'John' t.update_columns(title: 'super_title') assert_equal 'John', t.author_name assert_equal 'super_title', t.title assert t.changed?, "topic should have changed" assert t.author_name_changed?, "author_name should have changed" t.reload assert_equal author_name, t.author_name assert_equal 'super_title', t.title end def test_update_columns_changing_id topic = Topic.find(1) topic.update_columns(id: 123) assert_equal 123, topic.id topic.reload assert_equal 123, topic.id end def test_update_columns_returns_boolean topic = Topic.find(1) assert_equal true, topic.update_columns(title: "New title") end def test_update_columns_with_default_scope developer = DeveloperCalledDavid.first developer.name = 'John' developer.save! assert developer.update_columns(name: 'Will'), 'did not update record due to default scope' end def test_update topic = Topic.find(1) assert !topic.approved? assert_equal "The First Topic", topic.title topic.update("approved" => true, "title" => "The First Topic Updated") topic.reload assert topic.approved? assert_equal "The First Topic Updated", topic.title topic.update(approved: false, title: "The First Topic") topic.reload assert !topic.approved? assert_equal "The First Topic", topic.title end def test_update_attributes topic = Topic.find(1) assert !topic.approved? assert_equal "The First Topic", topic.title topic.update_attributes("approved" => true, "title" => "The First Topic Updated") topic.reload assert topic.approved? assert_equal "The First Topic Updated", topic.title topic.update_attributes(approved: false, title: "The First Topic") topic.reload assert !topic.approved? assert_equal "The First Topic", topic.title assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do topic.update_attributes(id: 3, title: "Hm is it possible?") end assert_not_equal "Hm is it possible?", Topic.find(3).title topic.update_attributes(id: 1234) assert_nothing_raised { topic.reload } assert_equal topic.title, Topic.find(1234).title end def test_update_attributes_parameters topic = Topic.find(1) assert_nothing_raised do topic.update_attributes({}) end assert_raises(ArgumentError) do topic.update_attributes(nil) end end def test_update! Reply.validates_presence_of(:title) reply = Reply.find(2) assert_equal "The Second Topic of the day", reply.title assert_equal "Have a nice day", reply.content reply.update!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening") reply.reload assert_equal "The Second Topic of the day updated", reply.title assert_equal "Have a nice evening", reply.content reply.update!(title: "The Second Topic of the day", content: "Have a nice day") reply.reload assert_equal "The Second Topic of the day", reply.title assert_equal "Have a nice day", reply.content assert_raise(ActiveRecord::RecordInvalid) { reply.update!(title: nil, content: "Have a nice evening") } ensure Reply.clear_validators! end def test_update_attributes! Reply.validates_presence_of(:title) reply = Reply.find(2) assert_equal "The Second Topic of the day", reply.title assert_equal "Have a nice day", reply.content reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening") reply.reload assert_equal "The Second Topic of the day updated", reply.title assert_equal "Have a nice evening", reply.content reply.update_attributes!(title: "The Second Topic of the day", content: "Have a nice day") reply.reload assert_equal "The Second Topic of the day", reply.title assert_equal "Have a nice day", reply.content assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(title: nil, content: "Have a nice evening") } ensure Reply.clear_validators! end def test_destroyed_returns_boolean developer = Developer.first assert_equal false, developer.destroyed? developer.destroy assert_equal true, developer.destroyed? developer = Developer.last assert_equal false, developer.destroyed? developer.delete assert_equal true, developer.destroyed? end def test_persisted_returns_boolean developer = Developer.new(:name => "Jose") assert_equal false, developer.persisted? developer.save! assert_equal true, developer.persisted? developer = Developer.first assert_equal true, developer.persisted? developer.destroy assert_equal false, developer.persisted? developer = Developer.last assert_equal true, developer.persisted? developer.delete assert_equal false, developer.persisted? end def test_class_level_destroy should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") Topic.find(1).replies << should_be_destroyed_reply Topic.destroy(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) } end def test_class_level_delete should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") Topic.find(1).replies << should_be_destroyed_reply Topic.delete(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) } end def test_create_with_custom_timestamps custom_datetime = 1.hour.ago.beginning_of_day %w(created_at created_on updated_at updated_on).each do |attribute| parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime) assert_equal custom_datetime, parrot[attribute] end end def test_persist_inherited_class_with_different_table_name minimalistic_aircrafts = Class.new(Minimalistic) do self.table_name = "aircraft" end assert_difference "Aircraft.count", 1 do aircraft = minimalistic_aircrafts.create(name: "Wright Flyer") aircraft.name = "Wright Glider" aircraft.save end assert_equal "Wright Glider", Aircraft.last.name end def test_instantiate_creates_a_new_instance post = Post.instantiate("title" => "appropriate documentation", "type" => "SpecialPost") assert_equal "appropriate documentation", post.title assert_instance_of SpecialPost, post # body was not initialized assert_raises ActiveModel::MissingAttributeError do post.body end end def test_reload_removes_custom_selects post = Post.select('posts.*, 1 as wibble').last! assert_equal 1, post[:wibble] assert_nil post.reload[:wibble] end def test_find_via_reload post = Post.new assert post.new_record? post.id = 1 post.reload assert_equal "Welcome to the weblog", post.title assert_not post.new_record? end def test_reload_via_querycache ActiveRecord::Base.connection.enable_query_cache! ActiveRecord::Base.connection.clear_query_cache assert ActiveRecord::Base.connection.query_cache_enabled, 'cache should be on' parrot = Parrot.create(:name => 'Shane') # populate the cache with the SELECT result found_parrot = Parrot.find(parrot.id) assert_equal parrot.id, found_parrot.id # Manually update the 'name' attribute in the DB directly assert_equal 1, ActiveRecord::Base.connection.query_cache.length ActiveRecord::Base.uncached do found_parrot.name = 'Mary' found_parrot.save end # Now reload, and verify that it gets the DB version, and not the querycache version found_parrot.reload assert_equal 'Mary', found_parrot.name found_parrot = Parrot.find(parrot.id) assert_equal 'Mary', found_parrot.name ensure ActiveRecord::Base.connection.disable_query_cache! end end rails-4.2.6/activerecord/test/cases/pooled_connections_test.rb000066400000000000000000000050331266740050600246430ustar00rootroot00000000000000require "cases/helper" require "models/project" require "timeout" class PooledConnectionsTest < ActiveRecord::TestCase self.use_transactional_fixtures = false def setup @per_test_teardown = [] @connection = ActiveRecord::Base.remove_connection end teardown do ActiveRecord::Base.clear_all_connections! ActiveRecord::Base.establish_connection(@connection) @per_test_teardown.each {|td| td.call } end # Will deadlock due to lack of Monitor timeouts in 1.9 def checkout_checkin_connections(pool_size, threads) ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) @connection_count = 0 @timed_out = 0 threads.times do Thread.new do begin conn = ActiveRecord::Base.connection_pool.checkout sleep 0.1 ActiveRecord::Base.connection_pool.checkin conn @connection_count += 1 rescue ActiveRecord::ConnectionTimeoutError @timed_out += 1 end end.join end end def checkout_checkin_connections_loop(pool_size, loops) ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) @connection_count = 0 @timed_out = 0 loops.times do begin conn = ActiveRecord::Base.connection_pool.checkout ActiveRecord::Base.connection_pool.checkin conn @connection_count += 1 ActiveRecord::Base.connection.tables rescue ActiveRecord::ConnectionTimeoutError @timed_out += 1 end end end def test_pooled_connection_checkin_one checkout_checkin_connections 1, 2 assert_equal 2, @connection_count assert_equal 0, @timed_out assert_equal 1, ActiveRecord::Base.connection_pool.connections.size end def test_pooled_connection_checkin_two checkout_checkin_connections_loop 2, 3 assert_equal 3, @connection_count assert_equal 0, @timed_out assert_equal 2, ActiveRecord::Base.connection_pool.connections.size end def test_pooled_connection_remove ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.5})) old_connection = ActiveRecord::Base.connection extra_connection = ActiveRecord::Base.connection_pool.checkout ActiveRecord::Base.connection_pool.remove(extra_connection) assert_equal ActiveRecord::Base.connection, old_connection end private def add_record(name) ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name } end end unless in_memory_db? rails-4.2.6/activerecord/test/cases/primary_keys_test.rb000066400000000000000000000154421266740050600235020ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/reply' require 'models/subscriber' require 'models/movie' require 'models/keyboard' require 'models/mixed_case_monkey' require 'models/dashboard' class PrimaryKeysTest < ActiveRecord::TestCase fixtures :topics, :subscribers, :movies, :mixed_case_monkeys def test_to_key_with_default_primary_key topic = Topic.new assert_nil topic.to_key topic = Topic.find(1) assert_equal [1], topic.to_key end def test_to_key_with_customized_primary_key keyboard = Keyboard.new assert_nil keyboard.to_key keyboard.save assert_equal keyboard.to_key, [keyboard.id] end def test_read_attribute_with_custom_primary_key keyboard = Keyboard.create! assert_equal keyboard.key_number, keyboard.read_attribute(:id) end def test_to_key_with_primary_key_after_destroy topic = Topic.find(1) topic.destroy assert_equal [1], topic.to_key end def test_integer_key topic = Topic.find(1) assert_equal(topics(:first).author_name, topic.author_name) topic = Topic.find(2) assert_equal(topics(:second).author_name, topic.author_name) topic = Topic.new topic.title = "New Topic" assert_nil topic.id assert_nothing_raised { topic.save! } id = topic.id topicReloaded = Topic.find(id) assert_equal("New Topic", topicReloaded.title) end def test_customized_primary_key_auto_assigns_on_save Keyboard.delete_all keyboard = Keyboard.new(:name => 'HHKB') assert_nothing_raised { keyboard.save! } assert_equal keyboard.id, Keyboard.find_by_name('HHKB').id end def test_customized_primary_key_can_be_get_before_saving keyboard = Keyboard.new assert_nil keyboard.id assert_nothing_raised { assert_nil keyboard.key_number } end def test_customized_string_primary_key_settable_before_save subscriber = Subscriber.new assert_nothing_raised { subscriber.id = 'webster123' } assert_equal 'webster123', subscriber.id assert_equal 'webster123', subscriber.nick end def test_string_key subscriber = Subscriber.find(subscribers(:first).nick) assert_equal(subscribers(:first).name, subscriber.name) subscriber = Subscriber.find(subscribers(:second).nick) assert_equal(subscribers(:second).name, subscriber.name) subscriber = Subscriber.new subscriber.id = "jdoe" assert_equal("jdoe", subscriber.id) subscriber.name = "John Doe" assert_nothing_raised { subscriber.save! } assert_equal("jdoe", subscriber.id) subscriberReloaded = Subscriber.find("jdoe") assert_equal("John Doe", subscriberReloaded.name) end def test_find_with_more_than_one_string_key assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length end def test_primary_key_prefix old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type ActiveRecord::Base.primary_key_prefix_type = :table_name Topic.reset_primary_key assert_equal "topicid", Topic.primary_key ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore Topic.reset_primary_key assert_equal "topic_id", Topic.primary_key ActiveRecord::Base.primary_key_prefix_type = nil Topic.reset_primary_key assert_equal "id", Topic.primary_key ensure ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type end def test_delete_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.delete(1) } end def test_update_counters_should_quote_pkey_and_quote_counter_columns assert_nothing_raised { MixedCaseMonkey.update_counters(1, :fleaCount => 99) } end def test_find_with_one_id_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1) } end def test_find_with_multiple_ids_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find([1,2]) } end def test_instance_update_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1).save } end def test_instance_destroy_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1).destroy } end def test_supports_primary_key assert_nothing_raised NoMethodError do ActiveRecord::Base.connection.supports_primary_key? end end def test_primary_key_returns_value_if_it_exists klass = Class.new(ActiveRecord::Base) do self.table_name = 'developers' end if ActiveRecord::Base.connection.supports_primary_key? assert_equal 'id', klass.primary_key end end def test_primary_key_returns_nil_if_it_does_not_exist klass = Class.new(ActiveRecord::Base) do self.table_name = 'developers_projects' end if ActiveRecord::Base.connection.supports_primary_key? assert_nil klass.primary_key end end def test_quoted_primary_key_after_set_primary_key k = Class.new( ActiveRecord::Base ) assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key k.primary_key = "foo" assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key end def test_auto_detect_primary_key_from_schema MixedCaseMonkey.reset_primary_key assert_equal "monkeyID", MixedCaseMonkey.primary_key end def test_primary_key_update_with_custom_key_name dashboard = Dashboard.create!(dashboard_id: '1') dashboard.id = '2' dashboard.save! dashboard = Dashboard.first assert_equal '2', dashboard.id end end class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase self.use_transactional_fixtures = false unless in_memory_db? def test_set_primary_key_with_no_connection connection = ActiveRecord::Base.remove_connection model = Class.new(ActiveRecord::Base) model.primary_key = 'foo' assert_equal 'foo', model.primary_key ActiveRecord::Base.establish_connection(connection) assert_equal 'foo', model.primary_key end end end if current_adapter?(:MysqlAdapter, :Mysql2Adapter) class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase self.use_transactional_fixtures = false def test_primary_key_method_with_ansi_quotes con = ActiveRecord::Base.connection con.execute("SET SESSION sql_mode='ANSI_QUOTES'") assert_equal "id", con.primary_key("topics") ensure con.reconnect! end end end if current_adapter?(:PostgreSQLAdapter) class PrimaryKeyBigSerialTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class Widget < ActiveRecord::Base end setup do @connection = ActiveRecord::Base.connection @connection.create_table(:widgets, id: :bigserial) { |t| } end teardown do @connection.drop_table :widgets end def test_bigserial_primary_key assert_equal "id", Widget.primary_key assert_equal :integer, Widget.columns_hash[Widget.primary_key].type widget = Widget.create! assert_not_nil widget.id end end end rails-4.2.6/activerecord/test/cases/query_cache_test.rb000066400000000000000000000210541266740050600232500ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/task' require 'models/category' require 'models/post' require 'rack' class QueryCacheTest < ActiveRecord::TestCase fixtures :tasks, :topics, :categories, :posts, :categories_posts teardown do Task.connection.clear_query_cache ActiveRecord::Base.connection.disable_query_cache! end def test_exceptional_middleware_clears_and_disables_cache_on_error assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' mw = ActiveRecord::QueryCache.new lambda { |env| Task.find 1 Task.find 1 assert_equal 1, ActiveRecord::Base.connection.query_cache.length raise "lol borked" } assert_raises(RuntimeError) { mw.call({}) } assert_equal 0, ActiveRecord::Base.connection.query_cache.length assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' end def test_exceptional_middleware_leaves_enabled_cache_alone ActiveRecord::Base.connection.enable_query_cache! mw = ActiveRecord::QueryCache.new lambda { |env| raise "lol borked" } assert_raises(RuntimeError) { mw.call({}) } assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' end def test_exceptional_middleware_assigns_original_connection_id_on_error connection_id = ActiveRecord::Base.connection_id mw = ActiveRecord::QueryCache.new lambda { |env| ActiveRecord::Base.connection_id = self.object_id raise "lol borked" } assert_raises(RuntimeError) { mw.call({}) } assert_equal connection_id, ActiveRecord::Base.connection_id end def test_middleware_delegates called = false mw = ActiveRecord::QueryCache.new lambda { |env| called = true [200, {}, nil] } mw.call({}) assert called, 'middleware should delegate' end def test_middleware_caches mw = ActiveRecord::QueryCache.new lambda { |env| Task.find 1 Task.find 1 assert_equal 1, ActiveRecord::Base.connection.query_cache.length [200, {}, nil] } mw.call({}) end def test_cache_enabled_during_call assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' mw = ActiveRecord::QueryCache.new lambda { |env| assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' [200, {}, nil] } mw.call({}) end def test_cache_on_during_body_write streaming = Class.new do def each yield ActiveRecord::Base.connection.query_cache_enabled end end mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, streaming.new] } body = mw.call({}).last body.each { |x| assert x, 'cache should be on' } body.close assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' end def test_cache_off_after_close mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] } body = mw.call({}).last assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled' body.close assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' end def test_cache_clear_after_close mw = ActiveRecord::QueryCache.new lambda { |env| Post.first [200, {}, nil] } body = mw.call({}).last assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty' body.close assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty' end def test_cache_passing_a_relation post = Post.first Post.cache do query = post.categories.select(:post_id) assert Post.connection.select_all(query).is_a?(ActiveRecord::Result) end end def test_find_queries assert_queries(2) { Task.find(1); Task.find(1) } end def test_find_queries_with_cache Task.cache do assert_queries(1) { Task.find(1); Task.find(1) } end end def test_find_queries_with_cache_multi_record Task.cache do assert_queries(2) { Task.find(1); Task.find(1); Task.find(2) } end end def test_find_queries_with_multi_cache_blocks Task.cache do Task.cache do assert_queries(2) { Task.find(1); Task.find(2) } end assert_queries(0) { Task.find(1); Task.find(1); Task.find(2) } end end def test_count_queries_with_cache Task.cache do assert_queries(1) { Task.count; Task.count } end end def test_query_cache_dups_results_correctly Task.cache do now = Time.now.utc task = Task.find 1 assert_not_equal now, task.starting task.starting = now task.reload assert_not_equal now, task.starting end end def test_cache_is_flat Task.cache do Topic.columns # don't count this query assert_queries(1) { Topic.find(1); Topic.find(1); } end ActiveRecord::Base.cache do assert_queries(1) { Task.find(1); Task.find(1) } end end def test_cache_does_not_wrap_string_results_in_arrays Task.cache do # Oracle adapter returns count() as Fixnum or Float if current_adapter?(:OracleAdapter) assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter) # Future versions of the sqlite3 adapter will return numeric assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") else assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") end end end def test_cache_is_ignored_for_locked_relations task = Task.find 1 Task.cache do assert_queries(2) { task.lock!; task.lock! } end end def test_cache_is_available_when_connection_is_connected conf = ActiveRecord::Base.configurations ActiveRecord::Base.configurations = {} Task.cache do assert_queries(1) { Task.find(1); Task.find(1) } end ensure ActiveRecord::Base.configurations = conf end def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries ActiveRecord::Base.connection.enable_query_cache! post = Post.first Post.transaction do post.update_attributes(title: 'rollback') assert_equal 1, Post.where(title: 'rollback').to_a.count raise ActiveRecord::Rollback end assert_equal 0, Post.where(title: 'rollback').to_a.count ActiveRecord::Base.connection.uncached do assert_equal 0, Post.where(title: 'rollback').to_a.count end begin Post.transaction do post.update_attributes(title: 'rollback') assert_equal 1, Post.where(title: 'rollback').to_a.count raise 'broken' end rescue Exception end assert_equal 0, Post.where(title: 'rollback').to_a.count ActiveRecord::Base.connection.uncached do assert_equal 0, Post.where(title: 'rollback').to_a.count end end end class QueryCacheExpiryTest < ActiveRecord::TestCase fixtures :tasks, :posts, :categories, :categories_posts def test_cache_gets_cleared_after_migration # warm the cache Post.find(1) # change the column definition Post.connection.change_column :posts, :title, :string, limit: 80 assert_nothing_raised { Post.find(1) } # restore the old definition Post.connection.change_column :posts, :title, :string end def test_find Task.connection.expects(:clear_query_cache).times(1) assert !Task.connection.query_cache_enabled Task.cache do assert Task.connection.query_cache_enabled Task.find(1) Task.uncached do assert !Task.connection.query_cache_enabled Task.find(1) end assert Task.connection.query_cache_enabled end assert !Task.connection.query_cache_enabled end def test_update Task.connection.expects(:clear_query_cache).times(2) Task.cache do task = Task.find(1) task.starting = Time.now.utc task.save! end end def test_destroy Task.connection.expects(:clear_query_cache).times(2) Task.cache do Task.find(1).destroy end end def test_insert ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) Task.cache do Task.create! end end def test_cache_is_expired_by_habtm_update ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) ActiveRecord::Base.cache do c = Category.first p = Post.first p.categories << c end end def test_cache_is_expired_by_habtm_delete ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) ActiveRecord::Base.cache do p = Post.find(1) assert p.categories.any? p.categories.delete_all end end end rails-4.2.6/activerecord/test/cases/quoting_test.rb000066400000000000000000000104141266740050600224440ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class QuotingTest < ActiveRecord::TestCase def setup @quoter = Class.new { include Quoting }.new end def test_quoted_true assert_equal "'t'", @quoter.quoted_true end def test_quoted_false assert_equal "'f'", @quoter.quoted_false end def test_quote_column_name assert_equal "foo", @quoter.quote_column_name('foo') end def test_quote_table_name assert_equal "foo", @quoter.quote_table_name('foo') end def test_quote_table_name_calls_quote_column_name @quoter.extend(Module.new { def quote_column_name(string) 'lol' end }) assert_equal 'lol', @quoter.quote_table_name('foo') end def test_quote_string assert_equal "''", @quoter.quote_string("'") assert_equal "\\\\", @quoter.quote_string("\\") assert_equal "hi''i", @quoter.quote_string("hi'i") assert_equal "hi\\\\i", @quoter.quote_string("hi\\i") end def test_quoted_date t = Date.today assert_equal t.to_s(:db), @quoter.quoted_date(t) end def test_quoted_time_utc with_timezone_config default: :utc do t = Time.now assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t) end end def test_quoted_time_local with_timezone_config default: :local do t = Time.now assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t) end end def test_quoted_time_crazy with_timezone_config default: :asdfasdf do t = Time.now assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t) end end def test_quoted_datetime_utc with_timezone_config default: :utc do t = DateTime.now assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t) end end ### # DateTime doesn't define getlocal, so make sure it does nothing def test_quoted_datetime_local with_timezone_config default: :local do t = DateTime.now assert_equal t.to_s(:db), @quoter.quoted_date(t) end end def test_quote_with_quoted_id assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1), nil) end def test_quote_nil assert_equal 'NULL', @quoter.quote(nil, nil) end def test_quote_true assert_equal @quoter.quoted_true, @quoter.quote(true, nil) end def test_quote_false assert_equal @quoter.quoted_false, @quoter.quote(false, nil) end def test_quote_float float = 1.2 assert_equal float.to_s, @quoter.quote(float, nil) end def test_quote_fixnum fixnum = 1 assert_equal fixnum.to_s, @quoter.quote(fixnum, nil) end def test_quote_bignum bignum = 1 << 100 assert_equal bignum.to_s, @quoter.quote(bignum, nil) end def test_quote_bigdecimal bigdec = BigDecimal.new((1 << 100).to_s) assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, nil) end def test_dates_and_times @quoter.extend(Module.new { def quoted_date(value) 'lol' end }) assert_equal "'lol'", @quoter.quote(Date.today, nil) assert_equal "'lol'", @quoter.quote(Time.now, nil) assert_equal "'lol'", @quoter.quote(DateTime.now, nil) end def test_crazy_object crazy = Class.new.new expected = "'#{YAML.dump(crazy)}'" assert_equal expected, @quoter.quote(crazy, nil) end def test_crazy_object_calls_quote_string crazy = Class.new { def initialize; @lol = 'lo\l' end }.new assert_match "lo\\\\l", @quoter.quote(crazy, nil) end def test_quote_string_no_column assert_equal "'lo\\\\l'", @quoter.quote('lo\l', nil) end def test_quote_as_mb_chars_no_column string = ActiveSupport::Multibyte::Chars.new('lo\l') assert_equal "'lo\\\\l'", @quoter.quote(string, nil) end def test_string_with_crazy_column assert_equal "'lo\\\\l'", @quoter.quote('lo\l') end def test_quote_duration assert_equal "1800", @quoter.quote(30.minutes) end end end end rails-4.2.6/activerecord/test/cases/readonly_test.rb000066400000000000000000000073321266740050600226000ustar00rootroot00000000000000require "cases/helper" require 'models/author' require 'models/post' require 'models/comment' require 'models/developer' require 'models/computer' require 'models/project' require 'models/reader' require 'models/person' class ReadOnlyTest < ActiveRecord::TestCase fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers def test_cant_save_readonly_record dev = Developer.find(1) assert !dev.readonly? dev.readonly! assert dev.readonly? assert_nothing_raised do dev.name = 'Luscious forbidden fruit.' assert !dev.save dev.name = 'Forbidden.' end e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save } assert_equal "Developer is marked as readonly", e.message e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! } assert_equal "Developer is marked as readonly", e.message e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy } assert_equal "Developer is marked as readonly", e.message end def test_find_with_readonly_option Developer.all.each { |d| assert !d.readonly? } Developer.readonly(false).each { |d| assert !d.readonly? } Developer.readonly(true).each { |d| assert d.readonly? } Developer.readonly.each { |d| assert d.readonly? } end def test_find_with_joins_option_does_not_imply_readonly Developer.joins(' ').each { |d| assert_not d.readonly? } Developer.joins(' ').readonly(true).each { |d| assert d.readonly? } Developer.joins(', projects').each { |d| assert_not d.readonly? } Developer.joins(', projects').readonly(true).each { |d| assert d.readonly? } end def test_has_many_find_readonly post = Post.find(1) assert !post.comments.empty? assert !post.comments.any?(&:readonly?) assert !post.comments.to_a.any?(&:readonly?) assert post.comments.readonly(true).all?(&:readonly?) end def test_has_many_with_through_is_not_implicitly_marked_readonly assert people = Post.find(1).people assert !people.any?(&:readonly?) end def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_by_id assert !posts(:welcome).people.find(1).readonly? end def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_first assert !posts(:welcome).people.first.readonly? end def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_last assert !posts(:welcome).people.last.readonly? end def test_readonly_scoping Post.where('1=1').scoping do assert !Post.find(1).readonly? assert Post.readonly(true).find(1).readonly? assert !Post.readonly(false).find(1).readonly? end Post.joins(' ').scoping do assert !Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? end # Oracle barfs on this because the join includes unqualified and # conflicting column names unless current_adapter?(:OracleAdapter) Post.joins(', developers').scoping do assert_not Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? end end Post.readonly(true).scoping do assert Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? end end def test_association_collection_method_missing_scoping_not_readonly developer = Developer.find(1) project = Post.find(1) assert !developer.projects.all_as_method.first.readonly? assert !developer.projects.all_as_scope.first.readonly? assert !project.comments.all_as_method.first.readonly? assert !project.comments.all_as_scope.first.readonly? end end rails-4.2.6/activerecord/test/cases/reaper_test.rb000066400000000000000000000034541266740050600222420ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class ReaperTest < ActiveRecord::TestCase attr_reader :pool def setup super @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec end teardown do @pool.connections.each(&:close) end class FakePool attr_reader :reaped def initialize @reaped = false end def reap @reaped = true end end # A reaper with nil time should never reap connections def test_nil_time fp = FakePool.new assert !fp.reaped reaper = ConnectionPool::Reaper.new(fp, nil) reaper.run assert !fp.reaped end def test_some_time fp = FakePool.new assert !fp.reaped reaper = ConnectionPool::Reaper.new(fp, 0.0001) reaper.run until fp.reaped Thread.pass end assert fp.reaped end def test_pool_has_reaper assert pool.reaper end def test_reaping_frequency_configuration spec = ActiveRecord::Base.connection_pool.spec.dup spec.config[:reaping_frequency] = 100 pool = ConnectionPool.new spec assert_equal 100, pool.reaper.frequency end def test_connection_pool_starts_reaper spec = ActiveRecord::Base.connection_pool.spec.dup spec.config[:reaping_frequency] = '0.0001' pool = ConnectionPool.new spec conn = nil child = Thread.new do conn = pool.checkout Thread.stop end Thread.pass while conn.nil? assert conn.in_use? child.terminate while conn.in_use? Thread.pass end assert !conn.in_use? end end end end rails-4.2.6/activerecord/test/cases/reflection_test.rb000066400000000000000000000445301266740050600231160ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/customer' require 'models/company' require 'models/company_in_module' require 'models/ship' require 'models/pirate' require 'models/price_estimate' require 'models/essay' require 'models/author' require 'models/organization' require 'models/post' require 'models/tagging' require 'models/category' require 'models/book' require 'models/subscriber' require 'models/subscription' require 'models/tag' require 'models/sponsor' require 'models/edge' require 'models/hotel' require 'models/chef' require 'models/department' require 'models/cake_designer' require 'models/drink_designer' require 'models/mocktail_designer' class ReflectionTest < ActiveRecord::TestCase include ActiveRecord::Reflection fixtures :topics, :customers, :companies, :subscribers, :price_estimates def setup @first = Topic.find(1) end def test_human_name assert_equal "Price estimate", PriceEstimate.model_name.human assert_equal "Subscriber", Subscriber.model_name.human end def test_read_attribute_names assert_equal( %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count unique_replies_count parent_id parent_title type created_at updated_at ).sort, @first.attribute_names.sort ) end def test_columns assert_equal 18, Topic.columns.length end def test_columns_are_returned_in_the_order_they_were_declared column_names = Topic.columns.map { |column| column.name } assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names end def test_content_columns content_columns = Topic.content_columns content_column_names = content_columns.map {|column| column.name} assert_equal 13, content_columns.length assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort end def test_column_string_type_and_limit assert_equal :string, @first.column_for_attribute("title").type assert_equal 250, @first.column_for_attribute("title").limit end def test_column_null_not_null subscriber = Subscriber.first assert subscriber.column_for_attribute("name").null assert !subscriber.column_for_attribute("nick").null end def test_human_name_for_column assert_equal "Author name", @first.column_for_attribute("author_name").human_name end def test_integer_columns assert_equal :integer, @first.column_for_attribute("id").type end def test_non_existent_columns_return_nil assert_deprecated do assert_nil @first.column_for_attribute("attribute_that_doesnt_exist") end end def test_reflection_klass_for_nested_class_name reflection = ActiveRecord::Reflection.create(:has_many, nil, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base) assert_nothing_raised do assert_equal MyApplication::Business::Company, reflection.klass end end def test_irregular_reflection_class_name ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'plural_irregular', 'plurales_irregulares' end reflection = ActiveRecord::Reflection.create(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base) assert_equal 'PluralIrregular', reflection.class_name end def test_aggregation_reflection reflection_for_address = AggregateReflection.new( :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer ) reflection_for_balance = AggregateReflection.new( :balance, nil, { :class_name => "Money", :mapping => %w(balance amount) }, Customer ) reflection_for_gps_location = AggregateReflection.new( :gps_location, nil, { }, Customer ) assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location) assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance) assert Customer.reflect_on_all_aggregations.include?(reflection_for_address) assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address) assert_equal Address, Customer.reflect_on_aggregation(:address).klass assert_equal Money, Customer.reflect_on_aggregation(:balance).klass end def test_reflect_on_all_autosave_associations expected = Pirate.reflect_on_all_associations.select { |r| r.options[:autosave] } received = Pirate.reflect_on_all_autosave_associations assert !received.empty? assert_not_equal Pirate.reflect_on_all_associations.length, received.length assert_equal expected, received end def test_has_many_reflection reflection_for_clients = ActiveRecord::Reflection.create(:has_many, :clients, nil, { :order => "id", :dependent => :destroy }, Firm) assert_equal reflection_for_clients, Firm.reflect_on_association(:clients) assert_equal Client, Firm.reflect_on_association(:clients).klass assert_equal 'companies', Firm.reflect_on_association(:clients).table_name assert_equal Client, Firm.reflect_on_association(:clients_of_firm).klass assert_equal 'companies', Firm.reflect_on_association(:clients_of_firm).table_name end def test_has_one_reflection reflection_for_account = ActiveRecord::Reflection.create(:has_one, :account, nil, { :foreign_key => "firm_id", :dependent => :destroy }, Firm) assert_equal reflection_for_account, Firm.reflect_on_association(:account) assert_equal Account, Firm.reflect_on_association(:account).klass assert_equal 'accounts', Firm.reflect_on_association(:account).table_name end def test_belongs_to_inferred_foreign_key_from_assoc_name Company.belongs_to :foo assert_equal "foo_id", Company.reflect_on_association(:foo).foreign_key Company.belongs_to :bar, :class_name => "Xyzzy" assert_equal "bar_id", Company.reflect_on_association(:bar).foreign_key Company.belongs_to :baz, :class_name => "Xyzzy", :foreign_key => "xyzzy_id" assert_equal "xyzzy_id", Company.reflect_on_association(:baz).foreign_key end def test_association_reflection_in_modules ActiveRecord::Base.store_full_sti_class = false assert_reflection MyApplication::Business::Firm, :clients_of_firm, :klass => MyApplication::Business::Client, :class_name => 'Client', :table_name => 'companies' assert_reflection MyApplication::Billing::Account, :firm, :klass => MyApplication::Business::Firm, :class_name => 'MyApplication::Business::Firm', :table_name => 'companies' assert_reflection MyApplication::Billing::Account, :qualified_billing_firm, :klass => MyApplication::Billing::Firm, :class_name => 'MyApplication::Billing::Firm', :table_name => 'companies' assert_reflection MyApplication::Billing::Account, :unqualified_billing_firm, :klass => MyApplication::Billing::Firm, :class_name => 'Firm', :table_name => 'companies' assert_reflection MyApplication::Billing::Account, :nested_qualified_billing_firm, :klass => MyApplication::Billing::Nested::Firm, :class_name => 'MyApplication::Billing::Nested::Firm', :table_name => 'companies' assert_reflection MyApplication::Billing::Account, :nested_unqualified_billing_firm, :klass => MyApplication::Billing::Nested::Firm, :class_name => 'Nested::Firm', :table_name => 'companies' ensure ActiveRecord::Base.store_full_sti_class = true end def test_reflection_should_not_raise_error_when_compared_to_other_object assert_not_equal Object.new, Firm._reflections['clients'] end def test_reflections_should_return_keys_as_strings assert Category.reflections.keys.all? { |key| key.is_a? String }, "Model.reflections is expected to return string for keys" end def test_has_and_belongs_to_many_reflection assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name end def test_has_many_through_reflection assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books) end def test_chain expected = [ Organization.reflect_on_association(:author_essay_categories), Author.reflect_on_association(:essays), Organization.reflect_on_association(:authors) ] actual = Organization.reflect_on_association(:author_essay_categories).chain assert_equal expected, actual end def test_scope_chain expected = [ [Tagging.reflect_on_association(:tag).scope, Post.reflect_on_association(:first_blue_tags).scope], [Post.reflect_on_association(:first_taggings).scope], [Author.reflect_on_association(:misc_posts).scope] ] actual = Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain assert_equal expected, actual expected = [ [ Tagging.reflect_on_association(:blue_tag).scope, Post.reflect_on_association(:first_blue_tags_2).scope, Author.reflect_on_association(:misc_post_first_blue_tags_2).scope ], [], [] ] actual = Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain assert_equal expected, actual end def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case @hotel = Hotel.create! @department = @hotel.departments.create! @department.chefs.create!(employable: CakeDesigner.create!) @department.chefs.create!(employable: DrinkDesigner.create!) assert_equal 1, @hotel.cake_designers.size assert_equal 1, @hotel.drink_designers.size assert_equal 2, @hotel.chefs.size end def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_sti @hotel = Hotel.create! @hotel.mocktail_designers << MocktailDesigner.create! assert_equal 1, @hotel.mocktail_designers.size assert_equal 1, @hotel.chef_lists.size end def test_nested? assert !Author.reflect_on_association(:comments).nested? assert Author.reflect_on_association(:tags).nested? # Only goes :through once, but the through_reflection is a has_and_belongs_to_many, so this is # a nested through association assert Category.reflect_on_association(:post_comments).nested? end def test_association_primary_key # Normal association assert_equal "id", Author.reflect_on_association(:posts).association_primary_key.to_s assert_equal "name", Author.reflect_on_association(:essay).association_primary_key.to_s assert_equal "name", Essay.reflect_on_association(:writer).association_primary_key.to_s # Through association (uses the :primary_key option from the source reflection) assert_equal "nick", Author.reflect_on_association(:subscribers).association_primary_key.to_s assert_equal "name", Author.reflect_on_association(:essay_category).association_primary_key.to_s assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested end def test_association_primary_key_raises_when_missing_primary_key reflection = ActiveRecord::Reflection.create(:has_many, :edge, nil, {}, Author) assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key } through = Class.new(ActiveRecord::Reflection::ThroughReflection) { define_method(:source_reflection) { reflection } }.new(reflection) assert_raises(ActiveRecord::UnknownPrimaryKey) { through.association_primary_key } end def test_active_record_primary_key assert_equal "nick", Subscriber.reflect_on_association(:subscriptions).active_record_primary_key.to_s assert_equal "name", Author.reflect_on_association(:essay).active_record_primary_key.to_s end def test_active_record_primary_key_raises_when_missing_primary_key reflection = ActiveRecord::Reflection.create(:has_many, :author, nil, {}, Edge) assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.active_record_primary_key } end def test_foreign_type assert_equal "sponsorable_type", Sponsor.reflect_on_association(:sponsorable).foreign_type.to_s assert_equal "sponsorable_type", Sponsor.reflect_on_association(:thing).foreign_type.to_s end def test_collection_association assert Pirate.reflect_on_association(:birds).collection? assert Pirate.reflect_on_association(:parrots).collection? assert !Pirate.reflect_on_association(:ship).collection? assert !Ship.reflect_on_association(:pirate).collection? end def test_default_association_validation assert ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm).validate? assert !ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm).validate? assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm).validate? end def test_always_validate_association_if_explicit assert ActiveRecord::Reflection.create(:has_one, :client, nil, { :validate => true }, Firm).validate? assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :validate => true }, Firm).validate? assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { :validate => true }, Firm).validate? end def test_validate_association_if_autosave assert ActiveRecord::Reflection.create(:has_one, :client, nil, { :autosave => true }, Firm).validate? assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :autosave => true }, Firm).validate? assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { :autosave => true }, Firm).validate? end def test_never_validate_association_if_explicit assert !ActiveRecord::Reflection.create(:has_one, :client, nil, { :autosave => true, :validate => false }, Firm).validate? assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :autosave => true, :validate => false }, Firm).validate? assert !ActiveRecord::Reflection.create(:has_many, :clients, nil, { :autosave => true, :validate => false }, Firm).validate? end def test_foreign_key assert_equal "author_id", Author.reflect_on_association(:posts).foreign_key.to_s assert_equal "category_id", Post.reflect_on_association(:categorizations).foreign_key.to_s end def test_through_reflection_scope_chain_does_not_modify_other_reflections orig_conds = Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain assert_equal orig_conds, Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect end def test_symbol_for_class_name assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass end def test_join_table category = Struct.new(:table_name, :pluralize_table_names).new('categories', true) product = Struct.new(:table_name, :pluralize_table_names).new('products', true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product) reflection.stubs(:klass).returns(category) assert_equal 'categories_products', reflection.join_table reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category) reflection.stubs(:klass).returns(product) assert_equal 'categories_products', reflection.join_table end def test_join_table_with_common_prefix category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true) product = Struct.new(:table_name, :pluralize_table_names).new('catalog_products', true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product) reflection.stubs(:klass).returns(category) assert_equal 'catalog_categories_products', reflection.join_table reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category) reflection.stubs(:klass).returns(product) assert_equal 'catalog_categories_products', reflection.join_table end def test_join_table_with_different_prefix category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true) page = Struct.new(:table_name, :pluralize_table_names).new('content_pages', true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, page) reflection.stubs(:klass).returns(category) assert_equal 'catalog_categories_content_pages', reflection.join_table reflection = ActiveRecord::Reflection.create(:has_many, :pages, nil, {}, category) reflection.stubs(:klass).returns(page) assert_equal 'catalog_categories_content_pages', reflection.join_table end def test_join_table_can_be_overridden category = Struct.new(:table_name, :pluralize_table_names).new('categories', true) product = Struct.new(:table_name, :pluralize_table_names).new('products', true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, { :join_table => 'product_categories' }, product) reflection.stubs(:klass).returns(category) assert_equal 'product_categories', reflection.join_table reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, { :join_table => 'product_categories' }, category) reflection.stubs(:klass).returns(product) assert_equal 'product_categories', reflection.join_table end def test_includes_accepts_symbols hotel = Hotel.create! department = hotel.departments.create! department.chefs.create! assert_nothing_raised do assert_equal department.chefs, Hotel.includes([departments: :chefs]).first.chefs end end def test_includes_accepts_strings hotel = Hotel.create! department = hotel.departments.create! department.chefs.create! assert_nothing_raised do assert_equal department.chefs, Hotel.includes(['departments' => 'chefs']).first.chefs end end def test_reflect_on_association_accepts_symbols assert_nothing_raised do assert_equal Hotel.reflect_on_association(:departments).name, :departments end end def test_reflect_on_association_accepts_strings assert_nothing_raised do assert_equal Hotel.reflect_on_association("departments").name, :departments end end private def assert_reflection(klass, association, options) assert reflection = klass.reflect_on_association(association) options.each do |method, value| assert_equal(value, reflection.send(method)) end end end rails-4.2.6/activerecord/test/cases/relation/000077500000000000000000000000001266740050600212075ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/relation/delegation_test.rb000066400000000000000000000033061266740050600247100ustar00rootroot00000000000000require 'cases/helper' require 'models/post' require 'models/comment' module ActiveRecord class DelegationTest < ActiveRecord::TestCase fixtures :posts def call_method(target, method) method_arity = target.to_a.method(method).arity if method_arity.zero? target.public_send(method) elsif method_arity < 0 if method == :shuffle! target.public_send(method) else target.public_send(method, 1) end elsif method_arity == 1 target.public_send(method, 1) else raise NotImplementedError end end end module DelegationWhitelistBlacklistTests ARRAY_DELEGATES = [ :+, :-, :|, :&, :[], :all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index, :exclude?, :find_all, :flat_map, :group_by, :include?, :length, :map, :none?, :one?, :partition, :reject, :reverse, :sample, :second, :sort, :sort_by, :third, :to_ary, :to_set, :to_xml, :to_yaml, :join ] ARRAY_DELEGATES.each do |method| define_method "test_delegates_#{method}_to_Array" do assert_respond_to target, method end end ActiveRecord::Delegation::BLACKLISTED_ARRAY_METHODS.each do |method| define_method "test_#{method}_is_not_delegated_to_Array" do assert_raises(NoMethodError) { call_method(target, method) } end end end class DelegationAssociationTest < DelegationTest include DelegationWhitelistBlacklistTests def target Post.first.comments end end class DelegationRelationTest < DelegationTest include DelegationWhitelistBlacklistTests fixtures :comments def target Comment.all end end end rails-4.2.6/activerecord/test/cases/relation/merging_test.rb000066400000000000000000000125331266740050600242270ustar00rootroot00000000000000require 'cases/helper' require 'models/author' require 'models/comment' require 'models/developer' require 'models/computer' require 'models/post' require 'models/project' require 'models/rating' class RelationMergingTest < ActiveRecord::TestCase fixtures :developers, :comments, :authors, :posts def test_relation_merging devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order('id ASC').where("id < 3")) assert_equal [developers(:david), developers(:jamis)], devs.to_a dev_with_count = Developer.limit(1).merge(Developer.order('id DESC')).merge(Developer.select('developers.*')) assert_equal [developers(:poor_jamis)], dev_with_count.to_a end def test_relation_to_sql post = Post.first sql = post.comments.to_sql assert_match(/.?post_id.? = #{post.id}\Z/i, sql) end def test_relation_merging_with_arel_equalities_keeps_last_equality devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge( Developer.where(Developer.arel_table[:salary].eq(9000)) ) assert_equal [developers(:poor_jamis)], devs.to_a end def test_relation_merging_with_arel_equalities_keeps_last_equality_with_non_attribute_left_hand salary_attr = Developer.arel_table[:salary] devs = Developer.where( Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(80000) ).merge( Developer.where( Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(9000) ) ) assert_equal [developers(:poor_jamis)], devs.to_a end def test_relation_merging_with_eager_load relations = [] relations << Post.order('comments.id DESC').merge(Post.eager_load(:last_comment)).merge(Post.all) relations << Post.eager_load(:last_comment).merge(Post.order('comments.id DESC')).merge(Post.all) relations.each do |posts| post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end end def test_relation_merging_with_locks devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2)) assert devs.locked.present? end def test_relation_merging_with_preload [Post.all.merge(Post.preload(:author)), Post.preload(:author).merge(Post.all)].each do |posts| assert_queries(2) { assert posts.first.author } end end def test_relation_merging_with_joins comments = Comment.joins(:post).where(:body => 'Thank you for the welcome').merge(Post.where(:body => 'Such a lovely day')) assert_equal 1, comments.count end def test_relation_merging_with_association assert_queries(2) do # one for loading post, and another one merged query post = Post.where(:body => 'Such a lovely day').first comments = Comment.where(:body => 'Thank you for the welcome').merge(post.comments) assert_equal 1, comments.count end end test "merge collapses wheres from the LHS only" do left = Post.where(title: "omg").where(comments_count: 1) right = Post.where(title: "wtf").where(title: "bbq") expected = [left.bind_values[1]] + right.bind_values merged = left.merge(right) assert_equal expected, merged.bind_values assert !merged.to_sql.include?("omg") assert merged.to_sql.include?("wtf") assert merged.to_sql.include?("bbq") end def test_merging_keeps_lhs_bind_parameters column = Post.columns_hash['id'] binds = [[column, 20]] right = Post.where(id: 20) left = Post.where(id: 10) merged = left.merge(right) assert_equal binds, merged.bind_values end def test_merging_reorders_bind_params post = Post.first right = Post.where(id: 1) left = Post.where(title: post.title) merged = left.merge(right) assert_equal post, merged.first end def test_merging_compares_symbols_and_strings_as_equal post = PostThatLoadsCommentsInAnAfterSaveHook.create!(title: "First Post", body: "Blah blah blah.") assert_equal "First comment!", post.comments.where(body: "First comment!").first_or_create.body end end class MergingDifferentRelationsTest < ActiveRecord::TestCase fixtures :posts, :authors, :developers test "merging where relations" do hello_by_bob = Post.where(body: "hello").joins(:author). merge(Author.where(name: "Bob")).order("posts.id").pluck("posts.id") assert_equal [posts(:misc_by_bob).id, posts(:other_by_bob).id], hello_by_bob end test "merging order relations" do posts_by_author_name = Post.limit(3).joins(:author). merge(Author.order(:name)).pluck("authors.name") assert_equal ["Bob", "Bob", "David"], posts_by_author_name posts_by_author_name = Post.limit(3).joins(:author). merge(Author.order("name")).pluck("authors.name") assert_equal ["Bob", "Bob", "David"], posts_by_author_name end test "merging order relations (using a hash argument)" do posts_by_author_name = Post.limit(4).joins(:author). merge(Author.order(name: :desc)).pluck("authors.name") assert_equal ["Mary", "Mary", "Mary", "David"], posts_by_author_name end test "relation merging (using a proc argument)" do dev = Developer.where(name: "Jamis").first comment_1 = dev.comments.create!(body: "I'm Jamis", post: Post.first) rating_1 = comment_1.ratings.create! comment_2 = dev.comments.create!(body: "I'm John", post: Post.first) comment_2.ratings.create! assert_equal dev.ratings, [rating_1] end end rails-4.2.6/activerecord/test/cases/relation/mutation_test.rb000066400000000000000000000114031266740050600244320ustar00rootroot00000000000000require 'cases/helper' require 'models/post' module ActiveRecord class RelationMutationTest < ActiveSupport::TestCase class FakeKlass < Struct.new(:table_name, :name) extend ActiveRecord::Delegation::DelegateCache inherited self def connection Post.connection end def relation_delegate_class(klass) self.class.relation_delegate_class(klass) end def attribute_alias?(name) false end def sanitize_sql(sql) sql end end def relation @relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table end (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal [:foo], relation.public_send("#{method}_values") end end test "#_select!" do assert relation.public_send("_select!", :foo).equal?(relation) assert_equal [:foo], relation.public_send("select_values") end test '#order!' do assert relation.order!('name ASC').equal?(relation) assert_equal ['name ASC'], relation.order_values end test '#order! with symbol prepends the table name' do assert relation.order!(:name).equal?(relation) node = relation.order_values.first assert node.ascending? assert_equal :name, node.expr.name assert_equal "posts", node.expr.relation.name end test '#order! on non-string does not attempt regexp match for references' do obj = Object.new obj.expects(:=~).never assert relation.order!(obj) assert_equal [obj], relation.order_values end test '#references!' do assert relation.references!(:foo).equal?(relation) assert relation.references_values.include?('foo') end test 'extending!' do mod, mod2 = Module.new, Module.new assert relation.extending!(mod).equal?(relation) assert_equal [mod], relation.extending_values assert relation.is_a?(mod) relation.extending!(mod2) assert_equal [mod, mod2], relation.extending_values end test 'extending! with empty args' do relation.extending! assert_equal [], relation.extending_values end (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal :foo, relation.public_send("#{method}_value") end end test '#from!' do assert relation.from!('foo').equal?(relation) assert_equal ['foo', nil], relation.from_value end test '#lock!' do assert relation.lock!('foo').equal?(relation) assert_equal 'foo', relation.lock_value end test '#reorder!' do order_relation = self.relation.order('foo') assert order_relation.reorder!('bar').equal?(order_relation) assert_equal ['bar'], order_relation.order_values assert order_relation.reordering_value end test '#reorder! with symbol prepends the table name' do assert relation.reorder!(:name).equal?(relation) node = relation.order_values.first assert node.ascending? assert_equal :name, node.expr.name assert_equal "posts", node.expr.relation.name end test 'reverse_order!' do order_relation = Post.order('title ASC, comments_count DESC') order_relation.reverse_order! assert_equal 'title DESC', order_relation.order_values.first assert_equal 'comments_count ASC', order_relation.order_values.last order_relation.reverse_order! assert_equal 'title ASC', order_relation.order_values.first assert_equal 'comments_count DESC', order_relation.order_values.last end test 'create_with!' do assert relation.create_with!(foo: 'bar').equal?(relation) assert_equal({foo: 'bar'}, relation.create_with_value) end test 'test_merge!' do assert relation.merge!(where: :foo).equal?(relation) assert_equal [:foo], relation.where_values end test 'merge with a proc' do assert_equal [:foo], relation.merge(-> { where(:foo) }).where_values end test 'none!' do assert relation.none!.equal?(relation) assert_equal [NullRelation], relation.extending_values assert relation.is_a?(NullRelation) end test 'distinct!' do relation.distinct! :foo assert_equal :foo, relation.distinct_value assert_equal :foo, relation.uniq_value # deprecated access end test 'uniq! was replaced by distinct!' do relation.uniq! :foo assert_equal :foo, relation.distinct_value assert_equal :foo, relation.uniq_value # deprecated access end end end rails-4.2.6/activerecord/test/cases/relation/predicate_builder_test.rb000066400000000000000000000006631266740050600262460ustar00rootroot00000000000000require "cases/helper" require 'models/topic' module ActiveRecord class PredicateBuilderTest < ActiveRecord::TestCase def test_registering_new_handlers PredicateBuilder.register_handler(Regexp, proc do |column, value| Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source)) end) assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql end end end rails-4.2.6/activerecord/test/cases/relation/where_chain_test.rb000066400000000000000000000136341266740050600250560ustar00rootroot00000000000000require 'cases/helper' require 'models/post' require 'models/comment' module ActiveRecord class WhereChainTest < ActiveRecord::TestCase fixtures :posts def setup super @name = 'title' end def test_not_eq relation = Post.where.not(title: 'hello') assert_equal 1, relation.where_values.length value = relation.where_values.first bind = relation.bind_values.first assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual assert_equal 'hello', bind.last end def test_not_null expected = Post.arel_table[@name].not_eq(nil) relation = Post.where.not(title: nil) assert_equal([expected], relation.where_values) end def test_not_with_nil assert_raise ArgumentError do Post.where.not(nil) end end def test_not_in expected = Post.arel_table[@name].not_in(%w[hello goodbye]) relation = Post.where.not(title: %w[hello goodbye]) assert_equal([expected], relation.where_values) end def test_association_not_eq expected = Comment.arel_table[@name].not_eq('hello') relation = Post.joins(:comments).where.not(comments: {title: 'hello'}) assert_equal(expected.to_sql, relation.where_values.first.to_sql) end def test_not_eq_with_preceding_where relation = Post.where(title: 'hello').where.not(title: 'world') value = relation.where_values.first bind = relation.bind_values.first assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality assert_equal 'hello', bind.last value = relation.where_values.last bind = relation.bind_values.last assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual assert_equal 'world', bind.last end def test_not_eq_with_succeeding_where relation = Post.where.not(title: 'hello').where(title: 'world') value = relation.where_values.first bind = relation.bind_values.first assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual assert_equal 'hello', bind.last value = relation.where_values.last bind = relation.bind_values.last assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality assert_equal 'world', bind.last end def test_not_eq_with_string_parameter expected = Arel::Nodes::Not.new("title = 'hello'") relation = Post.where.not("title = 'hello'") assert_equal([expected], relation.where_values) end def test_not_eq_with_array_parameter expected = Arel::Nodes::Not.new("title = 'hello'") relation = Post.where.not(['title = ?', 'hello']) assert_equal([expected], relation.where_values) end def test_chaining_multiple relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails') expected = Post.arel_table['author_id'].not_in([1, 2]) assert_equal(expected, relation.where_values[0]) value = relation.where_values[1] bind = relation.bind_values.first assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual assert_equal 'ruby on rails', bind.last end def test_rewhere_with_one_condition relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone') assert_equal 1, relation.where_values.size value = relation.where_values.first bind = relation.bind_values.first assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality assert_equal 'alone', bind.last end def test_rewhere_with_multiple_overwriting_conditions relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again') assert_equal 2, relation.where_values.size value = relation.where_values.first bind = relation.bind_values.first assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality assert_equal 'alone', bind.last value = relation.where_values[1] bind = relation.bind_values[1] assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality assert_equal 'again', bind.last end def assert_bound_ast value, table, type assert_equal table, value.left assert_kind_of type, value assert_kind_of Arel::Nodes::BindParam, value.right end def test_rewhere_with_one_overwriting_condition_and_one_unrelated relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone') assert_equal 2, relation.where_values.size value = relation.where_values.first bind = relation.bind_values.first assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality assert_equal 'world', bind.last value = relation.where_values.second bind = relation.bind_values.second assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality assert_equal 'alone', bind.last end def test_rewhere_with_range relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5) assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_upper_bound_range relation = Post.where(comments_count: 1..Float::INFINITY).rewhere(comments_count: 3..5) assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_lower_bound_range relation = Post.where(comments_count: -Float::INFINITY..1).rewhere(comments_count: 3..5) assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_range relation = Post.where(comments_count: -Float::INFINITY..Float::INFINITY).rewhere(comments_count: 3..5) assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end end end rails-4.2.6/activerecord/test/cases/relation/where_test.rb000066400000000000000000000211421266740050600237050ustar00rootroot00000000000000require "cases/helper" require "models/author" require "models/binary" require "models/cake_designer" require "models/category" require "models/chef" require "models/comment" require "models/edge" require "models/essay" require "models/post" require "models/price_estimate" require "models/topic" require "models/treasure" require "models/vertex" module ActiveRecord class WhereTest < ActiveRecord::TestCase fixtures :posts, :edges, :authors, :binaries, :essays def test_where_copies_bind_params author = authors(:david) posts = author.posts.where('posts.id != 1') joined = Post.where(id: posts) assert_operator joined.length, :>, 0 joined.each { |post| assert_equal author, post.author assert_not_equal 1, post.id } end def test_where_copies_arel_bind_params chef = Chef.create! CakeDesigner.create!(chef: chef) cake_designers = CakeDesigner.joins(:chef).where(chefs: { id: chef.id }) chefs = Chef.where(employable: cake_designers) assert_equal [chef], chefs.to_a end def test_rewhere_on_root assert_equal posts(:welcome), Post.rewhere(title: 'Welcome to the weblog').first end def test_belongs_to_shallow_where author = Author.new author.id = 1 assert_equal Post.where(author_id: 1).to_sql, Post.where(author: author).to_sql end def test_belongs_to_nil_where assert_equal Post.where(author_id: nil).to_sql, Post.where(author: nil).to_sql end def test_belongs_to_array_value_where assert_equal Post.where(author_id: [1,2]).to_sql, Post.where(author: [1,2]).to_sql end def test_belongs_to_nested_relation_where expected = Post.where(author_id: Author.where(id: [1,2])).to_sql actual = Post.where(author: Author.where(id: [1,2])).to_sql assert_equal expected, actual end def test_belongs_to_nested_where parent = Comment.new parent.id = 1 expected = Post.where(comments: { parent_id: 1 }).joins(:comments) actual = Post.where(comments: { parent: parent }).joins(:comments) assert_equal expected.to_sql, actual.to_sql end def test_belongs_to_nested_where_with_relation author = authors(:david) expected = Author.where(id: author ).joins(:posts) actual = Author.where(posts: { author_id: Author.where(id: author.id) }).joins(:posts) assert_equal expected.to_a, actual.to_a end def test_polymorphic_shallow_where treasure = Treasure.new treasure.id = 1 expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1) actual = PriceEstimate.where(estimate_of: treasure) assert_equal expected.to_sql, actual.to_sql end def test_polymorphic_nested_array_where treasure = Treasure.new treasure.id = 1 hidden = HiddenTreasure.new hidden.id = 2 expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: [treasure, hidden]) actual = PriceEstimate.where(estimate_of: [treasure, hidden]) assert_equal expected.to_sql, actual.to_sql end def test_polymorphic_empty_array_where treasure = Treasure.new treasure.id = 1 hidden = HiddenTreasure.new hidden.id = 2 expected = PriceEstimate.where("1=0") actual = PriceEstimate.where(estimate_of: []) assert_equal expected.to_a, actual.to_a end def test_polymorphic_nested_relation_where expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: Treasure.where(id: [1,2])) actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1,2])) assert_equal expected.to_sql, actual.to_sql end def test_polymorphic_sti_shallow_where treasure = HiddenTreasure.new treasure.id = 1 expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1) actual = PriceEstimate.where(estimate_of: treasure) assert_equal expected.to_sql, actual.to_sql end def test_polymorphic_nested_where thing = Post.new thing.id = 1 expected = Treasure.where(price_estimates: { thing_type: 'Post', thing_id: 1 }).joins(:price_estimates) actual = Treasure.where(price_estimates: { thing: thing }).joins(:price_estimates) assert_equal expected.to_sql, actual.to_sql end def test_polymorphic_sti_nested_where treasure = HiddenTreasure.new treasure.id = 1 expected = Treasure.where(price_estimates: { estimate_of_type: 'Treasure', estimate_of_id: 1 }).joins(:price_estimates) actual = Treasure.where(price_estimates: { estimate_of: treasure }).joins(:price_estimates) assert_equal expected.to_sql, actual.to_sql end def test_decorated_polymorphic_where treasure_decorator = Struct.new(:model) do def self.method_missing(method, *args, &block) Treasure.send(method, *args, &block) end def is_a?(klass) model.is_a?(klass) end def method_missing(method, *args, &block) model.send(method, *args, &block) end end treasure = Treasure.new treasure.id = 1 decorated_treasure = treasure_decorator.new(treasure) expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1) actual = PriceEstimate.where(estimate_of: decorated_treasure) assert_equal expected.to_sql, actual.to_sql end def test_aliased_attribute expected = Topic.where(heading: 'The First Topic') actual = Topic.where(title: 'The First Topic') assert_equal expected.to_sql, actual.to_sql end def test_where_error assert_raises(ActiveRecord::StatementInvalid) do Post.where(:id => { 'posts.author_id' => 10 }).first end end def test_where_with_table_name post = Post.first assert_equal post, Post.where(:posts => { 'id' => post.id }).first end def test_where_with_table_name_and_empty_hash assert_equal 0, Post.where(:posts => {}).count end def test_where_with_table_name_and_empty_array assert_equal 0, Post.where(:id => []).count end def test_where_with_table_name_and_nested_empty_array assert_deprecated do assert_equal [], Post.where(:id => [[]]).to_a end end def test_where_with_empty_hash_and_no_foreign_key assert_equal 0, Edge.where(:sink => {}).count end def test_where_with_blank_conditions [[], {}, nil, ""].each do |blank| assert_equal 4, Edge.where(blank).order("sink_id").to_a.size end end def test_where_with_integer_for_string_column count = Post.where(:title => 0).count assert_equal 0, count end def test_where_with_float_for_string_column count = Post.where(:title => 0.0).count assert_equal 0, count end def test_where_with_boolean_for_string_column count = Post.where(:title => false).count assert_equal 0, count end def test_where_with_decimal_for_string_column count = Post.where(:title => BigDecimal.new(0)).count assert_equal 0, count end def test_where_with_duration_for_string_column count = Post.where(:title => 0.seconds).count assert_equal 0, count end def test_where_with_integer_for_binary_column count = Binary.where(:data => 0).count assert_equal 0, count end def test_where_on_association_with_custom_primary_key author = authors(:david) essay = Essay.where(writer: author).first assert_equal essays(:david_modest_proposal), essay end def test_where_on_association_with_custom_primary_key_with_relation author = authors(:david) essay = Essay.where(writer: Author.where(id: author.id)).first assert_equal essays(:david_modest_proposal), essay end def test_where_on_association_with_relation_performs_subselect_not_two_queries author = authors(:david) assert_queries(1) do Essay.where(writer: Author.where(id: author.id)).to_a end end def test_where_on_association_with_custom_primary_key_with_array_of_base author = authors(:david) essay = Essay.where(writer: [author]).first assert_equal essays(:david_modest_proposal), essay end def test_where_on_association_with_custom_primary_key_with_array_of_ids essay = Essay.where(writer: ["David"]).first assert_equal essays(:david_modest_proposal), essay end def test_where_on_non_polymorphic_association_with_custom_primary_key essay = Essay.where(category: [Category.new(name: "General")]).first assert_equal essays(:david_modest_proposal), essay end end end rails-4.2.6/activerecord/test/cases/relation_test.rb000066400000000000000000000245341266740050600226030ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/comment' require 'models/author' require 'models/rating' module ActiveRecord class RelationTest < ActiveRecord::TestCase fixtures :posts, :comments, :authors class FakeKlass < Struct.new(:table_name, :name) extend ActiveRecord::Delegation::DelegateCache inherited self def self.connection Post.connection end def self.table_name 'fake_table' end end def test_construction relation = Relation.new FakeKlass, :b assert_equal FakeKlass, relation.klass assert_equal :b, relation.table assert !relation.loaded, 'relation is not loaded' end def test_responds_to_model_and_returns_klass relation = Relation.new FakeKlass, :b assert_equal FakeKlass, relation.model end def test_initialize_single_values relation = Relation.new FakeKlass, :b (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method| assert_nil relation.send("#{method}_value"), method.to_s end assert_equal({}, relation.create_with_value) end def test_multi_value_initialize relation = Relation.new FakeKlass, :b Relation::MULTI_VALUE_METHODS.each do |method| assert_equal [], relation.send("#{method}_values"), method.to_s end end def test_extensions relation = Relation.new FakeKlass, :b assert_equal [], relation.extensions end def test_empty_where_values_hash relation = Relation.new FakeKlass, :b assert_equal({}, relation.where_values_hash) relation.where! :hello assert_equal({}, relation.where_values_hash) end def test_has_values relation = Relation.new Post, Post.arel_table relation.where! relation.table[:id].eq(10) assert_equal({:id => 10}, relation.where_values_hash) end def test_values_wrong_table relation = Relation.new Post, Post.arel_table relation.where! Comment.arel_table[:id].eq(10) assert_equal({}, relation.where_values_hash) end def test_tree_is_not_traversed relation = Relation.new Post, Post.arel_table left = relation.table[:id].eq(10) right = relation.table[:id].eq(10) combine = left.and right relation.where! combine assert_equal({}, relation.where_values_hash) end def test_table_name_delegates_to_klass relation = Relation.new FakeKlass.new('posts'), :b assert_equal 'posts', relation.table_name end def test_scope_for_create relation = Relation.new FakeKlass, :b assert_equal({}, relation.scope_for_create) end def test_create_with_value relation = Relation.new Post, Post.arel_table hash = { :hello => 'world' } relation.create_with_value = hash assert_equal hash, relation.scope_for_create end def test_create_with_value_with_wheres relation = Relation.new Post, Post.arel_table relation.where! relation.table[:id].eq(10) relation.create_with_value = {:hello => 'world'} assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create) end # FIXME: is this really wanted or expected behavior? def test_scope_for_create_is_cached relation = Relation.new Post, Post.arel_table assert_equal({}, relation.scope_for_create) relation.where! relation.table[:id].eq(10) assert_equal({}, relation.scope_for_create) relation.create_with_value = {:hello => 'world'} assert_equal({}, relation.scope_for_create) end def test_bad_constants_raise_errors assert_raises(NameError) do ActiveRecord::Relation::HelloWorld end end def test_empty_eager_loading? relation = Relation.new FakeKlass, :b assert !relation.eager_loading? end def test_eager_load_values relation = Relation.new FakeKlass, :b relation.eager_load! :b assert relation.eager_loading? end def test_references_values relation = Relation.new FakeKlass, :b assert_equal [], relation.references_values relation = relation.references(:foo).references(:omg, :lol) assert_equal ['foo', 'omg', 'lol'], relation.references_values end def test_references_values_dont_duplicate relation = Relation.new FakeKlass, :b relation = relation.references(:foo).references(:foo) assert_equal ['foo'], relation.references_values end test 'merging a hash into a relation' do relation = Relation.new FakeKlass, :b relation = relation.merge where: :lol, readonly: true assert_equal [:lol], relation.where_values assert_equal true, relation.readonly_value end test 'merging an empty hash into a relation' do assert_equal [], Relation.new(FakeKlass, :b).merge({}).where_values end test 'merging a hash with unknown keys raises' do assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') } end test '#values returns a dup of the values' do relation = Relation.new(FakeKlass, :b).where! :foo values = relation.values values[:where] = nil assert_not_nil relation.where_values end test 'relations can be created with a values hash' do relation = Relation.new(FakeKlass, :b, where: [:foo]) assert_equal [:foo], relation.where_values end test 'merging a single where value' do relation = Relation.new(FakeKlass, :b) relation.merge!(where: :foo) assert_equal [:foo], relation.where_values end test 'merging a hash interpolates conditions' do klass = Class.new(FakeKlass) do def self.sanitize_sql(args) raise unless args == ['foo = ?', 'bar'] 'foo = bar' end end relation = Relation.new(klass, :b) relation.merge!(where: ['foo = ?', 'bar']) assert_equal ['foo = bar'], relation.where_values end def test_merging_readonly_false relation = Relation.new FakeKlass, :b readonly_false_relation = relation.readonly(false) # test merging in both directions assert_equal false, relation.merge(readonly_false_relation).readonly_value assert_equal false, readonly_false_relation.merge(relation).readonly_value end def test_relation_merging_with_merged_joins_as_symbols special_comments_with_ratings = SpecialComment.joins(:ratings) posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length end def test_conflicting_bind_values assert_nothing_raised do CommentWithConflictingDefaultScope.joins(:post_with_conflicting_default_scope).delete_all end end def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent post = Post.create!(title: "haha", body: "huhu") comment = post.comments.create!(body: "hu") 3.times { comment.ratings.create! } relation = Post.joins(:comments).merge Comment.joins(:ratings) assert_equal 3, relation.where(id: post.id).pluck(:id).size end def test_respond_to_for_non_selected_element post = Post.select(:title).first assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception" silence_warnings { post = Post.select("'title' as post_title").first } assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception" end def test_select_quotes_when_using_from_clause skip_if_sqlite3_version_includes_quoting_bug quoted_join = ActiveRecord::Base.connection.quote_table_name("join") selected = Post.select(:join).from(Post.select("id as #{quoted_join}")).map(&:join) assert_equal Post.pluck(:id), selected end def test_selecting_aliased_attribute_quotes_column_name_when_from_is_used skip_if_sqlite3_version_includes_quoting_bug klass = Class.new(ActiveRecord::Base) do self.table_name = :test_with_keyword_column_name alias_attribute :description, :desc end klass.create!(description: "foo") assert_equal ["foo"], klass.select(:description).from(klass.all).map(&:desc) end def test_relation_merging_with_merged_joins_as_strings join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id" special_comments_with_ratings = SpecialComment.joins join_string posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length end def test_merge_raises_with_invalid_argument assert_raises ArgumentError do relation = Relation.new(FakeKlass, :b) relation.merge(true) end end class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value def type :string end def type_cast_from_database(value) raise value unless value == "type cast for database" "type cast from database" end def type_cast_for_database(value) raise value unless value == "value from user" "type cast for database" end end class UpdateAllTestModel < ActiveRecord::Base self.table_name = 'posts' attribute :body, EnsureRoundTripTypeCasting.new end def test_update_all_goes_through_normal_type_casting UpdateAllTestModel.update_all(body: "value from user", type: nil) # No STI assert_equal "type cast from database", UpdateAllTestModel.first.body end private def skip_if_sqlite3_version_includes_quoting_bug if sqlite3_version_includes_quoting_bug? skip <<-ERROR.squish You are using an outdated version of SQLite3 which has a bug in quoted column names. Please update SQLite3 and rebuild the sqlite3 ruby gem ERROR end end def sqlite3_version_includes_quoting_bug? if current_adapter?(:SQLite3Adapter) selected_quoted_column_names = ActiveRecord::Base.connection.exec_query( 'SELECT "join" FROM (SELECT id AS "join" FROM posts) subquery' ).columns ["join"] != selected_quoted_column_names end end end end rails-4.2.6/activerecord/test/cases/relations_test.rb000066400000000000000000001605031266740050600227630ustar00rootroot00000000000000require "cases/helper" require 'models/tag' require 'models/tagging' require 'models/post' require 'models/topic' require 'models/comment' require 'models/author' require 'models/entrant' require 'models/developer' require 'models/computer' require 'models/reply' require 'models/company' require 'models/bird' require 'models/car' require 'models/engine' require 'models/tyre' require 'models/minivan' require 'models/aircraft' require "models/possession" require "models/reader" class RelationTest < ActiveRecord::TestCase fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, :tags, :taggings, :cars, :minivans def test_do_not_double_quote_string_id van = Minivan.last assert van assert_equal van.id, Minivan.where(:minivan_id => van).to_a.first.minivan_id end def test_do_not_double_quote_string_id_with_array van = Minivan.last assert van assert_equal van, Minivan.where(:minivan_id => [van]).to_a.first end def test_bind_values relation = Post.all assert_equal [], relation.bind_values relation2 = relation.bind 'foo' assert_equal %w{ foo }, relation2.bind_values assert_equal [], relation.bind_values end def test_two_scopes_with_includes_should_not_drop_any_include # heat habtm cache car = Car.incl_engines.incl_tyres.first car.tyres.length car.engines.length car = Car.incl_engines.incl_tyres.first assert_no_queries { car.tyres.length } assert_no_queries { car.engines.length } end def test_dynamic_finder x = Post.where('author_id = ?', 1) assert x.klass.respond_to?(:find_by_id), '@klass should handle dynamic finders' end def test_multivalue_where posts = Post.where('author_id = ? AND id = ?', 1, 1) assert_equal 1, posts.to_a.size end def test_scoped topics = Topic.all assert_kind_of ActiveRecord::Relation, topics assert_equal 5, topics.size end def test_to_json assert_nothing_raised { Bird.all.to_json } assert_nothing_raised { Bird.all.to_a.to_json } end def test_to_yaml assert_nothing_raised { Bird.all.to_yaml } assert_nothing_raised { Bird.all.to_a.to_yaml } end def test_to_xml assert_nothing_raised { Bird.all.to_xml } assert_nothing_raised { Bird.all.to_a.to_xml } end def test_scoped_all topics = Topic.all.to_a assert_kind_of Array, topics assert_no_queries { assert_equal 5, topics.size } end def test_loaded_all topics = Topic.all assert_queries(1) do 2.times { assert_equal 5, topics.to_a.size } end assert topics.loaded? end def test_scoped_first topics = Topic.all.order('id ASC') assert_queries(1) do 2.times { assert_equal "The First Topic", topics.first.title } end assert ! topics.loaded? end def test_loaded_first topics = Topic.all.order('id ASC') assert_queries(1) do topics.to_a # force load 2.times { assert_equal "The First Topic", topics.first.title } end assert topics.loaded? end def test_reload topics = Topic.all assert_queries(1) do 2.times { topics.to_a } end assert topics.loaded? original_size = topics.to_a.size Topic.create! :title => 'fake' assert_queries(1) { topics.reload } assert_equal original_size + 1, topics.size assert topics.loaded? end def test_finding_with_subquery relation = Topic.where(:approved => true) assert_equal relation.to_a, Topic.select('*').from(relation).to_a assert_equal relation.to_a, Topic.select('subquery.*').from(relation).to_a assert_equal relation.to_a, Topic.select('a.*').from(relation, :a).to_a end def test_finding_with_subquery_with_binds relation = Post.first.comments assert_equal relation.to_a, Comment.select('*').from(relation).to_a assert_equal relation.to_a, Comment.select('subquery.*').from(relation).to_a assert_equal relation.to_a, Comment.select('a.*').from(relation, :a).to_a end def test_finding_with_subquery_without_select_does_not_change_the_select relation = Topic.where(approved: true) assert_raises(ActiveRecord::StatementInvalid) do Topic.from(relation).to_a end end def test_select_with_subquery_in_from_does_not_use_original_table_name relation = Comment.group(:type).select('COUNT(post_id) AS post_count, type') subquery = Comment.from(relation).select('type','post_count') assert_equal(relation.map(&:post_count).sort,subquery.map(&:post_count).sort) end def test_group_with_subquery_in_from_does_not_use_original_table_name relation = Comment.group(:type).select('COUNT(post_id) AS post_count,type') subquery = Comment.from(relation).group('type').average("post_count") assert_equal(relation.map(&:post_count).sort,subquery.values.sort) end def test_finding_with_conditions assert_equal ["David"], Author.where(:name => 'David').map(&:name) assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name) assert_equal ['Mary'], Author.where("name = ?", 'Mary').map(&:name) end def test_finding_with_order topics = Topic.order('id') assert_equal 5, topics.to_a.size assert_equal topics(:first).title, topics.first.title end def test_finding_with_arel_order topics = Topic.order(Topic.arel_table[:id].asc) assert_equal 5, topics.to_a.size assert_equal topics(:first).title, topics.first.title end def test_finding_with_assoc_order topics = Topic.order(:id => :desc) assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end def test_finding_with_reverted_assoc_order topics = Topic.order(:id => :asc).reverse_order assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end def test_order_with_hash_and_symbol_generates_the_same_sql assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql end def test_finding_with_desc_order_with_string topics = Topic.order(id: "desc") assert_equal 5, topics.to_a.size assert_equal [topics(:fifth), topics(:fourth), topics(:third), topics(:second), topics(:first)], topics.to_a end def test_finding_with_asc_order_with_string topics = Topic.order(id: 'asc') assert_equal 5, topics.to_a.size assert_equal [topics(:first), topics(:second), topics(:third), topics(:fourth), topics(:fifth)], topics.to_a end def test_support_upper_and_lower_case_directions assert_includes Topic.order(id: "ASC").to_sql, "ASC" assert_includes Topic.order(id: "asc").to_sql, "ASC" assert_includes Topic.order(id: :ASC).to_sql, "ASC" assert_includes Topic.order(id: :asc).to_sql, "ASC" assert_includes Topic.order(id: "DESC").to_sql, "DESC" assert_includes Topic.order(id: "desc").to_sql, "DESC" assert_includes Topic.order(id: :DESC).to_sql, "DESC" assert_includes Topic.order(id: :desc).to_sql,"DESC" end def test_raising_exception_on_invalid_hash_params e = assert_raise(ArgumentError) { Topic.order(:name, "id DESC", id: :asfsdf) } assert_equal 'Direction "asfsdf" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]', e.message end def test_finding_last_with_arel_order topics = Topic.order(Topic.arel_table[:id].asc) assert_equal topics(:fifth).title, topics.last.title end def test_finding_with_order_concatenated topics = Topic.order('author_name').order('title') assert_equal 5, topics.to_a.size assert_equal topics(:fourth).title, topics.first.title end def test_finding_with_order_by_aliased_attributes topics = Topic.order(:heading) assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end def test_finding_with_assoc_order_by_aliased_attributes topics = Topic.order(heading: :desc) assert_equal 5, topics.to_a.size assert_equal topics(:third).title, topics.first.title end def test_finding_with_reorder topics = Topic.order('author_name').order('title').reorder('id').to_a topics_titles = topics.map{ |t| t.title } assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day', 'The Fifth Topic of the day'], topics_titles end def test_finding_with_reorder_by_aliased_attributes topics = Topic.order('author_name').reorder(:heading) assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end def test_finding_with_assoc_reorder_by_aliased_attributes topics = Topic.order('author_name').reorder(heading: :desc) assert_equal 5, topics.to_a.size assert_equal topics(:third).title, topics.first.title end def test_finding_with_order_and_take entrants = Entrant.order("id ASC").limit(2).to_a assert_equal 2, entrants.size assert_equal entrants(:first).name, entrants.first.name end def test_finding_with_cross_table_order_and_limit tags = Tag.includes(:taggings). order("tags.name asc", "taggings.taggable_id asc", "REPLACE('abc', taggings.taggable_type, taggings.taggable_type)"). limit(1).to_a assert_equal 1, tags.length end def test_finding_with_complex_order_and_limit tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a assert_equal 1, tags.length end def test_finding_with_complex_order tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a assert_equal 3, tags.length end def test_finding_with_order_limit_and_offset entrants = Entrant.order("id ASC").limit(2).offset(1) assert_equal 2, entrants.to_a.size assert_equal entrants(:second).name, entrants.first.name entrants = Entrant.order("id ASC").limit(2).offset(2) assert_equal 1, entrants.to_a.size assert_equal entrants(:third).name, entrants.first.name end def test_finding_with_group developers = Developer.group("salary").select("salary").to_a assert_equal 4, developers.size assert_equal 4, developers.map(&:salary).uniq.size end def test_select_with_block even_ids = Developer.all.select {|d| d.id % 2 == 0 }.map(&:id) assert_equal [2, 4, 6, 8, 10], even_ids.sort end def test_none assert_no_queries(ignore_none: false) do assert_equal [], Developer.none assert_equal [], Developer.all.none end end def test_none_chainable assert_no_queries(ignore_none: false) do assert_equal [], Developer.none.where(:name => 'David') end end def test_none_chainable_to_existing_scope_extension_method assert_no_queries(ignore_none: false) do assert_equal 1, Topic.anonymous_extension.none.one end end def test_none_chained_to_methods_firing_queries_straight_to_db assert_no_queries(ignore_none: false) do assert_equal [], Developer.none.pluck(:id, :name) assert_equal 0, Developer.none.delete_all assert_equal 0, Developer.none.update_all(:name => 'David') assert_equal 0, Developer.none.delete(1) assert_equal false, Developer.none.exists?(1) end end def test_null_relation_content_size_methods assert_no_queries(ignore_none: false) do assert_equal 0, Developer.none.size assert_equal 0, Developer.none.count assert_equal true, Developer.none.empty? assert_equal false, Developer.none.any? assert_equal false, Developer.none.many? end end def test_null_relation_calculations_methods assert_no_queries(ignore_none: false) do assert_equal 0, Developer.none.count assert_equal 0, Developer.none.calculate(:count, nil, {}) assert_equal nil, Developer.none.calculate(:average, 'salary') end end def test_null_relation_metadata_methods assert_equal "", Developer.none.to_sql assert_equal({}, Developer.none.where_values_hash) end def test_null_relation_where_values_hash assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash) end def test_null_relation_sum ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:id).sum(:id) assert_equal 0, ac.engines.count ac.save assert_equal Hash.new, ac.engines.group(:id).sum(:id) assert_equal 0, ac.engines.count end def test_null_relation_count ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:id).count assert_equal 0, ac.engines.count ac.save assert_equal Hash.new, ac.engines.group(:id).count assert_equal 0, ac.engines.count end def test_null_relation_size ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:id).size assert_equal 0, ac.engines.size ac.save assert_equal Hash.new, ac.engines.group(:id).size assert_equal 0, ac.engines.size end def test_null_relation_average ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:car_id).average(:id) assert_equal nil, ac.engines.average(:id) ac.save assert_equal Hash.new, ac.engines.group(:car_id).average(:id) assert_equal nil, ac.engines.average(:id) end def test_null_relation_minimum ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id) assert_equal nil, ac.engines.minimum(:id) ac.save assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id) assert_equal nil, ac.engines.minimum(:id) end def test_null_relation_maximum ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id) assert_equal nil, ac.engines.maximum(:id) ac.save assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id) assert_equal nil, ac.engines.maximum(:id) end def test_null_relation_in_where_condition assert_operator Comment.count, :>, 0 # precondition, make sure there are comments. assert_equal 0, Comment.where(post_id: Post.none).to_a.size end def test_joins_with_nil_argument assert_nothing_raised { DependentFirm.joins(nil).first } end def test_finding_with_hash_conditions_on_joined_table firms = DependentFirm.joins(:account).where({:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}).to_a assert_equal 1, firms.size assert_equal companies(:rails_core), firms.first end def test_find_all_with_join developers_on_project_one = Developer.joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id'). where('project_id=1').to_a assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map { |d| d.name } assert developer_names.include?('David') assert developer_names.include?('Jamis') end def test_find_on_hash_conditions assert_equal Topic.all.merge!(:where => {:approved => false}).to_a, Topic.where({ :approved => false }).to_a end def test_joins_with_string_array person_with_reader_and_post = Post.joins([ "INNER JOIN categorizations ON categorizations.post_id = posts.id", "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" ] ).to_a assert_equal 1, person_with_reader_and_post.size end def test_no_arguments_to_query_methods_raise_errors assert_raises(ArgumentError) { Topic.references() } assert_raises(ArgumentError) { Topic.includes() } assert_raises(ArgumentError) { Topic.preload() } assert_raises(ArgumentError) { Topic.group() } assert_raises(ArgumentError) { Topic.reorder() } end def test_blank_like_arguments_to_query_methods_dont_raise_errors assert_nothing_raised { Topic.references([]) } assert_nothing_raised { Topic.includes([]) } assert_nothing_raised { Topic.preload([]) } assert_nothing_raised { Topic.group([]) } assert_nothing_raised { Topic.reorder([]) } end def test_scoped_responds_to_delegated_methods relation = Topic.all ["map", "uniq", "sort", "insert", "delete", "update"].each do |method| assert_respond_to relation, method, "Topic.all should respond to #{method.inspect}" end end def test_respond_to_delegates_to_relation relation = Topic.all fake_arel = Struct.new(:responds) { def respond_to? method, access = false responds << [method, access] end }.new [] relation.extend(Module.new { attr_accessor :arel }) relation.arel = fake_arel relation.respond_to?(:matching_attributes) assert_equal [:matching_attributes, false], fake_arel.responds.first fake_arel.responds = [] relation.respond_to?(:matching_attributes, true) assert_equal [:matching_attributes, true], fake_arel.responds.first end def test_respond_to_dynamic_finders relation = Topic.all ["find_by_title", "find_by_title_and_author_name"].each do |method| assert_respond_to relation, method, "Topic.all should respond to #{method.inspect}" end end def test_respond_to_class_methods_and_scopes assert Topic.all.respond_to?(:by_lifo) end def test_find_with_readonly_option Developer.all.each { |d| assert !d.readonly? } Developer.all.readonly.each { |d| assert d.readonly? } end def test_eager_association_loading_of_stis_with_multiple_references authors = Author.eager_load(:posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } }). order('comments.body, very_special_comments_posts.body').where('posts.id = 4').to_a assert_equal [authors(:david)], authors assert_no_queries do authors.first.posts.first.special_comments.first.post.special_comments authors.first.posts.first.special_comments.first.post.very_special_comment end end def test_find_with_preloaded_associations assert_queries(2) do posts = Post.preload(:comments).order('posts.id') assert posts.first.comments.first end assert_queries(2) do posts = Post.preload(:comments).order('posts.id') assert posts.first.comments.first end assert_queries(2) do posts = Post.preload(:author).order('posts.id') assert posts.first.author end assert_queries(2) do posts = Post.preload(:author).order('posts.id') assert posts.first.author end assert_queries(3) do posts = Post.preload(:author, :comments).order('posts.id') assert posts.first.author assert posts.first.comments.first end end def test_preload_applies_to_all_chained_preloaded_scopes assert_queries(3) do post = Post.with_comments.with_tags.first assert post end end def test_find_with_included_associations assert_queries(2) do posts = Post.includes(:comments).order('posts.id') assert posts.first.comments.first end assert_queries(2) do posts = Post.all.includes(:comments).order('posts.id') assert posts.first.comments.first end assert_queries(2) do posts = Post.includes(:author).order('posts.id') assert posts.first.author end assert_queries(3) do posts = Post.includes(:author, :comments).order('posts.id') assert posts.first.author assert posts.first.comments.first end end def test_default_scope_with_conditions_string assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort assert_nil DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end def test_default_scoping_finder_methods developers = DeveloperCalledDavid.order('id').map(&:id).sort assert_equal Developer.where(name: 'David').map(&:id).sort, developers end def test_includes_with_select query = Post.select('comments_count AS ranking').order('ranking').includes(:comments) .where(comments: { id: 1 }) assert_equal ['comments_count AS ranking'], query.select_values assert_equal 1, query.to_a.size end def test_preloading_with_associations_and_merges post = Post.create! title: 'Uhuu', body: 'body' reader = Reader.create! post_id: post.id, person_id: 1 comment = Comment.create! post_id: post.id, body: 'body' assert !comment.respond_to?(:readers) post_rel = Post.preload(:readers).joins(:readers).where(title: 'Uhuu') result_comment = Comment.joins(:post).merge(post_rel).to_a.first assert_equal comment, result_comment assert_no_queries do assert_equal post, result_comment.post assert_equal [reader], result_comment.post.readers.to_a end post_rel = Post.includes(:readers).where(title: 'Uhuu') result_comment = Comment.joins(:post).merge(post_rel).first assert_equal comment, result_comment assert_no_queries do assert_equal post, result_comment.post assert_equal [reader], result_comment.post.readers.to_a end end def test_preloading_with_associations_default_scopes_and_merges post = Post.create! title: 'Uhuu', body: 'body' reader = Reader.create! post_id: post.id, person_id: 1 post_rel = PostWithPreloadDefaultScope.preload(:readers).joins(:readers).where(title: 'Uhuu') result_post = PostWithPreloadDefaultScope.all.merge(post_rel).to_a.first assert_no_queries do assert_equal [reader], result_post.readers.to_a end post_rel = PostWithIncludesDefaultScope.includes(:readers).where(title: 'Uhuu') result_post = PostWithIncludesDefaultScope.all.merge(post_rel).to_a.first assert_no_queries do assert_equal [reader], result_post.readers.to_a end end def test_loading_with_one_association posts = Post.preload(:comments) post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) post = Post.where("posts.title = 'Welcome to the weblog'").preload(:comments).first assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) posts = Post.preload(:last_comment) post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_to_sql_on_eager_join expected = assert_sql { Post.eager_load(:last_comment).order('comments.id DESC').to_a }.first actual = Post.eager_load(:last_comment).order('comments.id DESC').to_sql assert_equal expected, actual end def test_to_sql_on_scoped_proxy auth = Author.first Post.where("1=1").written_by(auth) assert_not auth.posts.to_sql.include?("1=1") end def test_loading_with_one_association_with_non_preload posts = Post.eager_load(:last_comment).order('comments.id DESC') post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_dynamic_find_by_attributes david = authors(:david) author = Author.preload(:taggings).find_by_id(david.id) expected_taggings = taggings(:welcome_general, :thinking_general) assert_no_queries do assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id } assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id } end authors = Author.all assert_equal david, authors.find_by_id_and_name(david.id, david.name) assert_equal david, authors.find_by_id_and_name!(david.id, david.name) end def test_dynamic_find_by_attributes_bang author = Author.all.find_by_id!(authors(:david).id) assert_equal "David", author.name assert_raises(ActiveRecord::RecordNotFound) { Author.all.find_by_id_and_name!(20, 'invalid') } end def test_find_id authors = Author.all david = authors.find(authors(:david).id) assert_equal 'David', david.name assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find('42') } end def test_find_ids authors = Author.order('id ASC') results = authors.find(authors(:david).id, authors(:mary).id) assert_kind_of Array, results assert_equal 2, results.size assert_equal 'David', results[0].name assert_equal 'Mary', results[1].name assert_equal results, authors.find([authors(:david).id, authors(:mary).id]) assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find(authors(:david).id, '42') } assert_raises(ActiveRecord::RecordNotFound) { authors.find(['42', 43]) } end def test_find_in_empty_array authors = Author.all.where(:id => []) assert authors.to_a.blank? end def test_where_with_ar_object author = Author.first authors = Author.all.where(:id => author) assert_equal 1, authors.to_a.length end def test_find_with_list_of_ar author = Author.first authors = Author.find([author.id]) assert_equal author, authors.first end class Mary < Author; end def test_find_by_classname Author.create!(:name => Mary.name) assert_equal 1, Author.where(:name => Mary).size end def test_find_by_id_with_list_of_ar author = Author.first authors = Author.find_by_id([author]) assert_equal author, authors end def test_find_all_using_where_twice_should_or_the_relation david = authors(:david) relation = Author.unscoped relation = relation.where(:name => david.name) relation = relation.where(:name => 'Santiago') relation = relation.where(:id => david.id) assert_equal [], relation.to_a end def test_multi_where_ands_queries relation = Author.unscoped david = authors(:david) sql = relation.where(:name => david.name).where(:name => 'Santiago').to_sql assert_match('AND', sql) end def test_find_all_with_multiple_should_use_and david = authors(:david) relation = [ { :name => david.name }, { :name => 'Santiago' }, { :name => 'tenderlove' }, ].inject(Author.unscoped) do |memo, param| memo.where(param) end assert_equal [], relation.to_a end def test_typecasting_where_with_array ids = Author.pluck(:id) slugs = ids.map { |id| "#{id}-as-a-slug" } assert_equal Author.all.to_a, Author.where(id: slugs).to_a end def test_find_all_using_where_with_relation david = authors(:david) # switching the lines below would succeed in current rails # assert_queries(2) { assert_queries(1) { relation = Author.where(:id => Author.where(:id => david.id)) assert_equal [david], relation.to_a } assert_queries(1) { relation = Author.where('id in (?)', Author.where(id: david).select(:id)) assert_equal [david], relation.to_a } assert_queries(1) do relation = Author.where('id in (:author_ids)', author_ids: Author.where(id: david).select(:id)) assert_equal [david], relation.to_a end end def test_find_all_using_where_with_relation_with_bound_values david = authors(:david) davids_posts = david.posts.order(:id).to_a assert_queries(1) do relation = Post.where(id: david.posts.select(:id)) assert_equal davids_posts, relation.order(:id).to_a end assert_queries(1) do relation = Post.where('id in (?)', david.posts.select(:id)) assert_equal davids_posts, relation.order(:id).to_a, 'should process Relation as bind variables' end assert_queries(1) do relation = Post.where('id in (:post_ids)', post_ids: david.posts.select(:id)) assert_equal davids_posts, relation.order(:id).to_a, 'should process Relation as named bind variables' end end def test_find_all_using_where_with_relation_and_alternate_primary_key cool_first = minivans(:cool_first) # switching the lines below would succeed in current rails # assert_queries(2) { assert_queries(1) { relation = Minivan.where(:minivan_id => Minivan.where(:name => cool_first.name)) assert_equal [cool_first], relation.to_a } end def test_find_all_using_where_with_relation_does_not_alter_select_values david = authors(:david) subquery = Author.where(:id => david.id) assert_queries(1) { relation = Author.where(:id => subquery) assert_equal [david], relation.to_a } assert_equal 0, subquery.select_values.size end def test_find_all_using_where_with_relation_with_joins david = authors(:david) assert_queries(1) { relation = Author.where(:id => Author.joins(:posts).where(:id => david.id)) assert_equal [david], relation.to_a } end def test_find_all_using_where_with_relation_with_select_to_build_subquery david = authors(:david) assert_queries(1) { relation = Author.where(:name => Author.where(:id => david.id).select(:name)) assert_equal [david], relation.to_a } end def test_exists davids = Author.where(:name => 'David') assert davids.exists? assert davids.exists?(authors(:david).id) assert ! davids.exists?(authors(:mary).id) assert ! davids.exists?("42") assert ! davids.exists?(42) assert ! davids.exists?(davids.new.id) fake = Author.where(:name => 'fake author') assert ! fake.exists? assert ! fake.exists?(authors(:david).id) end def test_exists_uses_existing_scope post = authors(:david).posts.first authors = Author.includes(:posts).where(name: "David", posts: { id: post.id }) assert authors.exists?(authors(:david).id) end def test_last authors = Author.all assert_equal authors(:bob), authors.last end def test_destroy_all davids = Author.where(:name => 'David') # Force load assert_equal [authors(:david)], davids.to_a assert davids.loaded? assert_difference('Author.count', -1) { davids.destroy_all } assert_equal [], davids.to_a assert davids.loaded? end def test_delete_all davids = Author.where(:name => 'David') assert_difference('Author.count', -1) { davids.delete_all } assert ! davids.loaded? end def test_delete_all_loaded davids = Author.where(:name => 'David') # Force load assert_equal [authors(:david)], davids.to_a assert davids.loaded? assert_difference('Author.count', -1) { davids.delete_all } assert_equal [], davids.to_a assert davids.loaded? end def test_delete_all_with_unpermitted_relation_raises_error assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.uniq.delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.having('SUM(id) < 3').delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.offset(10).delete_all } end def test_select_with_aggregates posts = Post.select(:title, :body) assert_equal 11, posts.count(:all) assert_equal 11, posts.size assert posts.any? assert posts.many? assert_not posts.empty? end def test_select_takes_a_variable_list_of_args david = developers(:david) developer = Developer.where(id: david.id).select(:name, :salary).first assert_equal david.name, developer.name assert_equal david.salary, developer.salary end def test_select_takes_an_aliased_attribute first = topics(:first) topic = Topic.where(id: first.id).select(:heading).first assert_equal first.heading, topic.heading end def test_select_argument_error assert_raises(ArgumentError) { Developer.select } end def test_count posts = Post.all assert_equal 11, posts.count assert_equal 11, posts.count(:all) assert_equal 11, posts.count(:id) assert_equal 1, posts.where('comments_count > 1').count assert_equal 9, posts.where(:comments_count => 0).count end def test_count_on_association_relation author = Author.last another_author = Author.first posts = Post.where(author_id: author.id) assert_equal author.posts.where(author_id: author.id).size, posts.count assert_equal 0, author.posts.where(author_id: another_author.id).size assert author.posts.where(author_id: another_author.id).empty? end def test_count_with_distinct posts = Post.all assert_equal 3, posts.distinct(true).count(:comments_count) assert_equal 11, posts.distinct(false).count(:comments_count) assert_equal 3, posts.distinct(true).select(:comments_count).count assert_equal 11, posts.distinct(false).select(:comments_count).count end def test_update_all_with_scope tag = Tag.first Post.tagged_with(tag.id).update_all title: "rofl" list = Post.tagged_with(tag.id).all.to_a assert_operator list.length, :>, 0 list.each { |post| assert_equal 'rofl', post.title } end def test_count_explicit_columns Post.update_all(:comments_count => nil) posts = Post.all assert_equal [0], posts.select('comments_count').where('id is not null').group('id').order('id').count.values.uniq assert_equal 0, posts.where('id is not null').select('comments_count').count assert_equal 11, posts.select('comments_count').count('id') assert_equal 0, posts.select('comments_count').count assert_equal 0, posts.count(:comments_count) assert_equal 0, posts.count('comments_count') end def test_multiple_selects post = Post.all.select('comments_count').select('title').order("id ASC").first assert_equal "Welcome to the weblog", post.title assert_equal 2, post.comments_count end def test_size posts = Post.all assert_queries(1) { assert_equal 11, posts.size } assert ! posts.loaded? best_posts = posts.where(:comments_count => 0) best_posts.to_a # force load assert_no_queries { assert_equal 9, best_posts.size } end def test_size_with_limit posts = Post.limit(10) assert_queries(1) { assert_equal 10, posts.size } assert ! posts.loaded? best_posts = posts.where(:comments_count => 0) best_posts.to_a # force load assert_no_queries { assert_equal 9, best_posts.size } end def test_size_with_zero_limit posts = Post.limit(0) assert_no_queries { assert_equal 0, posts.size } assert ! posts.loaded? posts.to_a # force load assert_no_queries { assert_equal 0, posts.size } end def test_empty_with_zero_limit posts = Post.limit(0) assert_no_queries { assert_equal true, posts.empty? } assert ! posts.loaded? end def test_count_complex_chained_relations posts = Post.select('comments_count').where('id is not null').group("author_id").where("comments_count > 0") expected = { 1 => 2 } assert_equal expected, posts.count end def test_empty posts = Post.all assert_queries(1) { assert_equal false, posts.empty? } assert ! posts.loaded? no_posts = posts.where(:title => "") assert_queries(1) { assert_equal true, no_posts.empty? } assert ! no_posts.loaded? best_posts = posts.where(:comments_count => 0) best_posts.to_a # force load assert_no_queries { assert_equal false, best_posts.empty? } end def test_empty_complex_chained_relations posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0") assert_queries(1) { assert_equal false, posts.empty? } assert ! posts.loaded? no_posts = posts.where(:title => "") assert_queries(1) { assert_equal true, no_posts.empty? } assert ! no_posts.loaded? end def test_any posts = Post.all # This test was failing when run on its own (as opposed to running the entire suite). # The second line in the assert_queries block was causing visit_Arel_Attributes_Attribute # in Arel::Visitors::ToSql to trigger a SHOW TABLES query. Running that line here causes # the SHOW TABLES result to be cached so we don't have to do it again in the block. # # This is obviously a rubbish fix but it's the best I can come up with for now... posts.where(:id => nil).any? assert_queries(3) do assert posts.any? # Uses COUNT() assert ! posts.where(:id => nil).any? assert posts.any? {|p| p.id > 0 } assert ! posts.any? {|p| p.id <= 0 } end assert posts.loaded? end def test_many posts = Post.all assert_queries(2) do assert posts.many? # Uses COUNT() assert posts.many? {|p| p.id > 0 } assert ! posts.many? {|p| p.id < 2 } end assert posts.loaded? end def test_many_with_limits posts = Post.all assert posts.many? assert ! posts.limit(1).many? end def test_build posts = Post.all post = posts.new assert_kind_of Post, post end def test_scoped_build posts = Post.where(:title => 'You told a lie') post = posts.new assert_kind_of Post, post assert_equal 'You told a lie', post.title end def test_create birds = Bird.all sparrow = birds.create assert_kind_of Bird, sparrow assert !sparrow.persisted? hen = birds.where(:name => 'hen').create assert hen.persisted? assert_equal 'hen', hen.name end def test_create_bang birds = Bird.all assert_raises(ActiveRecord::RecordInvalid) { birds.create! } hen = birds.where(:name => 'hen').create! assert_kind_of Bird, hen assert hen.persisted? assert_equal 'hen', hen.name end def test_first_or_create parrot = Bird.where(:color => 'green').first_or_create(:name => 'parrot') assert_kind_of Bird, parrot assert parrot.persisted? assert_equal 'parrot', parrot.name assert_equal 'green', parrot.color same_parrot = Bird.where(:color => 'green').first_or_create(:name => 'parakeet') assert_kind_of Bird, same_parrot assert same_parrot.persisted? assert_equal parrot, same_parrot end def test_first_or_create_with_no_parameters parrot = Bird.where(:color => 'green').first_or_create assert_kind_of Bird, parrot assert !parrot.persisted? assert_equal 'green', parrot.color end def test_first_or_create_with_block parrot = Bird.where(:color => 'green').first_or_create { |bird| bird.name = 'parrot' } assert_kind_of Bird, parrot assert parrot.persisted? assert_equal 'green', parrot.color assert_equal 'parrot', parrot.name same_parrot = Bird.where(:color => 'green').first_or_create { |bird| bird.name = 'parakeet' } assert_equal parrot, same_parrot end def test_first_or_create_with_array several_green_birds = Bird.where(:color => 'green').first_or_create([{:name => 'parrot'}, {:name => 'parakeet'}]) assert_kind_of Array, several_green_birds several_green_birds.each { |bird| assert bird.persisted? } same_parrot = Bird.where(:color => 'green').first_or_create([{:name => 'hummingbird'}, {:name => 'macaw'}]) assert_kind_of Bird, same_parrot assert_equal several_green_birds.first, same_parrot end def test_first_or_create_bang_with_valid_options parrot = Bird.where(:color => 'green').first_or_create!(:name => 'parrot') assert_kind_of Bird, parrot assert parrot.persisted? assert_equal 'parrot', parrot.name assert_equal 'green', parrot.color same_parrot = Bird.where(:color => 'green').first_or_create!(:name => 'parakeet') assert_kind_of Bird, same_parrot assert same_parrot.persisted? assert_equal parrot, same_parrot end def test_first_or_create_bang_with_invalid_options assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!(:pirate_id => 1) } end def test_first_or_create_bang_with_no_parameters assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create! } end def test_first_or_create_bang_with_valid_block parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.name = 'parrot' } assert_kind_of Bird, parrot assert parrot.persisted? assert_equal 'green', parrot.color assert_equal 'parrot', parrot.name same_parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.name = 'parakeet' } assert_equal parrot, same_parrot end def test_first_or_create_bang_with_invalid_block assert_raise(ActiveRecord::RecordInvalid) do Bird.where(:color => 'green').first_or_create! { |bird| bird.pirate_id = 1 } end end def test_first_or_create_with_valid_array several_green_birds = Bird.where(:color => 'green').first_or_create!([{:name => 'parrot'}, {:name => 'parakeet'}]) assert_kind_of Array, several_green_birds several_green_birds.each { |bird| assert bird.persisted? } same_parrot = Bird.where(:color => 'green').first_or_create!([{:name => 'hummingbird'}, {:name => 'macaw'}]) assert_kind_of Bird, same_parrot assert_equal several_green_birds.first, same_parrot end def test_first_or_create_with_invalid_array assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!([ {:name => 'parrot'}, {:pirate_id => 1} ]) } end def test_first_or_initialize parrot = Bird.where(:color => 'green').first_or_initialize(:name => 'parrot') assert_kind_of Bird, parrot assert !parrot.persisted? assert parrot.valid? assert parrot.new_record? assert_equal 'parrot', parrot.name assert_equal 'green', parrot.color end def test_first_or_initialize_with_no_parameters parrot = Bird.where(:color => 'green').first_or_initialize assert_kind_of Bird, parrot assert !parrot.persisted? assert !parrot.valid? assert parrot.new_record? assert_equal 'green', parrot.color end def test_first_or_initialize_with_block parrot = Bird.where(:color => 'green').first_or_initialize { |bird| bird.name = 'parrot' } assert_kind_of Bird, parrot assert !parrot.persisted? assert parrot.valid? assert parrot.new_record? assert_equal 'green', parrot.color assert_equal 'parrot', parrot.name end def test_find_or_create_by assert_nil Bird.find_by(name: 'bob') bird = Bird.find_or_create_by(name: 'bob') assert bird.persisted? assert_equal bird, Bird.find_or_create_by(name: 'bob') end def test_find_or_create_by_with_create_with assert_nil Bird.find_by(name: 'bob') bird = Bird.create_with(color: 'green').find_or_create_by(name: 'bob') assert bird.persisted? assert_equal 'green', bird.color assert_equal bird, Bird.create_with(color: 'blue').find_or_create_by(name: 'bob') end def test_find_or_create_by! assert_raises(ActiveRecord::RecordInvalid) { Bird.find_or_create_by!(color: 'green') } end def test_find_or_initialize_by assert_nil Bird.find_by(name: 'bob') bird = Bird.find_or_initialize_by(name: 'bob') assert bird.new_record? bird.save! assert_equal bird, Bird.find_or_initialize_by(name: 'bob') end def test_explicit_create_scope hens = Bird.where(:name => 'hen') assert_equal 'hen', hens.new.name hens = hens.create_with(:name => 'cock') assert_equal 'cock', hens.new.name end def test_except relation = Post.where(:author_id => 1).order('id ASC').limit(1) assert_equal [posts(:welcome)], relation.to_a author_posts = relation.except(:order, :limit) assert_equal Post.where(:author_id => 1).to_a, author_posts.to_a all_posts = relation.except(:where, :order, :limit) assert_equal Post.all, all_posts end def test_only relation = Post.where(:author_id => 1).order('id ASC').limit(1) assert_equal [posts(:welcome)], relation.to_a author_posts = relation.only(:where) assert_equal Post.where(:author_id => 1).to_a, author_posts.to_a all_posts = relation.only(:limit) assert_equal Post.limit(1).to_a.first, all_posts.first end def test_anonymous_extension relation = Post.where(:author_id => 1).order('id ASC').extending do def author 'lifo' end end assert_equal "lifo", relation.author assert_equal "lifo", relation.limit(1).author end def test_named_extension relation = Post.where(:author_id => 1).order('id ASC').extending(Post::NamedExtension) assert_equal "lifo", relation.author assert_equal "lifo", relation.limit(1).author end def test_order_by_relation_attribute assert_equal Post.order(Post.arel_table[:title]).to_a, Post.order("title").to_a end def test_default_scope_order_with_scope_order assert_equal 'zyke', CoolCar.order_using_new_style.limit(1).first.name assert_equal 'zyke', FastCar.order_using_new_style.limit(1).first.name end def test_order_using_scoping car1 = CoolCar.order('id DESC').scoping do CoolCar.all.merge!(order: 'id asc').first end assert_equal 'zyke', car1.name car2 = FastCar.order('id DESC').scoping do FastCar.all.merge!(order: 'id asc').first end assert_equal 'zyke', car2.name end def test_unscoped_block_style assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name} assert_equal 'honda', FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name} end def test_intersection_with_array relation = Author.where(:name => "David") rails_author = relation.first assert_equal [rails_author], [rails_author] & relation assert_equal [rails_author], relation & [rails_author] end def test_primary_key assert_equal "id", Post.all.primary_key end def test_disable_implicit_join_references_is_deprecated assert_deprecated do ActiveRecord::Base.disable_implicit_join_references = true end end def test_ordering_with_extra_spaces assert_equal authors(:david), Author.order('id DESC , name DESC').last end def test_update_all_with_blank_argument assert_raises(ArgumentError) { Comment.update_all({}) } end def test_update_all_with_joins comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id) count = comments.count assert_equal count, comments.update_all(:post_id => posts(:thinking).id) assert_equal posts(:thinking), comments(:greetings).post end def test_update_all_with_joins_and_limit comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).limit(1) assert_equal 1, comments.update_all(:post_id => posts(:thinking).id) end def test_update_all_with_joins_and_limit_and_order comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('comments.id').limit(1) assert_equal 1, comments.update_all(:post_id => posts(:thinking).id) assert_equal posts(:thinking), comments(:greetings).post assert_equal posts(:welcome), comments(:more_greetings).post end def test_update_all_with_joins_and_offset all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id) count = all_comments.count comments = all_comments.offset(1) assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id) end def test_update_all_with_joins_and_offset_and_order all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id', 'comments.id') count = all_comments.count comments = all_comments.offset(1) assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id) assert_equal posts(:thinking), comments(:more_greetings).post assert_equal posts(:welcome), comments(:greetings).post end def test_distinct tag1 = Tag.create(:name => 'Foo') tag2 = Tag.create(:name => 'Foo') query = Tag.select(:name).where(:id => [tag1.id, tag2.id]) assert_equal ['Foo', 'Foo'], query.map(&:name) assert_sql(/DISTINCT/) do assert_equal ['Foo'], query.distinct.map(&:name) assert_equal ['Foo'], query.uniq.map(&:name) end assert_sql(/DISTINCT/) do assert_equal ['Foo'], query.distinct(true).map(&:name) assert_equal ['Foo'], query.uniq(true).map(&:name) end assert_equal ['Foo', 'Foo'], query.distinct(true).distinct(false).map(&:name) assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name) end def test_doesnt_add_having_values_if_options_are_blank scope = Post.having('') assert_equal [], scope.having_values scope = Post.having([]) assert_equal [], scope.having_values end def test_grouping_by_column_with_reserved_name assert_equal [], Possession.select(:where).group(:where).to_a end def test_references_triggers_eager_loading scope = Post.includes(:comments) assert !scope.eager_loading? assert scope.references(:comments).eager_loading? end def test_references_doesnt_trigger_eager_loading_if_reference_not_included scope = Post.references(:comments) assert !scope.eager_loading? end def test_automatically_added_where_references scope = Post.where(:comments => { :body => "Bla" }) assert_equal ['comments'], scope.references_values scope = Post.where('comments.body' => 'Bla') assert_equal ['comments'], scope.references_values end def test_automatically_added_where_not_references scope = Post.where.not(comments: { body: "Bla" }) assert_equal ['comments'], scope.references_values scope = Post.where.not('comments.body' => 'Bla') assert_equal ['comments'], scope.references_values end def test_automatically_added_having_references scope = Post.having(:comments => { :body => "Bla" }) assert_equal ['comments'], scope.references_values scope = Post.having('comments.body' => 'Bla') assert_equal ['comments'], scope.references_values end def test_automatically_added_order_references scope = Post.order('comments.body') assert_equal ['comments'], scope.references_values scope = Post.order('comments.body', 'yaks.body') assert_equal ['comments', 'yaks'], scope.references_values # Don't infer yaks, let's not go down that road again... scope = Post.order('comments.body, yaks.body') assert_equal ['comments'], scope.references_values scope = Post.order('comments.body asc') assert_equal ['comments'], scope.references_values scope = Post.order('foo(comments.body)') assert_equal [], scope.references_values end def test_automatically_added_reorder_references scope = Post.reorder('comments.body') assert_equal %w(comments), scope.references_values scope = Post.reorder('comments.body', 'yaks.body') assert_equal %w(comments yaks), scope.references_values # Don't infer yaks, let's not go down that road again... scope = Post.reorder('comments.body, yaks.body') assert_equal %w(comments), scope.references_values scope = Post.reorder('comments.body asc') assert_equal %w(comments), scope.references_values scope = Post.reorder('foo(comments.body)') assert_equal [], scope.references_values end def test_order_with_reorder_nil_removes_the_order relation = Post.order(:title).reorder(nil) assert_nil relation.order_values.first end def test_reverse_order_with_reorder_nil_removes_the_order relation = Post.order(:title).reverse_order.reorder(nil) assert_nil relation.order_values.first end def test_presence topics = Topic.all # the first query is triggered because there are no topics yet. assert_queries(1) { assert topics.present? } # checking if there are topics is used before you actually display them, # thus it shouldn't invoke an extra count query. assert_no_queries { assert topics.present? } assert_no_queries { assert !topics.blank? } # shows count of topics and loops after loading the query should not trigger extra queries either. assert_no_queries { topics.size } assert_no_queries { topics.length } assert_no_queries { topics.each } # count always trigger the COUNT query. assert_queries(1) { topics.count } assert topics.loaded? end test "find_by with hash conditions returns the first matching record" do assert_equal posts(:eager_other), Post.order(:id).find_by(author_id: 2) end test "find_by with non-hash conditions returns the first matching record" do assert_equal posts(:eager_other), Post.order(:id).find_by("author_id = 2") end test "find_by with multi-arg conditions returns the first matching record" do assert_equal posts(:eager_other), Post.order(:id).find_by('author_id = ?', 2) end test "find_by returns nil if the record is missing" do assert_equal nil, Post.all.find_by("1 = 0") end test "find_by doesn't have implicit ordering" do assert_sql(/^((?!ORDER).)*$/) { Post.all.find_by(author_id: 2) } end test "find_by! with hash conditions returns the first matching record" do assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2) end test "find_by! with non-hash conditions returns the first matching record" do assert_equal posts(:eager_other), Post.order(:id).find_by!("author_id = 2") end test "find_by! with multi-arg conditions returns the first matching record" do assert_equal posts(:eager_other), Post.order(:id).find_by!('author_id = ?', 2) end test "find_by! doesn't have implicit ordering" do assert_sql(/^((?!ORDER).)*$/) { Post.all.find_by!(author_id: 2) } end test "find_by! raises RecordNotFound if the record is missing" do assert_raises(ActiveRecord::RecordNotFound) do Post.all.find_by!("1 = 0") end end test "loaded relations cannot be mutated by multi value methods" do relation = Post.all relation.to_a assert_raises(ActiveRecord::ImmutableRelation) do relation.where! 'foo' end end test "loaded relations cannot be mutated by single value methods" do relation = Post.all relation.to_a assert_raises(ActiveRecord::ImmutableRelation) do relation.limit! 5 end end test "loaded relations cannot be mutated by merge!" do relation = Post.all relation.to_a assert_raises(ActiveRecord::ImmutableRelation) do relation.merge! where: 'foo' end end test "loaded relations cannot be mutated by extending!" do relation = Post.all relation.to_a assert_raises(ActiveRecord::ImmutableRelation) do relation.extending! Module.new end end test "relations show the records in #inspect" do relation = Post.limit(2) assert_equal "#", relation.inspect end test "relations limit the records in #inspect at 10" do relation = Post.limit(11) assert_equal "#", relation.inspect end test "already-loaded relations don't perform a new query in #inspect" do relation = Post.limit(2) relation.to_a expected = "#" assert_no_queries do assert_equal expected, relation.inspect end end test 'using a custom table affects the wheres' do table_alias = Post.arel_table.alias('omg_posts') relation = ActiveRecord::Relation.new Post, table_alias relation.where!(:foo => "bar") node = relation.arel.constraints.first.grep(Arel::Attributes::Attribute).first assert_equal table_alias, node.relation end test '#load' do relation = Post.all assert_queries(1) do assert_equal relation, relation.load end assert_no_queries { relation.to_a } end test 'group with select and includes' do authors_count = Post.select('author_id, COUNT(author_id) AS num_posts'). group('author_id').order('author_id').includes(:author).to_a assert_no_queries do result = authors_count.map do |post| [post.num_posts, post.author.try(:name)] end expected = [[1, nil], [5, "David"], [3, "Mary"], [2, "Bob"]] assert_equal expected, result end end test "joins with select" do posts = Post.joins(:author).select("id", "authors.author_address_id").order("posts.id").limit(3) assert_equal [1, 2, 4], posts.map(&:id) assert_equal [1, 1, 1], posts.map(&:author_address_id) end test "delegations do not leak to other classes" do Topic.all.by_lifo assert Topic.all.class.method_defined?(:by_lifo) assert !Post.all.respond_to?(:by_lifo) end def test_unscope_removes_binds left = Post.where(id: Arel::Nodes::BindParam.new) column = Post.columns_hash['id'] left.bind_values += [[column, 20]] relation = left.unscope(where: :id) assert_equal [], relation.bind_values end def test_merging_removes_rhs_bind_parameters left = Post.where(id: 20) right = Post.where(id: [1,2,3,4]) merged = left.merge(right) assert_equal [], merged.bind_values end def test_merging_keeps_lhs_bind_parameters column = Post.columns_hash['id'] binds = [[column, 20]] right = Post.where(id: 20) left = Post.where(id: 10) merged = left.merge(right) assert_equal binds, merged.bind_values end def test_merging_reorders_bind_params post = Post.first right = Post.where(id: post.id) left = Post.where(title: post.title) merged = left.merge(right) assert_equal post, merged.first end def test_relation_join_method assert_equal 'Thank you for the welcome,Thank you again for the welcome', Post.first.comments.join(",") end end rails-4.2.6/activerecord/test/cases/reload_models_test.rb000066400000000000000000000013661266740050600235750ustar00rootroot00000000000000require "cases/helper" require 'models/owner' require 'models/pet' class ReloadModelsTest < ActiveRecord::TestCase fixtures :pets, :owners def test_has_one_with_reload pet = Pet.find_by_name('parrot') pet.owner = Owner.find_by_name('ashley') # Reload the class Owner, simulating auto-reloading of model classes in a # development environment. Note that meanwhile the class Pet is not # reloaded, simulating a class that is present in a plugin. Object.class_eval { remove_const :Owner } Kernel.load(File.expand_path(File.join(File.dirname(__FILE__), "../models/owner.rb"))) pet = Pet.find_by_name('parrot') pet.owner = Owner.find_by_name('ashley') assert_equal pet.owner, Owner.find_by_name('ashley') end end rails-4.2.6/activerecord/test/cases/result_test.rb000066400000000000000000000046171266740050600223040ustar00rootroot00000000000000require "cases/helper" module ActiveRecord class ResultTest < ActiveRecord::TestCase def result Result.new(['col_1', 'col_2'], [ ['row 1 col 1', 'row 1 col 2'], ['row 2 col 1', 'row 2 col 2'], ['row 3 col 1', 'row 3 col 2'], ]) end test "length" do assert_equal 3, result.length end test "to_hash returns row_hashes" do assert_equal [ {'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'}, {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'}, {'col_1' => 'row 3 col 1', 'col_2' => 'row 3 col 2'}, ], result.to_hash end test "each with block returns row hashes" do result.each do |row| assert_equal ['col_1', 'col_2'], row.keys end end test "each without block returns an enumerator" do result.each.with_index do |row, index| assert_equal ['col_1', 'col_2'], row.keys assert_kind_of Integer, index end end if Enumerator.method_defined? :size test "each without block returns a sized enumerator" do assert_equal 3, result.each.size end end test "cast_values returns rows after type casting" do values = [["1.1", "2.2"], ["3.3", "4.4"]] columns = ["col1", "col2"] types = { "col1" => Type::Integer.new, "col2" => Type::Float.new } result = Result.new(columns, values, types) assert_equal [[1, 2.2], [3, 4.4]], result.cast_values end test "cast_values uses identity type for unknown types" do values = [["1.1", "2.2"], ["3.3", "4.4"]] columns = ["col1", "col2"] types = { "col1" => Type::Integer.new } result = Result.new(columns, values, types) assert_equal [[1, "2.2"], [3, "4.4"]], result.cast_values end test "cast_values returns single dimensional array if single column" do values = [["1.1"], ["3.3"]] columns = ["col1"] types = { "col1" => Type::Integer.new } result = Result.new(columns, values, types) assert_equal [1, 3], result.cast_values end test "cast_values can receive types to use instead" do values = [["1.1", "2.2"], ["3.3", "4.4"]] columns = ["col1", "col2"] types = { "col1" => Type::Integer.new, "col2" => Type::Float.new } result = Result.new(columns, values, types) assert_equal [[1.1, 2.2], [3.3, 4.4]], result.cast_values("col1" => Type::Float.new) end end end rails-4.2.6/activerecord/test/cases/sanitize_test.rb000066400000000000000000000076621266740050600226170ustar00rootroot00000000000000require "cases/helper" require 'models/binary' require 'models/author' require 'models/post' class SanitizeTest < ActiveRecord::TestCase def setup end def test_sanitize_sql_hash_handles_associations quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") quoted_column_name = ActiveRecord::Base.connection.quote_column_name("name") quoted_table_name = ActiveRecord::Base.connection.quote_table_name("adorable_animals") expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}" assert_deprecated do assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}}) end end def test_sanitize_sql_array_handles_string_interpolation quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi") assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"]) assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi".mb_chars]) quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper") assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper"]) assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper".mb_chars]) end def test_sanitize_sql_array_handles_bind_variables quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi"]) assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi".mb_chars]) quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"]) assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars]) end def test_sanitize_sql_array_handles_relations david = Author.create!(name: 'David') david_posts = david.posts.select(:id) sub_query_pattern = /\(\bselect\b.*?\bwhere\b.*?\)/i select_author_sql = Post.send(:sanitize_sql_array, ['id in (?)', david_posts]) assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for bind variables') select_author_sql = Post.send(:sanitize_sql_array, ['id in (:post_ids)', post_ids: david_posts]) assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for named bind variables') end def test_sanitize_sql_array_handles_empty_statement select_author_sql = Post.send(:sanitize_sql_array, ['']) assert_equal('', select_author_sql) end def test_sanitize_sql_like assert_equal '100\%', Binary.send(:sanitize_sql_like, '100%') assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, 'snake_cased_string') assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint') assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42') end def test_sanitize_sql_like_with_custom_escape_character assert_equal '100!%', Binary.send(:sanitize_sql_like, '100%', '!') assert_equal 'snake!_cased!_string', Binary.send(:sanitize_sql_like, 'snake_cased_string', '!') assert_equal 'great!!', Binary.send(:sanitize_sql_like, 'great!', '!') assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', '!') assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42', '!') end def test_sanitize_sql_like_example_use_case searchable_post = Class.new(Post) do def self.search(term) where("title LIKE ?", sanitize_sql_like(term, '!')) end end assert_sql(/LIKE '20!% !_reduction!_!!'/) do searchable_post.search("20% _reduction_!").to_a end end end rails-4.2.6/activerecord/test/cases/schema_dumper_test.rb000066400000000000000000000406021266740050600235740ustar00rootroot00000000000000require "cases/helper" require 'support/schema_dumping_helper' class SchemaDumperTest < ActiveRecord::TestCase include SchemaDumpingHelper self.use_transactional_fixtures = false setup do ActiveRecord::SchemaMigration.create_table end def standard_dump @@standard_dump ||= perform_schema_dump end def perform_schema_dump dump_all_table_schema [] end def test_dump_schema_information_outputs_lexically_ordered_versions versions = %w{ 20100101010101 20100201010101 20100301010101 } versions.reverse_each do |v| ActiveRecord::SchemaMigration.create!(:version => v) end schema_info = ActiveRecord::Base.connection.dump_schema_information assert_match(/20100201010101.*20100301010101/m, schema_info) ensure ActiveRecord::SchemaMigration.delete_all end def test_magic_comment assert_match "# encoding: #{Encoding.default_external.name}", standard_dump end def test_schema_dump output = standard_dump assert_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output end def test_schema_dump_uses_force_cascade_on_create_table output = dump_table_schema "authors" assert_match %r{create_table "authors", force: :cascade}, output end def test_schema_dump_excludes_sqlite_sequence output = standard_dump assert_no_match %r{create_table "sqlite_sequence"}, output end def test_schema_dump_includes_camelcase_table_name output = standard_dump assert_match %r{create_table "CamelCase"}, output end def assert_line_up(lines, pattern, required = false) return assert(true) if lines.empty? matches = lines.map { |line| line.match(pattern) } assert matches.all? if required matches.compact! return assert(true) if matches.empty? assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length end def column_definition_lines(output = standard_dump) output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) } end def test_types_line_up column_definition_lines.each do |column_set| next if column_set.empty? lengths = column_set.map do |column| if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid|point)\s+"/) match[0].length end end assert_equal 1, lengths.uniq.length end end def test_arguments_line_up column_definition_lines.each do |column_set| assert_line_up(column_set, /default: /) assert_line_up(column_set, /limit: /) assert_line_up(column_set, /null: /) end end def test_no_dump_errors output = standard_dump assert_no_match %r{\# Could not dump table}, output end def test_schema_dump_includes_not_null_columns output = dump_all_table_schema([/^[^r]/]) assert_match %r{null: false}, output end def test_schema_dump_includes_limit_constraint_for_integer_columns output = dump_all_table_schema([/^(?!integer_limits)/]) assert_match %r{c_int_without_limit}, output if current_adapter?(:PostgreSQLAdapter) assert_no_match %r{c_int_without_limit.*limit:}, output assert_match %r{c_int_1.*limit: 2}, output assert_match %r{c_int_2.*limit: 2}, output # int 3 is 4 bytes in postgresql assert_match %r{c_int_3.*}, output assert_no_match %r{c_int_3.*limit:}, output assert_match %r{c_int_4.*}, output assert_no_match %r{c_int_4.*limit:}, output elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_match %r{c_int_without_limit.*limit: 4}, output assert_match %r{c_int_1.*limit: 1}, output assert_match %r{c_int_2.*limit: 2}, output assert_match %r{c_int_3.*limit: 3}, output assert_match %r{c_int_4.*}, output assert_no_match %r{c_int_4.*:limit}, output elsif current_adapter?(:SQLite3Adapter) assert_no_match %r{c_int_without_limit.*limit:}, output assert_match %r{c_int_1.*limit: 1}, output assert_match %r{c_int_2.*limit: 2}, output assert_match %r{c_int_3.*limit: 3}, output assert_match %r{c_int_4.*limit: 4}, output end if current_adapter?(:SQLite3Adapter) assert_match %r{c_int_5.*limit: 5}, output assert_match %r{c_int_6.*limit: 6}, output assert_match %r{c_int_7.*limit: 7}, output assert_match %r{c_int_8.*limit: 8}, output elsif current_adapter?(:OracleAdapter) assert_match %r{c_int_5.*limit: 5}, output assert_match %r{c_int_6.*limit: 6}, output assert_match %r{c_int_7.*limit: 7}, output assert_match %r{c_int_8.*limit: 8}, output else assert_match %r{c_int_5.*limit: 8}, output assert_match %r{c_int_6.*limit: 8}, output assert_match %r{c_int_7.*limit: 8}, output assert_match %r{c_int_8.*limit: 8}, output end end def test_schema_dump_with_string_ignored_table output = dump_all_table_schema(['accounts']) assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output end def test_schema_dump_with_regexp_ignored_table output = dump_all_table_schema([/^account/]) assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output end def test_schema_dumps_index_columns_in_right_order index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition else assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition end end def test_schema_dumps_partial_indices index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip if current_adapter?(:PostgreSQLAdapter) assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index? assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition else assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition end end def test_schema_dump_should_honor_nonstandard_primary_keys output = standard_dump match = output.match(%r{create_table "movies"(.*)do}) assert_not_nil(match, "nonstandardpk table not found") assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved" end def test_schema_dump_should_use_false_as_default output = standard_dump assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output end if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_schema_dump_should_add_default_value_for_mysql_text_field output = standard_dump assert_match %r{t.text\s+"body",\s+limit: 65535,\s+null: false$}, output end def test_schema_dump_includes_length_for_mysql_binary_fields output = standard_dump assert_match %r{t.binary\s+"var_binary",\s+limit: 255$}, output assert_match %r{t.binary\s+"var_binary_large",\s+limit: 4095$}, output end def test_schema_dump_includes_length_for_mysql_blob_and_text_fields output = standard_dump assert_match %r{t.binary\s+"tiny_blob",\s+limit: 255$}, output assert_match %r{t.binary\s+"normal_blob",\s+limit: 65535$}, output assert_match %r{t.binary\s+"medium_blob",\s+limit: 16777215$}, output assert_match %r{t.binary\s+"long_blob",\s+limit: 4294967295$}, output assert_match %r{t.text\s+"tiny_text",\s+limit: 255$}, output assert_match %r{t.text\s+"normal_text",\s+limit: 65535$}, output assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output assert_match %r{t.text\s+"long_text",\s+limit: 4294967295$}, output end def test_schema_does_not_include_limit_for_emulated_mysql_boolean_fields output = standard_dump assert_no_match %r{t\.boolean\s+"has_fun",.+limit: 1}, output end def test_schema_dumps_index_type output = standard_dump assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output end end if mysql_56? def test_schema_dump_includes_datetime_precision output = standard_dump assert_match %r{t.datetime\s+"written_on",\s+precision: 6$}, output end end def test_schema_dump_includes_decimal_options output = dump_all_table_schema([/^[^n]/]) assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output end if current_adapter?(:PostgreSQLAdapter) def test_schema_dump_includes_bigint_default output = standard_dump assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output end def test_schema_dump_includes_limit_on_array_type output = standard_dump assert_match %r{t\.integer\s+"big_int_data_points\",\s+limit: 8,\s+array: true}, output end if ActiveRecord::Base.connection.supports_extensions? def test_schema_dump_includes_extensions connection = ActiveRecord::Base.connection connection.stubs(:extensions).returns(['hstore']) output = perform_schema_dump assert_match "# These are extensions that must be enabled", output assert_match %r{enable_extension "hstore"}, output connection.stubs(:extensions).returns([]) output = perform_schema_dump assert_no_match "# These are extensions that must be enabled", output assert_no_match %r{enable_extension}, output end end def test_schema_dump_includes_xml_shorthand_definition output = standard_dump if %r{create_table "postgresql_xml_data_type"} =~ output assert_match %r{t.xml "data"}, output end end def test_schema_dump_includes_inet_shorthand_definition output = standard_dump if %r{create_table "postgresql_network_addresses"} =~ output assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output end end def test_schema_dump_includes_cidr_shorthand_definition output = standard_dump if %r{create_table "postgresql_network_addresses"} =~ output assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output end end def test_schema_dump_includes_macaddr_shorthand_definition output = standard_dump if %r{create_table "postgresql_network_addresses"} =~ output assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output end end def test_schema_dump_includes_uuid_shorthand_definition output = standard_dump if %r{create_table "postgresql_uuids"} =~ output assert_match %r{t.uuid "guid"}, output end end def test_schema_dump_includes_hstores_shorthand_definition output = standard_dump if %r{create_table "postgresql_hstores"} =~ output assert_match %r[t.hstore "hash_store", default: {}], output end end def test_schema_dump_includes_citext_shorthand_definition output = standard_dump if %r{create_table "postgresql_citext"} =~ output assert_match %r[t.citext "text_citext"], output end end def test_schema_dump_includes_ltrees_shorthand_definition output = standard_dump if %r{create_table "postgresql_ltrees"} =~ output assert_match %r[t.ltree "path"], output end end def test_schema_dump_includes_arrays_shorthand_definition output = standard_dump if %r{create_table "postgresql_arrays"} =~ output assert_match %r[t.text\s+"nicknames",\s+array: true], output assert_match %r[t.integer\s+"commission_by_quarter",\s+array: true], output end end def test_schema_dump_includes_tsvector_shorthand_definition output = standard_dump if %r{create_table "postgresql_tsvectors"} =~ output assert_match %r{t.tsvector "text_vector"}, output end end end def test_schema_dump_keeps_large_precision_integer_columns_as_decimal output = standard_dump # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers if current_adapter?(:OracleAdapter) assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38}, output else assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55}, output end end def test_schema_dump_keeps_id_column_when_id_is_false_and_id_column_added output = standard_dump match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n}) assert_not_nil(match, "goofy_string_id table not found") assert_match %r(id: false), match[1], "no table id not preserved" assert_match %r{t.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved" end def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added output = standard_dump assert_match %r{create_table "subscribers", id: false}, output end if ActiveRecord::Base.connection.supports_foreign_keys? def test_foreign_keys_are_dumped_at_the_bottom_to_circumvent_dependency_issues output = standard_dump assert_match(/^\s+add_foreign_key "fk_test_has_fk"[^\n]+\n\s+add_foreign_key "lessons_students"/, output) end def test_do_not_dump_foreign_keys_for_ignored_tables output = dump_table_schema "authors" assert_equal ["authors"], output.scan(/^\s*add_foreign_key "([^"]+)".+$/).flatten end end class CreateDogMigration < ActiveRecord::Migration def up create_table("dog_owners") do |t| end create_table("dogs") do |t| t.column :name, :string t.column :owner_id, :integer end add_index "dogs", [:name] add_foreign_key :dogs, :dog_owners, column: "owner_id" if supports_foreign_keys? end def down drop_table("dogs") drop_table("dog_owners") end end def test_schema_dump_with_table_name_prefix_and_suffix original, $stdout = $stdout, StringIO.new ActiveRecord::Base.table_name_prefix = 'foo_' ActiveRecord::Base.table_name_suffix = '_bar' migration = CreateDogMigration.new migration.migrate(:up) output = perform_schema_dump assert_no_match %r{create_table "foo_.+_bar"}, output assert_no_match %r{add_index "foo_.+_bar"}, output assert_no_match %r{create_table "schema_migrations"}, output if ActiveRecord::Base.connection.supports_foreign_keys? assert_no_match %r{add_foreign_key "foo_.+_bar"}, output assert_no_match %r{add_foreign_key "[^"]+", "foo_.+_bar"}, output end ensure migration.migrate(:down) ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = '' $stdout = original end end class SchemaDumperDefaultsTest < ActiveRecord::TestCase include SchemaDumpingHelper setup do @connection = ActiveRecord::Base.connection @connection.create_table :defaults, force: true do |t| t.string :string_with_default, default: "Hello!" t.date :date_with_default, default: '2014-06-05' t.datetime :datetime_with_default, default: "2014-06-05 07:17:04" t.time :time_with_default, default: "07:17:04" end end teardown do return unless @connection @connection.execute 'DROP TABLE defaults' if @connection.table_exists? 'defaults' end def test_schema_dump_defaults_with_universally_supported_types output = dump_table_schema('defaults') assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output assert_match %r{t\.date\s+"date_with_default",\s+default: '2014-06-05'}, output assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output end end rails-4.2.6/activerecord/test/cases/scoping/000077500000000000000000000000001266740050600210345ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/scoping/default_scoping_test.rb000066400000000000000000000434231266740050600255740ustar00rootroot00000000000000require 'cases/helper' require 'models/post' require 'models/comment' require 'models/developer' require 'models/computer' require 'models/vehicle' class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts, :comments def test_default_scope expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary } assert_equal expected, received end def test_default_scope_as_class_method assert_equal [developers(:david).becomes(ClassMethodDeveloperCalledDavid)], ClassMethodDeveloperCalledDavid.all end def test_default_scope_as_class_method_referencing_scope assert_equal [developers(:david).becomes(ClassMethodReferencingScopeDeveloperCalledDavid)], ClassMethodReferencingScopeDeveloperCalledDavid.all end def test_default_scope_as_block_referencing_scope assert_equal [developers(:david).becomes(LazyBlockReferencingScopeDeveloperCalledDavid)], LazyBlockReferencingScopeDeveloperCalledDavid.all end def test_default_scope_with_lambda assert_equal [developers(:david).becomes(LazyLambdaDeveloperCalledDavid)], LazyLambdaDeveloperCalledDavid.all end def test_default_scope_with_block assert_equal [developers(:david).becomes(LazyBlockDeveloperCalledDavid)], LazyBlockDeveloperCalledDavid.all end def test_default_scope_with_callable assert_equal [developers(:david).becomes(CallableDeveloperCalledDavid)], CallableDeveloperCalledDavid.all end def test_default_scope_is_unscoped_on_find assert_equal 1, DeveloperCalledDavid.count assert_equal 11, DeveloperCalledDavid.unscoped.count end def test_default_scope_is_unscoped_on_create assert_nil DeveloperCalledJamis.unscoped.create!.name end def test_default_scope_with_conditions_string assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort assert_equal nil, DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end unless in_memory_db? def test_default_scoping_with_threads 2.times do Thread.new { assert DeveloperOrderedBySalary.all.to_sql.include?('salary DESC') DeveloperOrderedBySalary.connection.close }.join end end end def test_default_scope_with_inheritance wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres['name'] assert_equal 50000, wheres['salary'] end def test_default_scope_with_module_includes wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres['name'] assert_equal 50000, wheres['salary'] end def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres['name'] assert_equal 50000, wheres['salary'] end def test_scope_overwrites_default expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect { |dev| dev.name } received = DeveloperOrderedBySalary.by_name.to_a.collect { |dev| dev.name } assert_equal expected, received end def test_reorder_overrides_default_scope_order expected = Developer.order('name DESC').collect { |dev| dev.name } received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name } assert_equal expected, received end def test_order_after_reorder_combines_orders expected = Developer.order('name DESC, id DESC').collect { |dev| [dev.name, dev.id] } received = Developer.order('name ASC').reorder('name DESC').order('id DESC').collect { |dev| [dev.name, dev.id] } assert_equal expected, received end def test_unscope_overrides_default_scope expected = Developer.all.collect { |dev| [dev.name, dev.id] } received = DeveloperCalledJamis.unscope(:where).collect { |dev| [dev.name, dev.id] } assert_equal expected, received end def test_unscope_after_reordering_and_combining expected = Developer.order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] } received = DeveloperOrderedBySalary.reorder('name DESC').unscope(:order).order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] } assert_equal expected, received expected_2 = Developer.all.collect { |dev| [dev.name, dev.id] } received_2 = Developer.order('id DESC, name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] } assert_equal expected_2, received_2 expected_3 = Developer.all.collect { |dev| [dev.name, dev.id] } received_3 = Developer.reorder('name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] } assert_equal expected_3, received_3 end def test_unscope_with_where_attributes expected = Developer.order('salary DESC').collect(&:name) received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect(&:name) assert_equal expected, received expected_2 = Developer.order('salary DESC').collect(&:name) received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect(&:name) assert_equal expected_2, received_2 expected_3 = Developer.order('salary DESC').collect(&:name) received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name) assert_equal expected_3, received_3 expected_4 = Developer.order('salary DESC').collect(&:name) received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name) assert_equal expected_4, received_4 expected_5 = Developer.order('salary DESC').collect(&:name) received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name) assert_equal expected_5, received_5 end def test_unscope_comparison_where_clauses # unscoped for WHERE (`developers`.`id` <= 2) expected = Developer.order('salary DESC').collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name } assert_equal expected, received # unscoped for WHERE (`developers`.`id` < 2) expected = Developer.order('salary DESC').collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_multiple_where_clauses expected = Developer.order('salary DESC').collect { |dev| dev.name } received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_string_where_clauses_involved dev_relation = Developer.order('salary DESC').where("created_at > ?", 1.year.ago) expected = dev_relation.collect { |dev| dev.name } dev_ordered_relation = DeveloperOrderedBySalary.where(name: 'Jamis').where("created_at > ?", 1.year.ago) received = dev_ordered_relation.unscope(where: [:name]).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_with_grouping_attributes expected = Developer.order('salary DESC').collect { |dev| dev.name } received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name } assert_equal expected, received expected_2 = Developer.order('salary DESC').collect { |dev| dev.name } received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect { |dev| dev.name } assert_equal expected_2, received_2 end def test_unscope_with_limit_in_query expected = Developer.order('salary DESC').collect { |dev| dev.name } received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect { |dev| dev.name } assert_equal expected, received end def test_order_to_unscope_reordering scope = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order) assert !(scope.to_sql =~ /order/i) end def test_unscope_reverse_order expected = Developer.all.collect { |dev| dev.name } received = Developer.order('salary DESC').reverse_order.unscope(:order).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_select expected = Developer.order('salary ASC').collect { |dev| dev.name } received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect { |dev| dev.name } assert_equal expected, received expected_2 = Developer.all.collect { |dev| dev.id } received_2 = Developer.select(:name).unscope(:select).collect { |dev| dev.id } assert_equal expected_2, received_2 end def test_unscope_offset expected = Developer.all.collect { |dev| dev.name } received = Developer.offset(5).unscope(:offset).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_joins_and_select_on_developers_projects expected = Developer.all.collect { |dev| dev.name } received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_includes expected = Developer.all.collect { |dev| dev.name } received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_having expected = DeveloperOrderedBySalary.all.collect { |dev| dev.name } received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_and_scope developer_klass = Class.new(Developer) do scope :by_name, -> name { unscope(where: :name).where(name: name) } end expected = developer_klass.where(name: 'Jamis').collect { |dev| [dev.name, dev.id] } received = developer_klass.where(name: 'David').by_name('Jamis').collect { |dev| [dev.name, dev.id] } assert_equal expected, received end def test_unscope_errors_with_invalid_value assert_raises(ArgumentError) do Developer.includes(:projects).where(name: "Jamis").unscope(:stupidly_incorrect_value) end assert_raises(ArgumentError) do Developer.all.unscope(:includes, :select, :some_broken_value) end assert_raises(ArgumentError) do Developer.order('name DESC').reverse_order.unscope(:reverse_order) end assert_raises(ArgumentError) do Developer.order('name DESC').where(name: "Jamis").unscope() end end def test_unscope_errors_with_non_where_hash_keys assert_raises(ArgumentError) do Developer.where(name: "Jamis").limit(4).unscope(limit: 4) end assert_raises(ArgumentError) do Developer.where(name: "Jamis").unscope("where" => :name) end end def test_unscope_errors_with_non_symbol_or_hash_arguments assert_raises(ArgumentError) do Developer.where(name: "Jamis").limit(3).unscope("limit") end assert_raises(ArgumentError) do Developer.select("id").unscope("select") end assert_raises(ArgumentError) do Developer.select("id").unscope(5) end end def test_unscope_merging merged = Developer.where(name: "Jamis").merge(Developer.unscope(:where)) assert merged.where_values.empty? assert !merged.where(name: "Jon").where_values.empty? end def test_order_in_default_scope_should_not_prevail expected = Developer.all.merge!(order: 'salary desc').to_a.collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect { |dev| dev.salary } assert_equal expected, received end def test_create_attribute_overwrites_default_scoping assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary end def test_create_attribute_overwrites_default_values assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary end def test_default_scope_attribute jamis = PoorDeveloperCalledJamis.new(:name => 'David') assert_equal 50000, jamis.salary end def test_where_attribute aaron = PoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron') assert_equal 20, aaron.salary assert_equal 'Aaron', aaron.name end def test_where_attribute_merge aaron = PoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron') assert_equal 'Aaron', aaron.name end def test_scope_composed_by_limit_and_then_offset_is_equal_to_scope_composed_by_offset_and_then_limit posts_limit_offset = Post.limit(3).offset(2) posts_offset_limit = Post.offset(2).limit(3) assert_equal posts_limit_offset, posts_offset_limit end def test_create_with_merge aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge( PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new assert_equal 20, aaron.salary assert_equal 'Aaron', aaron.name aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20). create_with(:name => 'Aaron').new assert_equal 20, aaron.salary assert_equal 'Aaron', aaron.name end def test_create_with_reset jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new assert_equal 'Jamis', jamis.name end # FIXME: I don't know if this is *desired* behavior, but it is *today's* # behavior. def test_create_with_empty_hash_will_not_reset jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with({}).new assert_equal 'Aaron', jamis.name end def test_unscoped_with_named_scope_should_not_have_default_scope assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) assert_equal 11, DeveloperCalledJamis.unscoped.length assert_equal 1, DeveloperCalledJamis.poor.length assert_equal 10, DeveloperCalledJamis.unscoped.poor.length assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length end def test_default_scope_select_ignored_by_aggregations assert_equal DeveloperWithSelect.all.to_a.count, DeveloperWithSelect.count end def test_default_scope_select_ignored_by_grouped_aggregations assert_equal Hash[Developer.all.group_by(&:salary).map { |s, d| [s, d.count] }], DeveloperWithSelect.group(:salary).count end def test_default_scope_order_ignored_by_aggregations assert_equal DeveloperOrderedBySalary.all.count, DeveloperOrderedBySalary.count end def test_default_scope_find_last assert DeveloperOrderedBySalary.count > 1, "need more than one row for test" lowest_salary_dev = DeveloperOrderedBySalary.find(developers(:poor_jamis).id) assert_equal lowest_salary_dev, DeveloperOrderedBySalary.last end def test_default_scope_include_with_count d = DeveloperWithIncludes.create! d.audit_logs.create! :message => 'foo' assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count end def test_default_scope_with_references_works_through_collection_association post = PostWithCommentWithDefaultScopeReferencesAssociation.create!(title: "Hello World", body: "Here we go.") comment = post.comment_with_default_scope_references_associations.create!(body: "Great post.", developer_id: Developer.first.id) assert_equal comment, post.comment_with_default_scope_references_associations.to_a.first end def test_default_scope_with_references_works_through_association post = PostWithCommentWithDefaultScopeReferencesAssociation.create!(title: "Hello World", body: "Here we go.") comment = post.comment_with_default_scope_references_associations.create!(body: "Great post.", developer_id: Developer.first.id) assert_equal comment, post.first_comment end def test_default_scope_with_references_works_with_find_by post = PostWithCommentWithDefaultScopeReferencesAssociation.create!(title: "Hello World", body: "Here we go.") comment = post.comment_with_default_scope_references_associations.create!(body: "Great post.", developer_id: Developer.first.id) assert_equal comment, CommentWithDefaultScopeReferencesAssociation.find_by(id: comment.id) end unless in_memory_db? def test_default_scope_is_threadsafe threads = [] assert_not_equal 1, ThreadsafeDeveloper.unscoped.count threads << Thread.new do Thread.current[:long_default_scope] = true assert_equal 1, ThreadsafeDeveloper.all.to_a.count ThreadsafeDeveloper.connection.close end threads << Thread.new do assert_equal 1, ThreadsafeDeveloper.all.to_a.count ThreadsafeDeveloper.connection.close end threads.each(&:join) end end test "additional conditions are ANDed with the default scope" do scope = DeveloperCalledJamis.where(name: "David") assert_equal 2, scope.where_values.length assert_equal [], scope.to_a end test "additional conditions in a scope are ANDed with the default scope" do scope = DeveloperCalledJamis.david assert_equal 2, scope.where_values.length assert_equal [], scope.to_a end test "a scope can remove the condition from the default scope" do scope = DeveloperCalledJamis.david2 assert_equal 1, scope.where_values.length assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id) end def test_with_abstract_class_where_clause_should_not_be_duplicated scope = Bus.all assert_equal scope.where_values.length, 1 end end rails-4.2.6/activerecord/test/cases/scoping/named_scoping_test.rb000066400000000000000000000400151266740050600252260ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/topic' require 'models/comment' require 'models/reply' require 'models/author' require 'models/developer' require 'models/computer' class NamedScopingTest < ActiveRecord::TestCase fixtures :posts, :authors, :topics, :comments, :author_addresses def test_implements_enumerable assert !Topic.all.empty? assert_equal Topic.all.to_a, Topic.base assert_equal Topic.all.to_a, Topic.base.to_a assert_equal Topic.first, Topic.base.first assert_equal Topic.all.to_a, Topic.base.map { |i| i } end def test_found_items_are_cached all_posts = Topic.base assert_queries(1) do all_posts.collect all_posts.collect end end def test_reload_expires_cache_of_found_items all_posts = Topic.base all_posts.to_a new_post = Topic.create! assert !all_posts.include?(new_post) assert all_posts.reload.include?(new_post) end def test_delegates_finds_and_calculations_to_the_base_class assert !Topic.all.empty? assert_equal Topic.all.to_a, Topic.base.to_a assert_equal Topic.first, Topic.base.first assert_equal Topic.count, Topic.base.count assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count) end def test_method_missing_priority_when_delegating klazz = Class.new(ActiveRecord::Base) do self.table_name = "topics" scope :since, Proc.new { where('written_on >= ?', Time.now - 1.day) } scope :to, Proc.new { where('written_on <= ?', Time.now) } end assert_equal klazz.to.since.to_a, klazz.since.to.to_a end def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy assert Topic.approved.respond_to?(:limit) assert Topic.approved.respond_to?(:count) assert Topic.approved.respond_to?(:length) end def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified assert !Topic.all.merge!(:where => {:approved => true}).to_a.empty? assert_equal Topic.all.merge!(:where => {:approved => true}).to_a, Topic.approved assert_equal Topic.where(:approved => true).count, Topic.approved.count end def test_scopes_with_string_name_can_be_composed # NOTE that scopes defined with a string as a name worked on their own # but when called on another scope the other scope was completely replaced assert_equal Topic.replied.approved, Topic.replied.approved_as_string end def test_scopes_are_composable assert_equal((approved = Topic.all.merge!(:where => {:approved => true}).to_a), Topic.approved) assert_equal((replied = Topic.all.merge!(:where => 'replies_count > 0').to_a), Topic.replied) assert !(approved == replied) assert !(approved & replied).empty? assert_equal approved & replied, Topic.approved.replied end def test_procedural_scopes topics_written_before_the_third = Topic.where('written_on < ?', topics(:third).written_on) topics_written_before_the_second = Topic.where('written_on < ?', topics(:second).written_on) assert_not_equal topics_written_before_the_second, topics_written_before_the_third assert_equal topics_written_before_the_third, Topic.written_before(topics(:third).written_on) assert_equal topics_written_before_the_second, Topic.written_before(topics(:second).written_on) end def test_procedural_scopes_returning_nil all_topics = Topic.all assert_equal all_topics, Topic.written_before(nil) end def test_scope_with_object objects = Topic.with_object assert_operator objects.length, :>, 0 assert objects.all?(&:approved?), 'all objects should be approved' end def test_has_many_associations_have_access_to_scopes assert_not_equal Post.containing_the_letter_a, authors(:david).posts assert !Post.containing_the_letter_a.empty? assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a end def test_scope_with_STI assert_equal 3,Post.containing_the_letter_a.count assert_equal 1,SpecialPost.containing_the_letter_a.count end def test_has_many_through_associations_have_access_to_scopes assert_not_equal Comment.containing_the_letter_e, authors(:david).comments assert !Comment.containing_the_letter_e.empty? assert_equal authors(:david).comments & Comment.containing_the_letter_e, authors(:david).comments.containing_the_letter_e end def test_scopes_honor_current_scopes_from_when_defined assert !Post.ranked_by_comments.limit_by(5).empty? assert !authors(:david).posts.ranked_by_comments.limit_by(5).empty? assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) # Oracle sometimes sorts differently if WHERE condition is changed assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).to_a.sort_by(&:id), authors(:david).posts.top(5).to_a.sort_by(&:id) assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5) end def test_scopes_body_is_a_callable e = assert_raises ArgumentError do Class.new(Post).class_eval { scope :containing_the_letter_z, where("body LIKE '%z%'") } end assert_equal "The scope body needs to be callable.", e.message end def test_active_records_have_scope_named__all__ assert !Topic.all.empty? assert_equal Topic.all.to_a, Topic.base end def test_active_records_have_scope_named__scoped__ scope = Topic.where("content LIKE '%Have%'") assert !scope.empty? assert_equal scope, Topic.all.merge!(where: "content LIKE '%Have%'") end def test_first_and_last_should_allow_integers_for_limit assert_equal Topic.base.first(2), Topic.base.to_a.first(2) assert_equal Topic.base.last(2), Topic.base.order("id").to_a.last(2) end def test_first_and_last_should_not_use_query_when_results_are_loaded topics = Topic.base topics.reload # force load assert_no_queries do topics.first topics.last end end def test_empty_should_not_load_results topics = Topic.base assert_queries(2) do topics.empty? # use count query topics.collect # force load topics.empty? # use loaded (no query) end end def test_any_should_not_load_results topics = Topic.base assert_queries(2) do topics.any? # use count query topics.collect # force load topics.any? # use loaded (no query) end end def test_any_should_call_proxy_found_if_using_a_block topics = Topic.base assert_queries(1) do topics.expects(:empty?).never topics.any? { true } end end def test_any_should_not_fire_query_if_scope_loaded topics = Topic.base topics.collect # force load assert_no_queries { assert topics.any? } end def test_model_class_should_respond_to_any assert Topic.any? Topic.delete_all assert !Topic.any? end def test_many_should_not_load_results topics = Topic.base assert_queries(2) do topics.many? # use count query topics.collect # force load topics.many? # use loaded (no query) end end def test_many_should_call_proxy_found_if_using_a_block topics = Topic.base assert_queries(1) do topics.expects(:size).never topics.many? { true } end end def test_many_should_not_fire_query_if_scope_loaded topics = Topic.base topics.collect # force load assert_no_queries { assert topics.many? } end def test_many_should_return_false_if_none_or_one topics = Topic.base.where(:id => 0) assert !topics.many? topics = Topic.base.where(:id => 1) assert !topics.many? end def test_many_should_return_true_if_more_than_one assert Topic.base.many? end def test_model_class_should_respond_to_many Topic.delete_all assert !Topic.many? Topic.create! assert !Topic.many? Topic.create! assert Topic.many? end def test_should_build_on_top_of_scope topic = Topic.approved.build({}) assert topic.approved end def test_should_build_new_on_top_of_scope topic = Topic.approved.new assert topic.approved end def test_should_create_on_top_of_scope topic = Topic.approved.create({}) assert topic.approved end def test_should_create_with_bang_on_top_of_scope topic = Topic.approved.create!({}) assert topic.approved end def test_should_build_on_top_of_chained_scopes topic = Topic.approved.by_lifo.build({}) assert topic.approved assert_equal 'lifo', topic.author_name end def test_reserved_scope_names klass = Class.new(ActiveRecord::Base) do self.table_name = "topics" scope :approved, -> { where(approved: true) } class << self public def pub; end private def pri; end protected def pro; end end end subklass = Class.new(klass) conflicts = [ :create, # public class method on AR::Base :relation, # private class method on AR::Base :new, # redefined class method on AR::Base :all, # a default scope :public, # some imporant methods on Module and Class :protected, :private, :name, :parent, :superclass ] non_conflicts = [ :find_by_title, # dynamic finder method :approved, # existing scope :pub, # existing public class method :pri, # existing private class method :pro, # existing protected class method :open, # a ::Kernel method ] conflicts.each do |name| assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do klass.class_eval { scope name, ->{ where(approved: true) } } end assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do subklass.class_eval { scope name, ->{ where(approved: true) } } end end non_conflicts.each do |name| assert_nothing_raised do silence_warnings do klass.class_eval { scope name, ->{ where(approved: true) } } end end assert_nothing_raised do subklass.class_eval { scope name, ->{ where(approved: true) } } end end end # Method delegation for scope names which look like /\A[a-zA-Z_]\w*[!?]?\z/ # has been done by evaluating a string with a plain def statement. For scope # names which contain spaces this approach doesn't work. def test_spaces_in_scope_names klass = Class.new(ActiveRecord::Base) do self.table_name = "topics" scope :"title containing space", -> { where("title LIKE '% %'") } scope :approved, -> { where(:approved => true) } end assert_equal klass.send(:"title containing space"), klass.where("title LIKE '% %'") assert_equal klass.approved.send(:"title containing space"), klass.approved.where("title LIKE '% %'") end def test_find_all_should_behave_like_select assert_equal Topic.base.to_a.select(&:approved), Topic.base.to_a.find_all(&:approved) end def test_rand_should_select_a_random_object_from_proxy assert_kind_of Topic, Topic.approved.sample end def test_should_use_where_in_query_for_scope assert_equal Developer.where(name: 'Jamis').to_set, Developer.where(id: Developer.jamises).to_set end def test_size_should_use_count_when_results_are_not_loaded topics = Topic.base assert_queries(1) do assert_sql(/COUNT/i) { topics.size } end end def test_size_should_use_length_when_results_are_loaded topics = Topic.base topics.reload # force load assert_no_queries do topics.size # use loaded (no query) end end def test_should_not_duplicates_where_values where_values = Topic.where("1=1").scope_with_lambda.where_values assert_equal ["1=1"], where_values end def test_chaining_with_duplicate_joins join = "INNER JOIN comments ON comments.post_id = posts.id" post = Post.find(1) assert_equal post.comments.size, Post.joins(join).joins(join).where("posts.id = #{post.id}").size end def test_chaining_applies_last_conditions_when_creating post = Topic.rejected.new assert !post.approved? post = Topic.rejected.approved.new assert post.approved? post = Topic.approved.rejected.new assert !post.approved? post = Topic.approved.rejected.approved.new assert post.approved? end def test_chaining_combines_conditions_when_searching # Normal hash conditions assert_equal Topic.where(approved: false).where(approved: true).to_a, Topic.rejected.approved.to_a assert_equal Topic.where(approved: true).where(approved: false).to_a, Topic.approved.rejected.to_a # Nested hash conditions with same keys assert_equal [], Post.with_special_comments.with_very_special_comments.to_a # Nested hash conditions with different keys assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).to_a.uniq end def test_scopes_batch_finders assert_equal 4, Topic.approved.count assert_queries(5) do Topic.approved.find_each(:batch_size => 1) {|t| assert t.approved? } end assert_queries(3) do Topic.approved.find_in_batches(:batch_size => 2) do |group| group.each {|t| assert t.approved? } end end end def test_table_names_for_chaining_scopes_with_and_without_table_name_included assert_nothing_raised do Comment.for_first_post.for_first_author.to_a end end def test_scopes_on_relations # Topic.replied approved_topics = Topic.all.approved.order('id DESC') assert_equal topics(:fifth), approved_topics.first replied_approved_topics = approved_topics.replied assert_equal topics(:third), replied_approved_topics.first end def test_index_on_scope approved = Topic.approved.order('id ASC') assert_equal topics(:second), approved[0] assert approved.loaded? end def test_nested_scopes_queries_size assert_queries(1) do Topic.approved.by_lifo.replied.written_before(Time.now).to_a end end # Note: these next two are kinda odd because they are essentially just testing that the # query cache works as it should, but they are here for legacy reasons as they was previously # a separate cache on association proxies, and these show that that is not necessary. def test_scopes_are_cached_on_associations post = posts(:welcome) Post.cache do assert_queries(1) { post.comments.containing_the_letter_e.to_a } assert_no_queries { post.comments.containing_the_letter_e.to_a } end end def test_scopes_with_arguments_are_cached_on_associations post = posts(:welcome) Post.cache do one = assert_queries(1) { post.comments.limit_by(1).to_a } assert_equal 1, one.size two = assert_queries(1) { post.comments.limit_by(2).to_a } assert_equal 2, two.size assert_no_queries { post.comments.limit_by(1).to_a } assert_no_queries { post.comments.limit_by(2).to_a } end end def test_scopes_to_get_newest post = posts(:welcome) old_last_comment = post.comments.newest new_comment = post.comments.create(:body => "My new comment") assert_equal new_comment, post.comments.newest assert_not_equal old_last_comment, post.comments.newest end def test_scopes_are_reset_on_association_reload post = posts(:welcome) [:destroy_all, :reset, :delete_all].each do |method| before = post.comments.containing_the_letter_e post.association(:comments).send(method) assert before.object_id != post.comments.containing_the_letter_e.object_id, "CollectionAssociation##{method} should reset the named scopes cache" end end def test_scoped_are_lazy_loaded_if_table_still_does_not_exist assert_nothing_raised do require "models/without_table" end end def test_eager_default_scope_relations_are_remove klass = Class.new(ActiveRecord::Base) klass.table_name = 'posts' assert_raises(ArgumentError) do klass.send(:default_scope, klass.where(:id => posts(:welcome).id)) end end def test_subclass_merges_scopes_properly assert_equal 1, SpecialComment.where(body: 'go crazy').created.count end end rails-4.2.6/activerecord/test/cases/scoping/relation_scoping_test.rb000066400000000000000000000254261266740050600257700ustar00rootroot00000000000000require "cases/helper" require 'models/post' require 'models/author' require 'models/developer' require 'models/computer' require 'models/project' require 'models/comment' require 'models/category' require 'models/person' require 'models/reference' class RelationScopingTest < ActiveRecord::TestCase fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects setup do developers(:david) end def test_unscoped_breaks_caching author = authors :mary assert_nil author.first_post post = FirstPost.unscoped do author.reload.first_post end assert post end def test_scope_breaks_caching_on_collections author = authors :david ids = author.reload.special_posts_with_default_scope.map(&:id) assert_equal [1,5,6], ids.sort scoped_posts = SpecialPostWithDefaultScope.unscoped do author = authors :david author.reload.special_posts_with_default_scope.to_a end assert_equal author.posts.map(&:id).sort, scoped_posts.map(&:id).sort end def test_reverse_order assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order end def test_reverse_order_with_arel_node assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order end def test_reverse_order_with_multiple_arel_nodes assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order end def test_reverse_order_with_arel_nodes_and_strings assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order end def test_double_reverse_order_produces_original_order assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order end def test_scoped_find Developer.where("name = 'David'").scoping do assert_nothing_raised { Developer.find(1) } end end def test_scoped_find_first developer = Developer.find(10) Developer.where("salary = 100000").scoping do assert_equal developer, Developer.order("name").first end end def test_scoped_find_last highest_salary = Developer.order("salary DESC").first Developer.order("salary").scoping do assert_equal highest_salary, Developer.last end end def test_scoped_find_last_preserves_scope lowest_salary = Developer.order("salary ASC").first highest_salary = Developer.order("salary DESC").first Developer.order("salary").scoping do assert_equal highest_salary, Developer.last assert_equal lowest_salary, Developer.first end end def test_scoped_find_combines_and_sanitizes_conditions Developer.where("salary = 9000").scoping do assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first end end def test_scoped_find_all Developer.where("name = 'David'").scoping do assert_equal [developers(:david)], Developer.all end end def test_scoped_find_select Developer.select("id, name").scoping do developer = Developer.where("name = 'David'").first assert_equal "David", developer.name assert !developer.has_attribute?(:salary) end end def test_scope_select_concatenates Developer.select("id, name").scoping do developer = Developer.select('salary').where("name = 'David'").first assert_equal 80000, developer.salary assert developer.has_attribute?(:id) assert developer.has_attribute?(:name) assert developer.has_attribute?(:salary) end end def test_scoped_count Developer.where("name = 'David'").scoping do assert_equal 1, Developer.count end Developer.where('salary = 100000').scoping do assert_equal 8, Developer.count assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count end end def test_scoped_find_include # with the include, will retrieve only developers for the given project scoped_developers = Developer.includes(:projects).scoping do Developer.where('projects.id' => 2).to_a end assert scoped_developers.include?(developers(:david)) assert !scoped_developers.include?(developers(:jamis)) assert_equal 1, scoped_developers.size end def test_scoped_find_joins scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do Developer.where('developers_projects.project_id = 2').to_a end assert scoped_developers.include?(developers(:david)) assert !scoped_developers.include?(developers(:jamis)) assert_equal 1, scoped_developers.size assert_equal developers(:david).attributes, scoped_developers.first.attributes end def test_scoped_create_with_where new_comment = VerySpecialComment.where(:post_id => 1).scoping do VerySpecialComment.create :body => "Wonderful world" end assert_equal 1, new_comment.post_id assert Post.find(1).comments.include?(new_comment) end def test_scoped_create_with_create_with new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do VerySpecialComment.create :body => "Wonderful world" end assert_equal 1, new_comment.post_id assert Post.find(1).comments.include?(new_comment) end def test_scoped_create_with_create_with_has_higher_priority new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do VerySpecialComment.create :body => "Wonderful world" end assert_equal 1, new_comment.post_id assert Post.find(1).comments.include?(new_comment) end def test_ensure_that_method_scoping_is_correctly_restored begin Developer.where("name = 'Jamis'").scoping do raise "an exception" end rescue end assert !Developer.all.where_values.include?("name = 'Jamis'") end def test_default_scope_filters_on_joins assert_equal 1, DeveloperFilteredOnJoins.all.count assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins) end def test_update_all_default_scope_filters_on_joins DeveloperFilteredOnJoins.update_all(:salary => 65000) assert_equal 65000, Developer.find(developers(:david).id).salary # has not changed jamis assert_not_equal 65000, Developer.find(developers(:jamis).id).salary end def test_delete_all_default_scope_filters_on_joins assert_not_equal [], DeveloperFilteredOnJoins.all DeveloperFilteredOnJoins.delete_all() assert_equal [], DeveloperFilteredOnJoins.all assert_not_equal [], Developer.all end end class NestedRelationScopingTest < ActiveRecord::TestCase fixtures :authors, :developers, :projects, :comments, :posts def test_merge_options Developer.where('salary = 80000').scoping do Developer.limit(10).scoping do devs = Developer.all sql = devs.to_sql assert_match '(salary = 80000)', sql assert_match 'LIMIT 10', sql end end end def test_merge_inner_scope_has_priority Developer.limit(5).scoping do Developer.limit(10).scoping do assert_equal 10, Developer.all.size end end end def test_replace_options Developer.where(:name => 'David').scoping do Developer.unscoped do assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name] end assert_equal 'David', Developer.first[:name] end end def test_three_level_nested_exclusive_scoped_find Developer.where("name = 'Jamis'").scoping do assert_equal 'Jamis', Developer.first.name Developer.unscoped.where("name = 'David'") do assert_equal 'David', Developer.first.name Developer.unscoped.where("name = 'Maiha'") do assert_equal nil, Developer.first end # ensure that scoping is restored assert_equal 'David', Developer.first.name end # ensure that scoping is restored assert_equal 'Jamis', Developer.first.name end end def test_nested_scoped_create comment = Comment.create_with(:post_id => 1).scoping do Comment.create_with(:post_id => 2).scoping do Comment.create :body => "Hey guys, nested scopes are broken. Please fix!" end end assert_equal 2, comment.post_id end def test_nested_exclusive_scope_for_create comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do Comment.unscoped.create_with(:post_id => 1).scoping do assert Comment.new.body.blank? Comment.create :body => "Hey guys" end end assert_equal 1, comment.post_id assert_equal 'Hey guys', comment.body end end class HasManyScopingTest < ActiveRecord::TestCase fixtures :comments, :posts, :people, :references def setup @welcome = Post.find(1) end def test_forwarding_of_static_methods assert_equal 'a comment...', Comment.what_are_you assert_equal 'a comment...', @welcome.comments.what_are_you end def test_forwarding_to_scoped assert_equal 4, Comment.search_by_type('Comment').size assert_equal 2, @welcome.comments.search_by_type('Comment').size end def test_nested_scope_finder Comment.where('1=0').scoping do assert_equal 0, @welcome.comments.count assert_equal 'a comment...', @welcome.comments.what_are_you end Comment.where('1=1').scoping do assert_equal 2, @welcome.comments.count assert_equal 'a comment...', @welcome.comments.what_are_you end end def test_should_maintain_default_scope_on_associations magician = BadReference.find(1) assert_equal [magician], people(:michael).bad_references end def test_should_default_scope_on_associations_is_overridden_by_association_conditions reference = references(:michael_unicyclist).becomes(BadReference) assert_equal [reference], people(:michael).fixed_bad_references end def test_should_maintain_default_scope_on_eager_loaded_associations michael = Person.where(:id => people(:michael).id).includes(:bad_references).first magician = BadReference.find(1) assert_equal [magician], michael.bad_references end end class HasAndBelongsToManyScopingTest < ActiveRecord::TestCase fixtures :posts, :categories, :categories_posts def setup @welcome = Post.find(1) end def test_forwarding_of_static_methods assert_equal 'a category...', Category.what_are_you assert_equal 'a category...', @welcome.categories.what_are_you end def test_nested_scope_finder Category.where('1=0').scoping do assert_equal 0, @welcome.categories.count assert_equal 'a category...', @welcome.categories.what_are_you end Category.where('1=1').scoping do assert_equal 2, @welcome.categories.count assert_equal 'a category...', @welcome.categories.what_are_you end end end rails-4.2.6/activerecord/test/cases/serialization_test.rb000066400000000000000000000067441266740050600236460ustar00rootroot00000000000000require "cases/helper" require 'models/contact' require 'models/topic' require 'models/book' require 'models/author' require 'models/post' class SerializationTest < ActiveRecord::TestCase fixtures :books FORMATS = [ :xml, :json ] def setup @contact_attributes = { :name => 'aaron stack', :age => 25, :avatar => 'binarydata', :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => 'ruby' }, :alternative_id => nil, :id => nil } end def test_include_root_in_json_is_false_by_default assert_equal false, ActiveRecord::Base.include_root_in_json, "include_root_in_json should be false by default but was not" end def test_serialize_should_be_reversible FORMATS.each do |format| @serialized = Contact.new.send("to_#{format}") contact = Contact.new.send("from_#{format}", @serialized) assert_equal @contact_attributes.keys.collect(&:to_s).sort, contact.attributes.keys.collect(&:to_s).sort, "For #{format}" end end def test_serialize_should_allow_attribute_only_filtering FORMATS.each do |format| @serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_equal @contact_attributes[:name], contact.name, "For #{format}" assert_nil contact.avatar, "For #{format}" end end def test_serialize_should_allow_attribute_except_filtering FORMATS.each do |format| @serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_nil contact.name, "For #{format}" assert_nil contact.age, "For #{format}" assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}" end end def test_include_root_in_json_allows_inheritance original_root_in_json = ActiveRecord::Base.include_root_in_json ActiveRecord::Base.include_root_in_json = true klazz = Class.new(ActiveRecord::Base) klazz.table_name = 'topics' assert klazz.include_root_in_json klazz.include_root_in_json = false assert ActiveRecord::Base.include_root_in_json assert !klazz.include_root_in_json assert !klazz.new.include_root_in_json ensure ActiveRecord::Base.include_root_in_json = original_root_in_json end def test_read_attribute_for_serialization_with_format_without_method_missing klazz = Class.new(ActiveRecord::Base) klazz.table_name = 'books' book = klazz.new assert_nil book.read_attribute_for_serialization(:format) end def test_read_attribute_for_serialization_with_format_after_init klazz = Class.new(ActiveRecord::Base) klazz.table_name = 'books' book = klazz.new(format: 'paperback') assert_equal 'paperback', book.read_attribute_for_serialization(:format) end def test_read_attribute_for_serialization_with_format_after_find klazz = Class.new(ActiveRecord::Base) klazz.table_name = 'books' book = klazz.find(books(:awdr).id) assert_equal 'paperback', book.read_attribute_for_serialization(:format) end def test_find_records_by_serialized_attributes_through_join author = Author.create!(name: "David") author.serialized_posts.create!(title: "Hello") assert_equal 1, Author.joins(:serialized_posts).where(name: "David", serialized_posts: { title: "Hello" }).length end end rails-4.2.6/activerecord/test/cases/serialized_attribute_test.rb000066400000000000000000000172071266740050600252030ustar00rootroot00000000000000require 'cases/helper' require 'models/topic' require 'models/reply' require 'models/person' require 'models/traffic_light' require 'models/post' require 'bcrypt' class SerializedAttributeTest < ActiveRecord::TestCase fixtures :topics, :posts MyObject = Struct.new :attribute1, :attribute2 teardown do Topic.serialize("content") end def test_serialize_does_not_eagerly_load_columns Topic.reset_column_information assert_no_queries do Topic.serialize(:content) end end def test_list_of_serialized_attributes assert_deprecated do assert_equal %w(content), Topic.serialized_attributes.keys end end def test_serialized_attribute Topic.serialize("content", MyObject) myobj = MyObject.new('value1', 'value2') topic = Topic.create("content" => myobj) assert_equal(myobj, topic.content) topic.reload assert_equal(myobj, topic.content) end def test_serialized_attribute_in_base_class Topic.serialize("content", Hash) hash = { 'content1' => 'value1', 'content2' => 'value2' } important_topic = ImportantTopic.create("content" => hash) assert_equal(hash, important_topic.content) important_topic.reload assert_equal(hash, important_topic.content) end def test_serialized_attributes_from_database_on_subclass Topic.serialize :content, Hash t = Reply.new(content: { foo: :bar }) assert_equal({ foo: :bar }, t.content) t.save! t = Reply.last assert_equal({ foo: :bar }, t.content) end def test_serialized_attribute_calling_dup_method Topic.serialize :content, JSON orig = Topic.new(content: { foo: :bar }) clone = orig.dup assert_equal(orig.content, clone.content) end def test_serialized_json_attribute_returns_unserialized_value Topic.serialize :content, JSON my_post = posts(:welcome) t = Topic.new(content: my_post) t.save! t.reload assert_instance_of(Hash, t.content) assert_equal(my_post.id, t.content["id"]) assert_equal(my_post.title, t.content["title"]) end def test_json_read_legacy_null Topic.serialize :content, JSON # Force a row to have a JSON "null" instead of a database NULL (this is how # null values are saved on 4.1 and before) id = Topic.connection.insert "INSERT INTO topics (content) VALUES('null')" t = Topic.find(id) assert_nil t.content end def test_json_read_db_null Topic.serialize :content, JSON # Force a row to have a database NULL instead of a JSON "null" id = Topic.connection.insert "INSERT INTO topics (content) VALUES(NULL)" t = Topic.find(id) assert_nil t.content end def test_serialized_attribute_declared_in_subclass hash = { 'important1' => 'value1', 'important2' => 'value2' } important_topic = ImportantTopic.create("important" => hash) assert_equal(hash, important_topic.important) important_topic.reload assert_equal(hash, important_topic.important) assert_equal(hash, important_topic.read_attribute(:important)) end def test_serialized_time_attribute myobj = Time.local(2008,1,1,1,0) topic = Topic.create("content" => myobj).reload assert_equal(myobj, topic.content) end def test_serialized_string_attribute myobj = "Yes" topic = Topic.create("content" => myobj).reload assert_equal(myobj, topic.content) end def test_nil_serialized_attribute_without_class_constraint topic = Topic.new assert_nil topic.content end def test_nil_not_serialized_without_class_constraint assert Topic.new(:content => nil).save assert_equal 1, Topic.where(:content => nil).count end def test_nil_not_serialized_with_class_constraint Topic.serialize :content, Hash assert Topic.new(:content => nil).save assert_equal 1, Topic.where(:content => nil).count end def test_serialized_attribute_should_raise_exception_on_assignment_with_wrong_type Topic.serialize(:content, Hash) assert_raise(ActiveRecord::SerializationTypeMismatch) do Topic.new(content: 'string') end end def test_should_raise_exception_on_serialized_attribute_with_type_mismatch myobj = MyObject.new('value1', 'value2') topic = Topic.new(:content => myobj) assert topic.save Topic.serialize(:content, Hash) assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content } end def test_serialized_attribute_with_class_constraint settings = { "color" => "blue" } Topic.serialize(:content, Hash) topic = Topic.new(:content => settings) assert topic.save assert_equal(settings, Topic.find(topic.id).content) end def test_serialized_default_class Topic.serialize(:content, Hash) topic = Topic.new assert_equal Hash, topic.content.class assert_equal Hash, topic.read_attribute(:content).class topic.content["beer"] = "MadridRb" assert topic.save topic.reload assert_equal Hash, topic.content.class assert_equal "MadridRb", topic.content["beer"] end def test_serialized_no_default_class_for_object topic = Topic.new assert_nil topic.content end def test_serialized_boolean_value_true topic = Topic.new(:content => true) assert topic.save topic = topic.reload assert_equal topic.content, true end def test_serialized_boolean_value_false topic = Topic.new(:content => false) assert topic.save topic = topic.reload assert_equal topic.content, false end def test_serialize_with_coder some_class = Struct.new(:foo) do def self.dump(value) value.foo end def self.load(value) new(value) end end Topic.serialize(:content, some_class) topic = Topic.new(:content => some_class.new('my value')) topic.save! topic.reload assert_kind_of some_class, topic.content assert_equal topic.content, some_class.new('my value') end def test_serialize_attribute_via_select_method_when_time_zone_available with_timezone_config aware_attributes: true do Topic.serialize(:content, MyObject) myobj = MyObject.new('value1', 'value2') topic = Topic.create(content: myobj) assert_equal(myobj, Topic.select(:content).find(topic.id).content) assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content } end end def test_serialize_attribute_can_be_serialized_in_an_integer_column insures = ['life'] person = SerializedPerson.new(first_name: 'David', insures: insures) assert person.save person = person.reload assert_equal(insures, person.insures) end def test_regression_serialized_default_on_text_column_with_null_false light = TrafficLight.new assert_equal [], light.state assert_equal [], light.long_state end def test_serialized_column_should_unserialize_after_update_column t = Topic.create(content: "first") assert_equal("first", t.content) t.update_column(:content, ["second"]) assert_equal(["second"], t.content) assert_equal(["second"], t.reload.content) end def test_serialized_column_should_unserialize_after_update_attribute t = Topic.create(content: "first") assert_equal("first", t.content) t.update_attribute(:content, "second") assert_equal("second", t.content) assert_equal("second", t.reload.content) end def test_nil_is_not_changed_when_serialized_with_a_class Topic.serialize(:content, Array) topic = Topic.new(content: nil) assert_not topic.content_changed? end def test_newly_emptied_serialized_hash_is_changed Topic.serialize(:content, Hash) topic = Topic.create(content: { "things" => "stuff" }) topic.content.delete("things") topic.save! topic.reload assert_equal({}, topic.content) end end rails-4.2.6/activerecord/test/cases/statement_cache_test.rb000066400000000000000000000053711266740050600241130ustar00rootroot00000000000000require 'cases/helper' require 'models/book' require 'models/liquid' require 'models/molecule' require 'models/electron' module ActiveRecord class StatementCacheTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection end #Cache v 1.1 tests def test_statement_cache Book.create(name: "my book") Book.create(name: "my other book") cache = StatementCache.create(Book.connection) do |params| Book.where(:name => params.bind) end b = cache.execute([ "my book" ], Book, Book.connection) assert_equal "my book", b[0].name b = cache.execute([ "my other book" ], Book, Book.connection) assert_equal "my other book", b[0].name end def test_statement_cache_id b1 = Book.create(name: "my book") b2 = Book.create(name: "my other book") cache = StatementCache.create(Book.connection) do |params| Book.where(id: params.bind) end b = cache.execute([ b1.id ], Book, Book.connection) assert_equal b1.name, b[0].name b = cache.execute([ b2.id ], Book, Book.connection) assert_equal b2.name, b[0].name end def test_find_or_create_by Book.create(name: "my book") a = Book.find_or_create_by(name: "my book") b = Book.find_or_create_by(name: "my other book") assert_equal("my book", a.name) assert_equal("my other book", b.name) end #End def test_statement_cache_with_simple_statement cache = ActiveRecord::StatementCache.create(Book.connection) do |params| Book.where(name: "my book").where("author_id > 3") end Book.create(name: "my book", author_id: 4) books = cache.execute([], Book, Book.connection) assert_equal "my book", books[0].name end def test_statement_cache_with_complex_statement cache = ActiveRecord::StatementCache.create(Book.connection) do |params| Liquid.joins(:molecules => :electrons).where('molecules.name' => 'dioxane', 'electrons.name' => 'lepton') end salty = Liquid.create(name: 'salty') molecule = salty.molecules.create(name: 'dioxane') molecule.electrons.create(name: 'lepton') liquids = cache.execute([], Book, Book.connection) assert_equal "salty", liquids[0].name end def test_statement_cache_values_differ cache = ActiveRecord::StatementCache.create(Book.connection) do |params| Book.where(name: "my book") end 3.times do Book.create(name: "my book") end first_books = cache.execute([], Book, Book.connection) 3.times do Book.create(name: "my book") end additional_books = cache.execute([], Book, Book.connection) assert first_books != additional_books end end end rails-4.2.6/activerecord/test/cases/store_test.rb000066400000000000000000000143521266740050600221170ustar00rootroot00000000000000require 'cases/helper' require 'models/admin' require 'models/admin/user' class StoreTest < ActiveRecord::TestCase fixtures :'admin/users' setup do @john = Admin::User.create!(:name => 'John Doe', :color => 'black', :remember_login => true, :height => 'tall', :is_a_good_guy => true) end test "reading store attributes through accessors" do assert_equal 'black', @john.color assert_nil @john.homepage end test "writing store attributes through accessors" do @john.color = 'red' @john.homepage = '37signals.com' assert_equal 'red', @john.color assert_equal '37signals.com', @john.homepage end test "accessing attributes not exposed by accessors" do @john.settings[:icecream] = 'graeters' @john.save assert_equal 'graeters', @john.reload.settings[:icecream] end test "overriding a read accessor" do @john.settings[:phone_number] = '1234567890' assert_equal '(123) 456-7890', @john.phone_number end test "overriding a read accessor using super" do @john.settings[:color] = nil assert_equal 'red', @john.color end test "updating the store will mark it as changed" do @john.color = 'red' assert @john.settings_changed? end test "updating the store populates the changed array correctly" do @john.color = 'red' assert_equal 'black', @john.settings_change[0]['color'] assert_equal 'red', @john.settings_change[1]['color'] end test "updating the store won't mark it as changed if an attribute isn't changed" do @john.color = @john.color assert !@john.settings_changed? end test "object initialization with not nullable column" do assert_equal true, @john.remember_login end test "writing with not nullable column" do @john.remember_login = false assert_equal false, @john.remember_login end test "overriding a write accessor" do @john.phone_number = '(123) 456-7890' assert_equal '1234567890', @john.settings[:phone_number] end test "overriding a write accessor using super" do @john.color = 'yellow' assert_equal 'blue', @john.color end test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do @john.json_data = ActiveSupport::HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy') @john.height = 'low' assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess) assert_equal 'low', @john.json_data[:height] assert_equal 'low', @john.json_data['height'] assert_equal 'heavy', @john.json_data[:weight] assert_equal 'heavy', @john.json_data['weight'] end test "convert store attributes from Hash to HashWithIndifferentAccess saving the data and access attributes indifferently" do user = Admin::User.find_by_name('Jamis') assert_equal 'symbol', user.settings[:symbol] assert_equal 'symbol', user.settings['symbol'] assert_equal 'string', user.settings[:string] assert_equal 'string', user.settings['string'] assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess) user.height = 'low' assert_equal 'symbol', user.settings[:symbol] assert_equal 'symbol', user.settings['symbol'] assert_equal 'string', user.settings[:string] assert_equal 'string', user.settings['string'] assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess) end test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do @john.json_data = "somedata" @john.height = 'low' assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess) assert_equal 'low', @john.json_data[:height] assert_equal 'low', @john.json_data['height'] assert_equal false, @john.json_data.delete_if { |k, v| k == 'height' }.any? end test "reading store attributes through accessors encoded with JSON" do assert_equal 'tall', @john.height assert_nil @john.weight end test "writing store attributes through accessors encoded with JSON" do @john.height = 'short' @john.weight = 'heavy' assert_equal 'short', @john.height assert_equal 'heavy', @john.weight end test "accessing attributes not exposed by accessors encoded with JSON" do @john.json_data['somestuff'] = 'somecoolstuff' @john.save assert_equal 'somecoolstuff', @john.reload.json_data['somestuff'] end test "updating the store will mark it as changed encoded with JSON" do @john.height = 'short' assert @john.json_data_changed? end test "object initialization with not nullable column encoded with JSON" do assert_equal true, @john.is_a_good_guy end test "writing with not nullable column encoded with JSON" do @john.is_a_good_guy = false assert_equal false, @john.is_a_good_guy end test "all stored attributes are returned" do assert_equal [:color, :homepage, :favorite_food], Admin::User.stored_attributes[:settings] end test "stored_attributes are tracked per class" do first_model = Class.new(ActiveRecord::Base) do store_accessor :data, :color end second_model = Class.new(ActiveRecord::Base) do store_accessor :data, :width, :height end assert_equal [:color], first_model.stored_attributes[:data] assert_equal [:width, :height], second_model.stored_attributes[:data] end test "stored_attributes are tracked per subclass" do first_model = Class.new(ActiveRecord::Base) do store_accessor :data, :color end second_model = Class.new(first_model) do store_accessor :data, :width, :height end third_model = Class.new(first_model) do store_accessor :data, :area, :volume end assert_equal [:color], first_model.stored_attributes[:data] assert_equal [:color, :width, :height], second_model.stored_attributes[:data] assert_equal [:color, :area, :volume], third_model.stored_attributes[:data] end test "YAML coder initializes the store when a Nil value is given" do assert_equal({}, @john.params) end test "dump, load and dump again a model" do dumped = YAML.dump(@john) loaded = YAML.load(dumped) assert_equal @john, loaded second_dump = YAML.dump(loaded) assert_equal @john, YAML.load(second_dump) end end rails-4.2.6/activerecord/test/cases/tasks/000077500000000000000000000000001266740050600205175ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/tasks/database_tasks_test.rb000066400000000000000000000311561266740050600250620ustar00rootroot00000000000000require 'cases/helper' require 'active_record/tasks/database_tasks' module ActiveRecord module DatabaseTasksSetupper def setup @mysql_tasks, @postgresql_tasks, @sqlite_tasks = stub, stub, stub ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks end end ADAPTERS_TASKS = { mysql: :mysql_tasks, mysql2: :mysql_tasks, postgresql: :postgresql_tasks, sqlite3: :sqlite_tasks } class DatabaseTasksRegisterTask < ActiveRecord::TestCase def test_register_task klazz = Class.new do def initialize(*arguments); end def structure_dump(filename); end end instance = klazz.new klazz.stubs(:new).returns instance instance.expects(:structure_dump).with("awesome-file.sql") ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz) ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :foo}, "awesome-file.sql") end def test_unregistered_task assert_raise(ActiveRecord::Tasks::DatabaseNotSupported) do ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :bar}, "awesome-file.sql") end end end class DatabaseTasksCreateTest < ActiveRecord::TestCase include DatabaseTasksSetupper ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_create") do eval("@#{v}").expects(:create) ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => k end end end class DatabaseTasksCreateAllTest < ActiveRecord::TestCase def setup @configurations = {'development' => {'database' => 'my-db'}} ActiveRecord::Base.stubs(:configurations).returns(@configurations) end def test_ignores_configurations_without_databases @configurations['development'].merge!('database' => nil) ActiveRecord::Tasks::DatabaseTasks.expects(:create).never ActiveRecord::Tasks::DatabaseTasks.create_all end def test_ignores_remote_databases @configurations['development'].merge!('host' => 'my.server.tld') $stderr.stubs(:puts).returns(nil) ActiveRecord::Tasks::DatabaseTasks.expects(:create).never ActiveRecord::Tasks::DatabaseTasks.create_all end def test_warning_for_remote_databases @configurations['development'].merge!('host' => 'my.server.tld') $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.') ActiveRecord::Tasks::DatabaseTasks.create_all end def test_creates_configurations_with_local_ip @configurations['development'].merge!('host' => '127.0.0.1') ActiveRecord::Tasks::DatabaseTasks.expects(:create) ActiveRecord::Tasks::DatabaseTasks.create_all end def test_creates_configurations_with_local_host @configurations['development'].merge!('host' => 'localhost') ActiveRecord::Tasks::DatabaseTasks.expects(:create) ActiveRecord::Tasks::DatabaseTasks.create_all end def test_creates_configurations_with_blank_hosts @configurations['development'].merge!('host' => nil) ActiveRecord::Tasks::DatabaseTasks.expects(:create) ActiveRecord::Tasks::DatabaseTasks.create_all end end class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase def setup @configurations = { 'development' => {'database' => 'dev-db'}, 'test' => {'database' => 'test-db'}, 'production' => {'database' => 'prod-db'} } ActiveRecord::Base.stubs(:configurations).returns(@configurations) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_creates_current_environment_database ActiveRecord::Tasks::DatabaseTasks.expects(:create). with('database' => 'prod-db') ActiveRecord::Tasks::DatabaseTasks.create_current( ActiveSupport::StringInquirer.new('production') ) end def test_creates_test_and_development_databases_when_env_was_not_specified ActiveRecord::Tasks::DatabaseTasks.expects(:create). with('database' => 'dev-db') ActiveRecord::Tasks::DatabaseTasks.expects(:create). with('database' => 'test-db') ENV.expects(:[]).with('RAILS_ENV').returns(nil) ActiveRecord::Tasks::DatabaseTasks.create_current( ActiveSupport::StringInquirer.new('development') ) end def test_creates_only_development_database_when_rails_env_is_development ActiveRecord::Tasks::DatabaseTasks.expects(:create). with('database' => 'dev-db') ENV.expects(:[]).with('RAILS_ENV').returns('development') ActiveRecord::Tasks::DatabaseTasks.create_current( ActiveSupport::StringInquirer.new('development') ) end def test_establishes_connection_for_the_given_environment ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true ActiveRecord::Base.expects(:establish_connection).with(:development) ActiveRecord::Tasks::DatabaseTasks.create_current( ActiveSupport::StringInquirer.new('development') ) end end class DatabaseTasksDropTest < ActiveRecord::TestCase include DatabaseTasksSetupper ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_drop") do eval("@#{v}").expects(:drop) ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => k end end end class DatabaseTasksDropAllTest < ActiveRecord::TestCase def setup @configurations = {:development => {'database' => 'my-db'}} ActiveRecord::Base.stubs(:configurations).returns(@configurations) end def test_ignores_configurations_without_databases @configurations[:development].merge!('database' => nil) ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never ActiveRecord::Tasks::DatabaseTasks.drop_all end def test_ignores_remote_databases @configurations[:development].merge!('host' => 'my.server.tld') $stderr.stubs(:puts).returns(nil) ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never ActiveRecord::Tasks::DatabaseTasks.drop_all end def test_warning_for_remote_databases @configurations[:development].merge!('host' => 'my.server.tld') $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.') ActiveRecord::Tasks::DatabaseTasks.drop_all end def test_drops_configurations_with_local_ip @configurations[:development].merge!('host' => '127.0.0.1') ActiveRecord::Tasks::DatabaseTasks.expects(:drop) ActiveRecord::Tasks::DatabaseTasks.drop_all end def test_drops_configurations_with_local_host @configurations[:development].merge!('host' => 'localhost') ActiveRecord::Tasks::DatabaseTasks.expects(:drop) ActiveRecord::Tasks::DatabaseTasks.drop_all end def test_drops_configurations_with_blank_hosts @configurations[:development].merge!('host' => nil) ActiveRecord::Tasks::DatabaseTasks.expects(:drop) ActiveRecord::Tasks::DatabaseTasks.drop_all end end class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase def setup @configurations = { 'development' => {'database' => 'dev-db'}, 'test' => {'database' => 'test-db'}, 'production' => {'database' => 'prod-db'} } ActiveRecord::Base.stubs(:configurations).returns(@configurations) end def test_drops_current_environment_database ActiveRecord::Tasks::DatabaseTasks.expects(:drop). with('database' => 'prod-db') ActiveRecord::Tasks::DatabaseTasks.drop_current( ActiveSupport::StringInquirer.new('production') ) end def test_drops_test_and_development_databases_when_env_was_not_specified ActiveRecord::Tasks::DatabaseTasks.expects(:drop). with('database' => 'dev-db') ActiveRecord::Tasks::DatabaseTasks.expects(:drop). with('database' => 'test-db') ENV.expects(:[]).with('RAILS_ENV').returns(nil) ActiveRecord::Tasks::DatabaseTasks.drop_current( ActiveSupport::StringInquirer.new('development') ) end def test_drops_only_development_database_when_rails_env_is_development ActiveRecord::Tasks::DatabaseTasks.expects(:drop). with('database' => 'dev-db') ENV.expects(:[]).with('RAILS_ENV').returns('development') ActiveRecord::Tasks::DatabaseTasks.drop_current( ActiveSupport::StringInquirer.new('development') ) end end class DatabaseTasksMigrateTest < ActiveRecord::TestCase def test_migrate_receives_correct_env_vars verbose, version = ENV['VERBOSE'], ENV['VERSION'] ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path' ENV['VERBOSE'] = 'false' ENV['VERSION'] = '4' ActiveRecord::Migrator.expects(:migrate).with('custom/path', 4) ActiveRecord::Tasks::DatabaseTasks.migrate ensure ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil ENV['VERBOSE'], ENV['VERSION'] = verbose, version end end class DatabaseTasksPurgeTest < ActiveRecord::TestCase include DatabaseTasksSetupper ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_purge") do eval("@#{v}").expects(:purge) ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => k end end end class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase def test_purges_current_environment_database configurations = { 'development' => {'database' => 'dev-db'}, 'test' => {'database' => 'test-db'}, 'production' => {'database' => 'prod-db'} } ActiveRecord::Base.stubs(:configurations).returns(configurations) ActiveRecord::Tasks::DatabaseTasks.expects(:purge). with('database' => 'prod-db') ActiveRecord::Base.expects(:establish_connection).with(:production) ActiveRecord::Tasks::DatabaseTasks.purge_current('production') end end class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase def test_purge_all_local_configurations configurations = {:development => {'database' => 'my-db'}} ActiveRecord::Base.stubs(:configurations).returns(configurations) ActiveRecord::Tasks::DatabaseTasks.expects(:purge). with('database' => 'my-db') ActiveRecord::Tasks::DatabaseTasks.purge_all end end class DatabaseTasksCharsetTest < ActiveRecord::TestCase include DatabaseTasksSetupper ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_charset") do eval("@#{v}").expects(:charset) ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => k end end end class DatabaseTasksCollationTest < ActiveRecord::TestCase include DatabaseTasksSetupper ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_collation") do eval("@#{v}").expects(:collation) ActiveRecord::Tasks::DatabaseTasks.collation 'adapter' => k end end end class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase include DatabaseTasksSetupper ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_structure_dump") do eval("@#{v}").expects(:structure_dump).with("awesome-file.sql") ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => k}, "awesome-file.sql") end end end class DatabaseTasksStructureLoadTest < ActiveRecord::TestCase include DatabaseTasksSetupper ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_structure_load") do eval("@#{v}").expects(:structure_load).with("awesome-file.sql") ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => k}, "awesome-file.sql") end end end class DatabaseTasksCheckSchemaFileTest < ActiveRecord::TestCase def test_check_schema_file Kernel.expects(:abort).with(regexp_matches(/awesome-file.sql/)) ActiveRecord::Tasks::DatabaseTasks.check_schema_file("awesome-file.sql") end end class DatabaseTasksCheckSchemaFileDefaultsTest < ActiveRecord::TestCase def test_check_schema_file_defaults ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns('/tmp') assert_equal '/tmp/schema.rb', ActiveRecord::Tasks::DatabaseTasks.schema_file end end class DatabaseTasksCheckSchemaFileSpecifiedFormatsTest < ActiveRecord::TestCase {ruby: 'schema.rb', sql: 'structure.sql'}.each_pair do |fmt, filename| define_method("test_check_schema_file_for_#{fmt}_format") do ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns('/tmp') assert_equal "/tmp/#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_file(fmt) end end end end rails-4.2.6/activerecord/test/cases/tasks/mysql_rake_test.rb000066400000000000000000000246251266740050600242630ustar00rootroot00000000000000require 'cases/helper' if current_adapter?(:MysqlAdapter, :Mysql2Adapter) module ActiveRecord class MysqlDBCreateTest < ActiveRecord::TestCase def setup @connection = stub(:create_database => true) @configuration = { 'adapter' => 'mysql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_establishes_connection_without_database ActiveRecord::Base.expects(:establish_connection). with('adapter' => 'mysql', 'database' => nil) ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_creates_database_with_default_encoding_and_collation @connection.expects(:create_database). with('my-app-db', charset: 'utf8', collation: 'utf8_unicode_ci') ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_creates_database_with_given_encoding_and_default_collation @connection.expects(:create_database). with('my-app-db', charset: 'utf8', collation: 'utf8_unicode_ci') ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('encoding' => 'utf8') end def test_creates_database_with_given_encoding_and_no_collation @connection.expects(:create_database). with('my-app-db', charset: 'latin1') ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('encoding' => 'latin1') end def test_creates_database_with_given_collation_and_no_encoding @connection.expects(:create_database). with('my-app-db', collation: 'latin1_swedish_ci') ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('collation' => 'latin1_swedish_ci') end def test_establishes_connection_to_database ActiveRecord::Base.expects(:establish_connection).with(@configuration) ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_create_when_database_exists_outputs_info_to_stderr $stderr.expects(:puts).with("my-app-db already exists").once ActiveRecord::Base.connection.stubs(:create_database).raises( ActiveRecord::StatementInvalid.new("Can't create database 'dev'; database exists:") ) ActiveRecord::Tasks::DatabaseTasks.create @configuration end end if current_adapter?(:MysqlAdapter) class MysqlDBCreateAsRootTest < ActiveRecord::TestCase def setup @connection = stub("Connection", create_database: true) @error = Mysql::Error.new "Invalid permissions" @configuration = { 'adapter' => 'mysql', 'database' => 'my-app-db', 'username' => 'pat', 'password' => 'wossname' } $stdin.stubs(:gets).returns("secret\n") $stdout.stubs(:print).returns(nil) @error.stubs(:errno).returns(1045) ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection). raises(@error). then.returns(true) end if defined?(::Mysql) def test_root_password_is_requested assert_permissions_granted_for "pat" $stdin.expects(:gets).returns("secret\n") ActiveRecord::Tasks::DatabaseTasks.create @configuration end end def test_connection_established_as_root assert_permissions_granted_for "pat" ActiveRecord::Base.expects(:establish_connection).with( 'adapter' => 'mysql', 'database' => nil, 'username' => 'root', 'password' => 'secret' ) ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_database_created_by_root assert_permissions_granted_for "pat" @connection.expects(:create_database). with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci') ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_grant_privileges_for_normal_user assert_permissions_granted_for "pat" ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_do_not_grant_privileges_for_root_user @configuration['username'] = 'root' @configuration['password'] = '' ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_connection_established_as_normal_user assert_permissions_granted_for "pat" ActiveRecord::Base.expects(:establish_connection).returns do ActiveRecord::Base.expects(:establish_connection).with( 'adapter' => 'mysql', 'database' => 'my-app-db', 'username' => 'pat', 'password' => 'secret' ) raise @error end ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_sends_output_to_stderr_when_other_errors @error.stubs(:errno).returns(42) $stderr.expects(:puts).at_least_once.returns(nil) ActiveRecord::Tasks::DatabaseTasks.create @configuration end private def assert_permissions_granted_for(db_user) db_name = @configuration['database'] db_password = @configuration['password'] @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;") end end end class MySQLDBDropTest < ActiveRecord::TestCase def setup @connection = stub(:drop_database => true) @configuration = { 'adapter' => 'mysql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_establishes_connection_to_mysql_database ActiveRecord::Base.expects(:establish_connection).with @configuration ActiveRecord::Tasks::DatabaseTasks.drop @configuration end def test_drops_database @connection.expects(:drop_database).with('my-app-db') ActiveRecord::Tasks::DatabaseTasks.drop @configuration end end class MySQLPurgeTest < ActiveRecord::TestCase def setup @connection = stub(:recreate_database => true) @configuration = { 'adapter' => 'mysql', 'database' => 'test-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_establishes_connection_to_the_appropriate_database ActiveRecord::Base.expects(:establish_connection).with(@configuration) ActiveRecord::Tasks::DatabaseTasks.purge @configuration end def test_recreates_database_with_the_default_options @connection.expects(:recreate_database). with('test-db', charset: 'utf8', collation: 'utf8_unicode_ci') ActiveRecord::Tasks::DatabaseTasks.purge @configuration end def test_recreates_database_with_the_given_options @connection.expects(:recreate_database). with('test-db', charset: 'latin', collation: 'latin1_swedish_ci') ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge( 'encoding' => 'latin', 'collation' => 'latin1_swedish_ci') end end class MysqlDBCharsetTest < ActiveRecord::TestCase def setup @connection = stub(:create_database => true) @configuration = { 'adapter' => 'mysql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_db_retrieves_charset @connection.expects(:charset) ActiveRecord::Tasks::DatabaseTasks.charset @configuration end end class MysqlDBCollationTest < ActiveRecord::TestCase def setup @connection = stub(:create_database => true) @configuration = { 'adapter' => 'mysql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_db_retrieves_collation @connection.expects(:collation) ActiveRecord::Tasks::DatabaseTasks.collation @configuration end end class MySQLStructureDumpTest < ActiveRecord::TestCase def setup @configuration = { 'adapter' => 'mysql', 'database' => 'test-db' } end def test_structure_dump filename = "awesome-file.sql" Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) end def test_warn_when_external_structure_dump_fails filename = "awesome-file.sql" Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(false) # There doesn't seem to be a good way to get a handle on a Process::Status object without actually # creating a child process, hence this to populate $? system("not_a_real_program_#{SecureRandom.hex}") assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) } end def test_warn_when_external_structure_dump_command_execution_fails filename = "awesome-file.sql" Kernel.expects(:system) .with("mysqldump", "--result-file", filename, "--no-data", "test-db") .returns(nil) assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) } end def test_structure_dump_with_port_number filename = "awesome-file.sql" Kernel.expects(:system).with("mysqldump", "--port", "10000", "--result-file", filename, "--no-data", "test-db").returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_dump( @configuration.merge('port' => 10000), filename) end end class MySQLStructureLoadTest < ActiveRecord::TestCase def setup @configuration = { 'adapter' => 'mysql', 'database' => 'test-db' } end def test_structure_load filename = "awesome-file.sql" Kernel.expects(:system).with('mysql', '--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db") .returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) end end end end rails-4.2.6/activerecord/test/cases/tasks/postgresql_rake_test.rb000066400000000000000000000174701266740050600253210ustar00rootroot00000000000000require 'cases/helper' if current_adapter?(:PostgreSQLAdapter) module ActiveRecord class PostgreSQLDBCreateTest < ActiveRecord::TestCase def setup @connection = stub(:create_database => true) @configuration = { 'adapter' => 'postgresql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_establishes_connection_to_postgresql_database ActiveRecord::Base.expects(:establish_connection).with( 'adapter' => 'postgresql', 'database' => 'postgres', 'schema_search_path' => 'public' ) ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_creates_database_with_default_encoding @connection.expects(:create_database). with('my-app-db', @configuration.merge('encoding' => 'utf8')) ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_creates_database_with_given_encoding @connection.expects(:create_database). with('my-app-db', @configuration.merge('encoding' => 'latin')) ActiveRecord::Tasks::DatabaseTasks.create @configuration. merge('encoding' => 'latin') end def test_creates_database_with_given_collation_and_ctype @connection.expects(:create_database). with('my-app-db', @configuration.merge('encoding' => 'utf8', 'collation' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8')) ActiveRecord::Tasks::DatabaseTasks.create @configuration. merge('collation' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8') end def test_establishes_connection_to_new_database ActiveRecord::Base.expects(:establish_connection).with(@configuration) ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_db_create_with_error_prints_message ActiveRecord::Base.stubs(:establish_connection).raises(Exception) $stderr.stubs(:puts).returns(true) $stderr.expects(:puts). with("Couldn't create database for #{@configuration.inspect}") ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_create_when_database_exists_outputs_info_to_stderr $stderr.expects(:puts).with("my-app-db already exists").once ActiveRecord::Base.connection.stubs(:create_database).raises( ActiveRecord::StatementInvalid.new('database "my-app-db" already exists') ) ActiveRecord::Tasks::DatabaseTasks.create @configuration end end class PostgreSQLDBDropTest < ActiveRecord::TestCase def setup @connection = stub(:drop_database => true) @configuration = { 'adapter' => 'postgresql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_establishes_connection_to_postgresql_database ActiveRecord::Base.expects(:establish_connection).with( 'adapter' => 'postgresql', 'database' => 'postgres', 'schema_search_path' => 'public' ) ActiveRecord::Tasks::DatabaseTasks.drop @configuration end def test_drops_database @connection.expects(:drop_database).with('my-app-db') ActiveRecord::Tasks::DatabaseTasks.drop @configuration end end class PostgreSQLPurgeTest < ActiveRecord::TestCase def setup @connection = stub(:create_database => true, :drop_database => true) @configuration = { 'adapter' => 'postgresql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:clear_active_connections!).returns(true) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_clears_active_connections ActiveRecord::Base.expects(:clear_active_connections!) ActiveRecord::Tasks::DatabaseTasks.purge @configuration end def test_establishes_connection_to_postgresql_database ActiveRecord::Base.expects(:establish_connection).with( 'adapter' => 'postgresql', 'database' => 'postgres', 'schema_search_path' => 'public' ) ActiveRecord::Tasks::DatabaseTasks.purge @configuration end def test_drops_database @connection.expects(:drop_database).with('my-app-db') ActiveRecord::Tasks::DatabaseTasks.purge @configuration end def test_creates_database @connection.expects(:create_database). with('my-app-db', @configuration.merge('encoding' => 'utf8')) ActiveRecord::Tasks::DatabaseTasks.purge @configuration end def test_establishes_connection ActiveRecord::Base.expects(:establish_connection).with(@configuration) ActiveRecord::Tasks::DatabaseTasks.purge @configuration end end class PostgreSQLDBCharsetTest < ActiveRecord::TestCase def setup @connection = stub(:create_database => true) @configuration = { 'adapter' => 'postgresql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_db_retrieves_charset @connection.expects(:encoding) ActiveRecord::Tasks::DatabaseTasks.charset @configuration end end class PostgreSQLDBCollationTest < ActiveRecord::TestCase def setup @connection = stub(:create_database => true) @configuration = { 'adapter' => 'postgresql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_db_retrieves_collation @connection.expects(:collation) ActiveRecord::Tasks::DatabaseTasks.collation @configuration end end class PostgreSQLStructureDumpTest < ActiveRecord::TestCase def setup @connection = stub(:structure_dump => true) @configuration = { 'adapter' => 'postgresql', 'database' => 'my-app-db' } @filename = "awesome-file.sql" ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) Kernel.stubs(:system) File.stubs(:open) end def test_structure_dump Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, 'my-app-db').returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) end def test_structure_dump_with_schema_search_path @configuration['schema_search_path'] = 'foo,bar' Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, '--schema=foo', '--schema=bar', 'my-app-db').returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) end end class PostgreSQLStructureLoadTest < ActiveRecord::TestCase def setup @connection = stub @configuration = { 'adapter' => 'postgresql', 'database' => 'my-app-db' } ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) Kernel.stubs(:system) end def test_structure_load filename = "awesome-file.sql" Kernel.expects(:system).with('psql', '-q', '-f', filename, 'my-app-db').returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) end def test_structure_load_accepts_path_with_spaces filename = "awesome file.sql" Kernel.expects(:system).with('psql', '-q', '-f', 'awesome file.sql', 'my-app-db').returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) end end end end rails-4.2.6/activerecord/test/cases/tasks/sqlite_rake_test.rb000066400000000000000000000130031266740050600244030ustar00rootroot00000000000000require 'cases/helper' require 'pathname' if current_adapter?(:SQLite3Adapter) module ActiveRecord class SqliteDBCreateTest < ActiveRecord::TestCase def setup @database = 'db_create.sqlite3' @connection = stub :connection @configuration = { 'adapter' => 'sqlite3', 'database' => @database } File.stubs(:exist?).returns(false) ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_db_checks_database_exists File.expects(:exist?).with(@database).returns(false) ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' end def test_db_create_when_file_exists File.stubs(:exist?).returns(true) $stderr.expects(:puts).with("#{@database} already exists") ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' end def test_db_create_with_file_does_nothing File.stubs(:exist?).returns(true) $stderr.stubs(:puts).returns(nil) ActiveRecord::Base.expects(:establish_connection).never ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' end def test_db_create_establishes_a_connection ActiveRecord::Base.expects(:establish_connection).with(@configuration) ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' end def test_db_create_with_error_prints_message ActiveRecord::Base.stubs(:establish_connection).raises(Exception) $stderr.stubs(:puts).returns(true) $stderr.expects(:puts). with("Couldn't create database for #{@configuration.inspect}") ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' end end class SqliteDBDropTest < ActiveRecord::TestCase def setup @database = "db_create.sqlite3" @path = stub(:to_s => '/absolute/path', :absolute? => true) @configuration = { 'adapter' => 'sqlite3', 'database' => @database } Pathname.stubs(:new).returns(@path) File.stubs(:join).returns('/former/relative/path') FileUtils.stubs(:rm).returns(true) end def test_creates_path_from_database Pathname.expects(:new).with(@database).returns(@path) ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root' end def test_removes_file_with_absolute_path File.stubs(:exist?).returns(true) @path.stubs(:absolute?).returns(true) FileUtils.expects(:rm).with('/absolute/path') ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root' end def test_generates_absolute_path_with_given_root @path.stubs(:absolute?).returns(false) File.expects(:join).with('/rails/root', @path). returns('/former/relative/path') ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root' end def test_removes_file_with_relative_path File.stubs(:exist?).returns(true) @path.stubs(:absolute?).returns(false) FileUtils.expects(:rm).with('/former/relative/path') ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root' end end class SqliteDBCharsetTest < ActiveRecord::TestCase def setup @database = 'db_create.sqlite3' @connection = stub :connection @configuration = { 'adapter' => 'sqlite3', 'database' => @database } File.stubs(:exist?).returns(false) ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_db_retrieves_charset @connection.expects(:encoding) ActiveRecord::Tasks::DatabaseTasks.charset @configuration, '/rails/root' end end class SqliteDBCollationTest < ActiveRecord::TestCase def setup @database = 'db_create.sqlite3' @connection = stub :connection @configuration = { 'adapter' => 'sqlite3', 'database' => @database } File.stubs(:exist?).returns(false) ActiveRecord::Base.stubs(:connection).returns(@connection) ActiveRecord::Base.stubs(:establish_connection).returns(true) end def test_db_retrieves_collation assert_raise NoMethodError do ActiveRecord::Tasks::DatabaseTasks.collation @configuration, '/rails/root' end end end class SqliteStructureDumpTest < ActiveRecord::TestCase def setup @database = "db_create.sqlite3" @configuration = { 'adapter' => 'sqlite3', 'database' => @database } end def test_structure_dump dbfile = @database filename = "awesome-file.sql" ActiveRecord::Tasks::DatabaseTasks.structure_dump @configuration, filename, '/rails/root' assert File.exist?(dbfile) assert File.exist?(filename) ensure FileUtils.rm_f(filename) FileUtils.rm_f(dbfile) end end class SqliteStructureLoadTest < ActiveRecord::TestCase def setup @database = "db_create.sqlite3" @configuration = { 'adapter' => 'sqlite3', 'database' => @database } end def test_structure_load dbfile = @database filename = "awesome-file.sql" open(filename, 'w') { |f| f.puts("select datetime('now', 'localtime');") } ActiveRecord::Tasks::DatabaseTasks.structure_load @configuration, filename, '/rails/root' assert File.exist?(dbfile) ensure FileUtils.rm_f(filename) FileUtils.rm_f(dbfile) end end end end rails-4.2.6/activerecord/test/cases/test_case.rb000066400000000000000000000102351266740050600216720ustar00rootroot00000000000000require 'active_support/test_case' module ActiveRecord # = Active Record Test Case # # Defines some test assertions to test against SQL queries. class TestCase < ActiveSupport::TestCase #:nodoc: def teardown SQLCounter.clear_log end def assert_date_from_db(expected, actual, message = nil) assert_equal expected.to_s, actual.to_s, message end def capture(stream) stream = stream.to_s captured_stream = Tempfile.new(stream) stream_io = eval("$#{stream}") origin_stream = stream_io.dup stream_io.reopen(captured_stream) yield stream_io.rewind return captured_stream.read ensure captured_stream.close captured_stream.unlink stream_io.reopen(origin_stream) end def capture_sql SQLCounter.clear_log yield SQLCounter.log_all.dup end def assert_sql(*patterns_to_match) capture_sql { yield } ensure failed_patterns = [] patterns_to_match.each do |pattern| failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql } end assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}" end def assert_queries(num = 1, options = {}) ignore_none = options.fetch(:ignore_none) { num == :any } SQLCounter.clear_log x = yield the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log if num == :any assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed." else mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}" assert_equal num, the_log.size, mesg end x end def assert_no_queries(options = {}, &block) options.reverse_merge! ignore_none: true assert_queries(0, options, &block) end def assert_column(model, column_name, msg=nil) assert has_column?(model, column_name), msg end def assert_no_column(model, column_name, msg=nil) assert_not has_column?(model, column_name), msg end def has_column?(model, column_name) model.reset_column_information model.column_names.include?(column_name.to_s) end end class SQLCounter class << self attr_accessor :ignored_sql, :log, :log_all def clear_log; self.log = []; self.log_all = []; end end self.clear_log self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] # FIXME: this needs to be refactored so specific database can add their own # ignored SQL, or better yet, use a different notification for the queries # instead examining the SQL content. oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im] mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /] postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i] sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im] [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql| ignored_sql.concat db_ignored_sql end attr_reader :ignore def initialize(ignore = Regexp.union(self.class.ignored_sql)) @ignore = ignore end def call(name, start, finish, message_id, values) sql = values[:sql] # FIXME: this seems bad. we should probably have a better way to indicate # the query was cached return if 'CACHE' == values[:name] self.class.log_all << sql self.class.log << sql unless ignore =~ sql end end ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) end rails-4.2.6/activerecord/test/cases/timestamp_test.rb000066400000000000000000000306001266740050600227600ustar00rootroot00000000000000require 'cases/helper' require 'support/ddl_helper' require 'models/developer' require 'models/computer' require 'models/owner' require 'models/pet' require 'models/toy' require 'models/car' require 'models/task' class TimestampTest < ActiveRecord::TestCase fixtures :developers, :owners, :pets, :toys, :cars, :tasks def setup @developer = Developer.first @owner = Owner.first @developer.update_columns(updated_at: Time.now.prev_month) @previously_updated_at = @developer.updated_at end def test_saving_a_changed_record_updates_its_timestamp @developer.name = "Jack Bauer" @developer.save! assert_not_equal @previously_updated_at, @developer.updated_at end def test_saving_a_unchanged_record_doesnt_update_its_timestamp @developer.save! assert_equal @previously_updated_at, @developer.updated_at end def test_touching_a_record_updates_its_timestamp previous_salary = @developer.salary @developer.salary = previous_salary + 10000 @developer.touch assert_not_equal @previously_updated_at, @developer.updated_at assert_equal previous_salary + 10000, @developer.salary assert @developer.salary_changed?, 'developer salary should have changed' assert @developer.changed?, 'developer should be marked as changed' @developer.reload assert_equal previous_salary, @developer.salary end def test_touching_a_record_with_default_scope_that_excludes_it_updates_its_timestamp developer = @developer.becomes(DeveloperCalledJamis) developer.touch assert_not_equal @previously_updated_at, developer.updated_at developer.reload assert_not_equal @previously_updated_at, developer.updated_at end def test_saving_when_record_timestamps_is_false_doesnt_update_its_timestamp Developer.record_timestamps = false @developer.name = "John Smith" @developer.save! assert_equal @previously_updated_at, @developer.updated_at ensure Developer.record_timestamps = true end def test_saving_when_instance_record_timestamps_is_false_doesnt_update_its_timestamp @developer.record_timestamps = false assert Developer.record_timestamps @developer.name = "John Smith" @developer.save! assert_equal @previously_updated_at, @developer.updated_at end def test_touching_an_attribute_updates_timestamp previously_created_at = @developer.created_at @developer.touch(:created_at) assert !@developer.created_at_changed? , 'created_at should not be changed' assert !@developer.changed?, 'record should not be changed' assert_not_equal previously_created_at, @developer.created_at assert_not_equal @previously_updated_at, @developer.updated_at end def test_touching_an_attribute_updates_it task = Task.first previous_value = task.ending task.touch(:ending) assert_not_equal previous_value, task.ending assert_in_delta Time.now, task.ending, 1 end def test_touching_many_attributes_updates_them task = Task.first previous_starting = task.starting previous_ending = task.ending task.touch(:starting, :ending) assert_not_equal previous_starting, task.starting assert_not_equal previous_ending, task.ending assert_in_delta Time.now, task.starting, 1 assert_in_delta Time.now, task.ending, 1 end def test_touching_a_record_without_timestamps_is_unexceptional assert_nothing_raised { Car.first.touch } end def test_touching_a_no_touching_object Developer.no_touching do assert @developer.no_touching? assert !@owner.no_touching? @developer.touch end assert !@developer.no_touching? assert !@owner.no_touching? assert_equal @previously_updated_at, @developer.updated_at end def test_touching_related_objects @owner = Owner.first @previously_updated_at = @owner.updated_at Owner.no_touching do @owner.pets.first.touch end assert_equal @previously_updated_at, @owner.updated_at end def test_global_no_touching ActiveRecord::Base.no_touching do assert @developer.no_touching? assert @owner.no_touching? @developer.touch end assert !@developer.no_touching? assert !@owner.no_touching? assert_equal @previously_updated_at, @developer.updated_at end def test_no_touching_threadsafe Thread.new do Developer.no_touching do assert @developer.no_touching? sleep(1) end end assert !@developer.no_touching? end def test_no_touching_with_callbacks klass = Class.new(ActiveRecord::Base) do self.table_name = "developers" attr_accessor :after_touch_called after_touch do |user| user.after_touch_called = true end end developer = klass.first klass.no_touching do developer.touch assert_not developer.after_touch_called end end def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at pet = Pet.first owner = pet.owner previously_owner_updated_at = owner.updated_at pet.name = "Fluffy the Third" pet.save assert_not_equal previously_owner_updated_at, pet.owner.updated_at end def test_destroying_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at pet = Pet.first owner = pet.owner previously_owner_updated_at = owner.updated_at pet.destroy assert_not_equal previously_owner_updated_at, pet.owner.updated_at end def test_saving_a_new_record_belonging_to_invalid_parent_with_touch_should_not_raise_exception klass = Class.new(Owner) do def self.name; 'Owner'; end validate { errors.add(:base, :invalid) } end pet = Pet.new(owner: klass.new) pet.save! assert pet.owner.new_record? end def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute klass = Class.new(ActiveRecord::Base) do def self.name; 'Pet'; end belongs_to :owner, :touch => :happy_at end pet = klass.first owner = pet.owner previously_owner_happy_at = owner.happy_at pet.name = "Fluffy the Third" pet.save assert_not_equal previously_owner_happy_at, pet.owner.happy_at end def test_touching_a_record_with_a_belongs_to_that_uses_a_counter_cache_should_update_the_parent klass = Class.new(ActiveRecord::Base) do def self.name; 'Pet'; end belongs_to :owner, :counter_cache => :use_count, :touch => true end pet = klass.first owner = pet.owner owner.update_columns(happy_at: 3.days.ago) previously_owner_updated_at = owner.updated_at pet.name = "I'm a parrot" pet.save assert_not_equal previously_owner_updated_at, pet.owner.updated_at end def test_touching_a_record_touches_parent_record_and_grandparent_record klass = Class.new(ActiveRecord::Base) do def self.name; 'Toy'; end belongs_to :pet, :touch => true end toy = klass.first pet = toy.pet owner = pet.owner time = 3.days.ago owner.update_columns(updated_at: time) toy.touch owner.reload assert_not_equal time, owner.updated_at end def test_touching_a_record_touches_polymorphic_record klass = Class.new(ActiveRecord::Base) do def self.name; 'Toy'; end end wheel_klass = Class.new(ActiveRecord::Base) do def self.name; 'Wheel'; end belongs_to :wheelable, :polymorphic => true, :touch => true end toy = klass.first time = 3.days.ago toy.update_columns(updated_at: time) wheel = wheel_klass.new wheel.wheelable = toy wheel.save wheel.touch assert_not_equal time, toy.updated_at end def test_changing_parent_of_a_record_touches_both_new_and_old_parent_record klass = Class.new(ActiveRecord::Base) do def self.name; 'Toy'; end belongs_to :pet, touch: true end toy1 = klass.find(1) old_pet = toy1.pet toy2 = klass.find(2) new_pet = toy2.pet time = 3.days.ago.at_beginning_of_hour old_pet.update_columns(updated_at: time) new_pet.update_columns(updated_at: time) toy1.pet = new_pet toy1.save! old_pet.reload new_pet.reload assert_not_equal time, new_pet.updated_at assert_not_equal time, old_pet.updated_at end def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_within_same_class car_class = Class.new(ActiveRecord::Base) do def self.name; 'Car'; end end wheel_class = Class.new(ActiveRecord::Base) do def self.name; 'Wheel'; end belongs_to :wheelable, :polymorphic => true, :touch => true end car1 = car_class.find(1) car2 = car_class.find(2) wheel = wheel_class.create!(wheelable: car1) time = 3.days.ago.at_beginning_of_hour car1.update_columns(updated_at: time) car2.update_columns(updated_at: time) wheel.wheelable = car2 wheel.save! assert_not_equal time, car1.reload.updated_at assert_not_equal time, car2.reload.updated_at end def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_with_other_class car_class = Class.new(ActiveRecord::Base) do def self.name; 'Car'; end end toy_class = Class.new(ActiveRecord::Base) do def self.name; 'Toy'; end end wheel_class = Class.new(ActiveRecord::Base) do def self.name; 'Wheel'; end belongs_to :wheelable, :polymorphic => true, :touch => true end car = car_class.find(1) toy = toy_class.find(3) wheel = wheel_class.create!(wheelable: car) time = 3.days.ago.at_beginning_of_hour car.update_columns(updated_at: time) toy.update_columns(updated_at: time) wheel.wheelable = toy wheel.save! assert_not_equal time, car.reload.updated_at assert_not_equal time, toy.reload.updated_at end def test_clearing_association_touches_the_old_record klass = Class.new(ActiveRecord::Base) do def self.name; 'Toy'; end belongs_to :pet, touch: true end toy = klass.find(1) pet = toy.pet time = 3.days.ago.at_beginning_of_hour pet.update_columns(updated_at: time) toy.pet = nil toy.save! pet.reload assert_not_equal time, pet.updated_at end def test_timestamp_column_values_are_present_in_the_callbacks klass = Class.new(ActiveRecord::Base) do self.table_name = 'people' before_create do self.born_at = self.created_at end end person = klass.create first_name: 'David' assert_not_equal person.born_at, nil end def test_timestamp_attributes_for_create toy = Toy.first assert_equal [:created_at, :created_on], toy.send(:timestamp_attributes_for_create) end def test_timestamp_attributes_for_update toy = Toy.first assert_equal [:updated_at, :updated_on], toy.send(:timestamp_attributes_for_update) end def test_all_timestamp_attributes toy = Toy.first assert_equal [:created_at, :created_on, :updated_at, :updated_on], toy.send(:all_timestamp_attributes) end def test_timestamp_attributes_for_create_in_model toy = Toy.first assert_equal [:created_at], toy.send(:timestamp_attributes_for_create_in_model) end def test_timestamp_attributes_for_update_in_model toy = Toy.first assert_equal [:updated_at], toy.send(:timestamp_attributes_for_update_in_model) end def test_all_timestamp_attributes_in_model toy = Toy.first assert_equal [:created_at, :updated_at], toy.send(:all_timestamp_attributes_in_model) end def test_index_is_created_for_both_timestamps ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| t.timestamps(:foos, null: true, index: true) end indexes = ActiveRecord::Base.connection.indexes('foos') assert_equal ['created_at', 'updated_at'], indexes.flat_map(&:columns).sort ensure ActiveRecord::Base.connection.drop_table(:foos) end end class TimestampsWithoutTransactionTest < ActiveRecord::TestCase include DdlHelper self.use_transactional_fixtures = false class TimestampAttributePost < ActiveRecord::Base attr_accessor :created_at, :updated_at end def test_do_not_write_timestamps_on_save_if_they_are_not_attributes with_example_table ActiveRecord::Base.connection, "timestamp_attribute_posts", "id integer primary key" do post = TimestampAttributePost.new(id: 1) post.save! # should not try to assign and persist created_at, updated_at assert_nil post.created_at assert_nil post.updated_at end end end rails-4.2.6/activerecord/test/cases/transaction_callbacks_test.rb000066400000000000000000000335111266740050600253050ustar00rootroot00000000000000require "cases/helper" require 'models/owner' require 'models/pet' require 'models/topic' class TransactionCallbacksTest < ActiveRecord::TestCase self.use_transactional_fixtures = false fixtures :topics, :owners, :pets class ReplyWithCallbacks < ActiveRecord::Base self.table_name = :topics belongs_to :topic, foreign_key: "parent_id" validates_presence_of :content after_commit :do_after_commit, on: :create attr_accessor :save_on_after_create after_create do self.save! if save_on_after_create end def history @history ||= [] end def do_after_commit history << :commit_on_create end end class TopicWithCallbacks < ActiveRecord::Base self.table_name = :topics has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id" after_commit { |record| record.do_after_commit(nil) } after_commit(on: :create) { |record| record.do_after_commit(:create) } after_commit(on: :update) { |record| record.do_after_commit(:update) } after_commit(on: :destroy) { |record| record.do_after_commit(:destroy) } after_rollback { |record| record.do_after_rollback(nil) } after_rollback(on: :create) { |record| record.do_after_rollback(:create) } after_rollback(on: :update) { |record| record.do_after_rollback(:update) } after_rollback(on: :destroy) { |record| record.do_after_rollback(:destroy) } def history @history ||= [] end def after_commit_block(on = nil, &block) @after_commit ||= {} @after_commit[on] ||= [] @after_commit[on] << block end def after_rollback_block(on = nil, &block) @after_rollback ||= {} @after_rollback[on] ||= [] @after_rollback[on] << block end def do_after_commit(on) blocks = @after_commit[on] if defined?(@after_commit) blocks.each{|b| b.call(self)} if blocks end def do_after_rollback(on) blocks = @after_rollback[on] if defined?(@after_rollback) blocks.each{|b| b.call(self)} if blocks end end def setup @first = TopicWithCallbacks.find(1) end def test_call_after_commit_after_transaction_commits @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} @first.save! assert_equal [:after_commit], @first.history end def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record add_transaction_execution_blocks @first @first.save! assert_equal [:commit_on_update], @first.history end def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_record add_transaction_execution_blocks @first @first.destroy assert_equal [:commit_on_destroy], @first.history end def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) add_transaction_execution_blocks new_record new_record.save! assert_equal [:commit_on_create], new_record.history end def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record_if_create_succeeds_creating_through_association topic = TopicWithCallbacks.create!(:title => "New topic", :written_on => Date.today) reply = topic.replies.create assert_equal [], reply.history end def test_only_call_after_commit_on_create_and_doesnt_leaky r = ReplyWithCallbacks.new(content: 'foo') r.save_on_after_create = true r.save! r.content = 'bar' r.save! r.save! assert_equal [:commit_on_create], r.history end def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record_on_touch add_transaction_execution_blocks @first @first.touch assert_equal [:commit_on_update], @first.history end def test_only_call_after_commit_on_top_level_transactions @first.after_commit_block{|r| r.history << :after_commit} assert @first.history.empty? @first.transaction do @first.transaction(requires_new: true) do @first.touch end assert @first.history.empty? end assert_equal [:after_commit], @first.history end def test_call_after_rollback_after_transaction_rollsback @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} Topic.transaction do @first.save! raise ActiveRecord::Rollback end assert_equal [:after_rollback], @first.history end def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record add_transaction_execution_blocks @first Topic.transaction do @first.save! raise ActiveRecord::Rollback end assert_equal [:rollback_on_update], @first.history end def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record_on_touch add_transaction_execution_blocks @first Topic.transaction do @first.touch raise ActiveRecord::Rollback end assert_equal [:rollback_on_update], @first.history end def test_only_call_after_rollback_on_destroy_after_transaction_rollsback_for_destroyed_record add_transaction_execution_blocks @first Topic.transaction do @first.destroy raise ActiveRecord::Rollback end assert_equal [:rollback_on_destroy], @first.history end def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) add_transaction_execution_blocks new_record Topic.transaction do new_record.save! raise ActiveRecord::Rollback end assert_equal [:rollback_on_create], new_record.history end def test_call_after_rollback_when_commit_fails @first.class.connection.singleton_class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction) begin @first.class.connection.singleton_class.class_eval do def commit_db_transaction; raise "boom!"; end end @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} assert !@first.save rescue nil assert_equal [:after_rollback], @first.history ensure @first.class.connection.singleton_class.send(:remove_method, :commit_db_transaction) @first.class.connection.singleton_class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction) end end def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def @first.commits(i=0); @commits ||= 0; @commits += i if i; end @first.after_rollback_block{|r| r.rollbacks(1)} @first.after_commit_block{|r| r.commits(1)} second = TopicWithCallbacks.find(3) def second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def second.commits(i=0); @commits ||= 0; @commits += i if i; end second.after_rollback_block{|r| r.rollbacks(1)} second.after_commit_block{|r| r.commits(1)} Topic.transaction do @first.save! Topic.transaction(:requires_new => true) do second.save! raise ActiveRecord::Rollback end end assert_equal 1, @first.commits assert_equal 0, @first.rollbacks assert_equal 0, second.commits assert_equal 1, second.rollbacks end def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def @first.commits(i=0); @commits ||= 0; @commits += i if i; end @first.after_rollback_block{|r| r.rollbacks(1)} @first.after_commit_block{|r| r.commits(1)} Topic.transaction do @first.save Topic.transaction(:requires_new => true) do @first.save! raise ActiveRecord::Rollback end Topic.transaction(:requires_new => true) do @first.save! raise ActiveRecord::Rollback end end assert_equal 1, @first.commits assert_equal 2, @first.rollbacks end def test_after_transaction_callbacks_should_prevent_callbacks_from_being_called old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks ActiveRecord::Base.raise_in_transactional_callbacks = false def @first.last_after_transaction_error=(e); @last_transaction_error = e; end def @first.last_after_transaction_error; @last_transaction_error; end @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";} @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";} second = TopicWithCallbacks.find(3) second.after_commit_block{|r| r.history << :after_commit} second.after_rollback_block{|r| r.history << :after_rollback} Topic.transaction do @first.save! second.save! end assert_equal :commit, @first.last_after_transaction_error assert_equal [:after_commit], second.history second.history.clear Topic.transaction do @first.save! second.save! raise ActiveRecord::Rollback end assert_equal :rollback, @first.last_after_transaction_error assert_equal [:after_rollback], second.history ensure ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config end def test_after_commit_should_not_raise_when_raise_in_transactional_callbacks_false old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks ActiveRecord::Base.raise_in_transactional_callbacks = false @first.after_commit_block{ fail "boom" } Topic.transaction { @first.save! } ensure ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config end def test_after_commit_callback_should_not_swallow_errors @first.after_commit_block{ fail "boom" } assert_raises(RuntimeError) do Topic.transaction do @first.save! end end end def test_after_commit_callback_when_raise_should_not_restore_state first = TopicWithCallbacks.new second = TopicWithCallbacks.new first.after_commit_block{ fail "boom" } second.after_commit_block{ fail "boom" } begin Topic.transaction do first.save! assert_not_nil first.id second.save! assert_not_nil second.id end rescue end assert_not_nil first.id assert_not_nil second.id assert first.reload end def test_after_rollback_callback_should_not_swallow_errors_when_set_to_raise error_class = Class.new(StandardError) @first.after_rollback_block{ raise error_class } assert_raises(error_class) do Topic.transaction do @first.save! raise ActiveRecord::Rollback end end end def test_after_rollback_callback_when_raise_should_restore_state error_class = Class.new(StandardError) first = TopicWithCallbacks.new second = TopicWithCallbacks.new first.after_rollback_block{ raise error_class } second.after_rollback_block{ raise error_class } begin Topic.transaction do first.save! assert_not_nil first.id second.save! assert_not_nil second.id raise ActiveRecord::Rollback end rescue error_class end assert_nil first.id assert_nil second.id end def test_after_rollback_callbacks_should_validate_on_condition assert_raise(ArgumentError) { Topic.after_rollback(on: :save) } e = assert_raise(ArgumentError) { Topic.after_rollback(on: 'create') } assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message) end def test_after_commit_callbacks_should_validate_on_condition assert_raise(ArgumentError) { Topic.after_commit(on: :save) } e = assert_raise(ArgumentError) { Topic.after_commit(on: 'create') } assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message) end def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_call_callbacks_on_the_parent_object pet = Pet.first owner = pet.owner flag = false owner.on_after_commit do flag = true end pet.name = "Fluffy the Third" pet.save assert flag end private def add_transaction_execution_blocks(record) record.after_commit_block(:create) { |r| r.history << :commit_on_create } record.after_commit_block(:update) { |r| r.history << :commit_on_update } record.after_commit_block(:destroy) { |r| r.history << :commit_on_destroy } record.after_rollback_block(:create) { |r| r.history << :rollback_on_create } record.after_rollback_block(:update) { |r| r.history << :rollback_on_update } record.after_rollback_block(:destroy) { |r| r.history << :rollback_on_destroy } end end class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class TopicWithCallbacksOnMultipleActions < ActiveRecord::Base self.table_name = :topics after_commit(on: [:create, :destroy]) { |record| record.history << :create_and_destroy } after_commit(on: [:create, :update]) { |record| record.history << :create_and_update } after_commit(on: [:update, :destroy]) { |record| record.history << :update_and_destroy } def clear_history @history = [] end def history @history ||= [] end end def test_after_commit_on_multiple_actions topic = TopicWithCallbacksOnMultipleActions.new topic.save assert_equal [:create_and_update, :create_and_destroy], topic.history topic.clear_history topic.approved = true topic.save assert_equal [:update_and_destroy, :create_and_update], topic.history topic.clear_history topic.destroy assert_equal [:update_and_destroy, :create_and_destroy], topic.history end end rails-4.2.6/activerecord/test/cases/transaction_isolation_test.rb000066400000000000000000000060401266740050600253640ustar00rootroot00000000000000require 'cases/helper' unless ActiveRecord::Base.connection.supports_transaction_isolation? class TransactionIsolationUnsupportedTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class Tag < ActiveRecord::Base end test "setting the isolation level raises an error" do assert_raises(ActiveRecord::TransactionIsolationError) do Tag.transaction(isolation: :serializable) { } end end end end if ActiveRecord::Base.connection.supports_transaction_isolation? class TransactionIsolationTest < ActiveRecord::TestCase self.use_transactional_fixtures = false class Tag < ActiveRecord::Base self.table_name = 'tags' end class Tag2 < ActiveRecord::Base self.table_name = 'tags' end setup do Tag.establish_connection :arunit Tag2.establish_connection :arunit Tag.destroy_all end # It is impossible to properly test read uncommitted. The SQL standard only # specifies what must not happen at a certain level, not what must happen. At # the read uncommitted level, there is nothing that must not happen. if ActiveRecord::Base.connection.transaction_isolation_levels.include?(:read_uncommitted) test "read uncommitted" do Tag.transaction(isolation: :read_uncommitted) do assert_equal 0, Tag.count Tag2.create assert_equal 1, Tag.count end end end # We are testing that a dirty read does not happen test "read committed" do Tag.transaction(isolation: :read_committed) do assert_equal 0, Tag.count Tag2.transaction do Tag2.create assert_equal 0, Tag.count end end assert_equal 1, Tag.count end # We are testing that a nonrepeatable read does not happen if ActiveRecord::Base.connection.transaction_isolation_levels.include?(:repeatable_read) test "repeatable read" do tag = Tag.create(name: 'jon') Tag.transaction(isolation: :repeatable_read) do tag.reload Tag2.find(tag.id).update(name: 'emily') tag.reload assert_equal 'jon', tag.name end tag.reload assert_equal 'emily', tag.name end end # We are only testing that there are no errors because it's too hard to # test serializable. Databases behave differently to enforce the serializability # constraint. test "serializable" do Tag.transaction(isolation: :serializable) do Tag.create end end test "setting isolation when joining a transaction raises an error" do Tag.transaction do assert_raises(ActiveRecord::TransactionIsolationError) do Tag.transaction(isolation: :serializable) { } end end end test "setting isolation when starting a nested transaction raises error" do Tag.transaction do assert_raises(ActiveRecord::TransactionIsolationError) do Tag.transaction(requires_new: true, isolation: :serializable) { } end end end end end rails-4.2.6/activerecord/test/cases/transactions_test.rb000066400000000000000000000553651266740050600235040ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/reply' require 'models/developer' require 'models/computer' require 'models/book' require 'models/author' require 'models/post' require 'models/movie' class TransactionTest < ActiveRecord::TestCase self.use_transactional_fixtures = false fixtures :topics, :developers, :authors, :posts def setup @first, @second = Topic.find(1, 2).sort_by { |t| t.id } end def test_persisted_in_a_model_with_custom_primary_key_after_failed_save movie = Movie.create assert !movie.persisted? end def test_raise_after_destroy assert_not @first.frozen? assert_raises(RuntimeError) { Topic.transaction do @first.destroy assert @first.frozen? raise end } assert @first.reload assert_not @first.frozen? end def test_successful Topic.transaction do @first.approved = true @second.approved = false @first.save @second.save end assert Topic.find(1).approved?, "First should have been approved" assert !Topic.find(2).approved?, "Second should have been unapproved" end def transaction_with_return Topic.transaction do @first.approved = true @second.approved = false @first.save @second.save return end end def test_successful_with_return committed = false Topic.connection.class_eval do alias :real_commit_db_transaction :commit_db_transaction define_method(:commit_db_transaction) do committed = true real_commit_db_transaction end end transaction_with_return assert committed assert Topic.find(1).approved?, "First should have been approved" assert !Topic.find(2).approved?, "Second should have been unapproved" ensure Topic.connection.class_eval do remove_method :commit_db_transaction alias :commit_db_transaction :real_commit_db_transaction rescue nil end end def test_number_of_transactions_in_commit num = nil Topic.connection.class_eval do alias :real_commit_db_transaction :commit_db_transaction define_method(:commit_db_transaction) do num = transaction_manager.open_transactions real_commit_db_transaction end end Topic.transaction do @first.approved = true @first.save! end assert_equal 0, num ensure Topic.connection.class_eval do remove_method :commit_db_transaction alias :commit_db_transaction :real_commit_db_transaction rescue nil end end def test_successful_with_instance_method @first.transaction do @first.approved = true @second.approved = false @first.save @second.save end assert Topic.find(1).approved?, "First should have been approved" assert !Topic.find(2).approved?, "Second should have been unapproved" end def test_failing_on_exception begin Topic.transaction do @first.approved = true @second.approved = false @first.save @second.save raise "Bad things!" end rescue # caught it end assert @first.approved?, "First should still be changed in the objects" assert !@second.approved?, "Second should still be changed in the objects" assert !Topic.find(1).approved?, "First shouldn't have been approved" assert Topic.find(2).approved?, "Second should still be approved" end def test_raising_exception_in_callback_rollbacks_in_save def @first.after_save_for_transaction raise 'Make the transaction rollback' end @first.approved = true e = assert_raises(RuntimeError) { @first.save } assert_equal "Make the transaction rollback", e.message assert !Topic.find(1).approved? end def test_rolling_back_in_a_callback_rollbacks_before_save def @first.before_save_for_transaction raise ActiveRecord::Rollback end assert !@first.approved Topic.transaction do @first.approved = true @first.save! end assert !Topic.find(@first.id).approved?, "Should not commit the approved flag" end def test_raising_exception_in_nested_transaction_restore_state_in_save topic = Topic.new def topic.after_save_for_transaction raise 'Make the transaction rollback' end assert_raises(RuntimeError) do Topic.transaction { topic.save } end assert topic.new_record?, "#{topic.inspect} should be new record" end def test_transaction_state_is_cleared_when_record_is_persisted author = Author.create! name: 'foo' author.name = nil assert_not author.save assert_not author.new_record? end def test_update_should_rollback_on_failure author = Author.find(1) posts_count = author.posts.size assert posts_count > 0 status = author.update(name: nil, post_ids: []) assert !status assert_equal posts_count, author.posts(true).size end def test_update_should_rollback_on_failure! author = Author.find(1) posts_count = author.posts.size assert posts_count > 0 assert_raise(ActiveRecord::RecordInvalid) do author.update!(name: nil, post_ids: []) end assert_equal posts_count, author.posts(true).size end def test_cancellation_from_before_destroy_rollbacks_in_destroy add_cancelling_before_destroy_with_db_side_effect_to_topic @first nbooks_before_destroy = Book.count status = @first.destroy assert !status @first.reload assert_equal nbooks_before_destroy, Book.count end %w(validation save).each do |filter| define_method("test_cancellation_from_before_filters_rollbacks_in_#{filter}") do send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first) nbooks_before_save = Book.count original_author_name = @first.author_name @first.author_name += '_this_should_not_end_up_in_the_db' status = @first.save assert !status assert_equal original_author_name, @first.reload.author_name assert_equal nbooks_before_save, Book.count end define_method("test_cancellation_from_before_filters_rollbacks_in_#{filter}!") do send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first) nbooks_before_save = Book.count original_author_name = @first.author_name @first.author_name += '_this_should_not_end_up_in_the_db' begin @first.save! rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved end assert_equal original_author_name, @first.reload.author_name assert_equal nbooks_before_save, Book.count end end def test_callback_rollback_in_create topic = Class.new(Topic) { def after_create_for_transaction raise 'Make the transaction rollback' end } new_topic = topic.new(:title => "A new topic", :author_name => "Ben", :author_email_address => "ben@example.com", :written_on => "2003-07-16t15:28:11.2233+01:00", :last_read => "2004-04-15", :bonus_time => "2005-01-30t15:28:00.00+01:00", :content => "Have a nice day", :approved => false) new_record_snapshot = !new_topic.persisted? id_present = new_topic.has_attribute?(Topic.primary_key) id_snapshot = new_topic.id # Make sure the second save gets the after_create callback called. 2.times do new_topic.approved = true e = assert_raises(RuntimeError) { new_topic.save } assert_equal "Make the transaction rollback", e.message assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value" assert_equal id_snapshot, new_topic.id, "The topic should have its old id" assert_equal id_present, new_topic.has_attribute?(Topic.primary_key) end end def test_callback_rollback_in_create_with_record_invalid_exception topic = Class.new(Topic) { def after_create_for_transaction raise ActiveRecord::RecordInvalid.new(Author.new) end } new_topic = topic.create(:title => "A new topic") assert !new_topic.persisted?, "The topic should not be persisted" assert_nil new_topic.id, "The topic should not have an ID" end def test_nested_explicit_transactions Topic.transaction do Topic.transaction do @first.approved = true @second.approved = false @first.save @second.save end end assert Topic.find(1).approved?, "First should have been approved" assert !Topic.find(2).approved?, "Second should have been unapproved" end def test_manually_rolling_back_a_transaction Topic.transaction do @first.approved = true @second.approved = false @first.save @second.save raise ActiveRecord::Rollback end assert @first.approved?, "First should still be changed in the objects" assert !@second.approved?, "Second should still be changed in the objects" assert !Topic.find(1).approved?, "First shouldn't have been approved" assert Topic.find(2).approved?, "Second should still be approved" end def test_invalid_keys_for_transaction assert_raise ArgumentError do Topic.transaction :nested => true do end end end def test_force_savepoint_in_nested_transaction Topic.transaction do @first.approved = true @second.approved = false @first.save! @second.save! begin Topic.transaction :requires_new => true do @first.happy = false @first.save! raise end rescue end end assert @first.reload.approved? assert !@second.reload.approved? end if Topic.connection.supports_savepoints? def test_force_savepoint_on_instance @first.transaction do @first.approved = true @second.approved = false @first.save! @second.save! begin @second.transaction :requires_new => true do @first.happy = false @first.save! raise end rescue end end assert @first.reload.approved? assert !@second.reload.approved? end if Topic.connection.supports_savepoints? def test_no_savepoint_in_nested_transaction_without_force Topic.transaction do @first.approved = true @second.approved = false @first.save! @second.save! begin Topic.transaction do @first.approved = false @first.save! raise end rescue end end assert !@first.reload.approved? assert !@second.reload.approved? end if Topic.connection.supports_savepoints? def test_many_savepoints Topic.transaction do @first.content = "One" @first.save! begin Topic.transaction :requires_new => true do @first.content = "Two" @first.save! begin Topic.transaction :requires_new => true do @first.content = "Three" @first.save! begin Topic.transaction :requires_new => true do @first.content = "Four" @first.save! raise end rescue end @three = @first.reload.content raise end rescue end @two = @first.reload.content raise end rescue end @one = @first.reload.content end assert_equal "One", @one assert_equal "Two", @two assert_equal "Three", @three end if Topic.connection.supports_savepoints? def test_using_named_savepoints Topic.transaction do @first.approved = true @first.save! Topic.connection.create_savepoint("first") @first.approved = false @first.save! Topic.connection.rollback_to_savepoint("first") assert @first.reload.approved? @first.approved = false @first.save! Topic.connection.release_savepoint("first") assert_not @first.reload.approved? end end if Topic.connection.supports_savepoints? def test_releasing_named_savepoints Topic.transaction do Topic.connection.create_savepoint("another") Topic.connection.release_savepoint("another") # The savepoint is now gone and we can't remove it again. assert_raises(ActiveRecord::StatementInvalid) do Topic.connection.release_savepoint("another") end end end def test_savepoints_name Topic.transaction do assert_nil Topic.connection.current_savepoint_name assert_nil Topic.connection.current_transaction.savepoint_name Topic.transaction(requires_new: true) do assert_equal "active_record_1", Topic.connection.current_savepoint_name assert_equal "active_record_1", Topic.connection.current_transaction.savepoint_name Topic.transaction(requires_new: true) do assert_equal "active_record_2", Topic.connection.current_savepoint_name assert_equal "active_record_2", Topic.connection.current_transaction.savepoint_name end assert_equal "active_record_1", Topic.connection.current_savepoint_name assert_equal "active_record_1", Topic.connection.current_transaction.savepoint_name end end end def test_rollback_when_commit_raises Topic.connection.expects(:begin_db_transaction) Topic.connection.expects(:commit_db_transaction).raises('OH NOES') Topic.connection.expects(:rollback_db_transaction) assert_raise RuntimeError do Topic.transaction do # do nothing end end end def test_rollback_when_saving_a_frozen_record topic = Topic.new(:title => 'test') topic.freeze e = assert_raise(RuntimeError) { topic.save } assert_match(/frozen/i, e.message) # Not good enough, but we can't do much # about it since there is no specific error # for frozen objects. assert !topic.persisted?, 'not persisted' assert_nil topic.id assert topic.frozen?, 'not frozen' end # The behavior of killed threads having a status of "aborting" was changed # in Ruby 2.0, so Thread#kill on 1.9 will prematurely commit the transaction # and there's nothing we can do about it. if !RUBY_VERSION.start_with?('1.9') && !in_memory_db? def test_rollback_when_thread_killed queue = Queue.new thread = Thread.new do Topic.transaction do @first.approved = true @second.approved = false @first.save queue.push nil sleep @second.save end end queue.pop thread.kill thread.join assert @first.approved?, "First should still be changed in the objects" assert !@second.approved?, "Second should still be changed in the objects" assert !Topic.find(1).approved?, "First shouldn't have been approved" assert Topic.find(2).approved?, "Second should still be approved" end end def test_restore_active_record_state_for_all_records_in_a_transaction topic_without_callbacks = Class.new(ActiveRecord::Base) do self.table_name = 'topics' end topic_1 = Topic.new(:title => 'test_1') topic_2 = Topic.new(:title => 'test_2') topic_3 = topic_without_callbacks.new(:title => 'test_3') Topic.transaction do assert topic_1.save assert topic_2.save assert topic_3.save @first.save @second.destroy assert topic_1.persisted?, 'persisted' assert_not_nil topic_1.id assert topic_2.persisted?, 'persisted' assert_not_nil topic_2.id assert topic_3.persisted?, 'persisted' assert_not_nil topic_3.id assert @first.persisted?, 'persisted' assert_not_nil @first.id assert @second.destroyed?, 'destroyed' raise ActiveRecord::Rollback end assert !topic_1.persisted?, 'not persisted' assert_nil topic_1.id assert !topic_2.persisted?, 'not persisted' assert_nil topic_2.id assert !topic_3.persisted?, 'not persisted' assert_nil topic_3.id assert @first.persisted?, 'persisted' assert_not_nil @first.id assert !@second.destroyed?, 'not destroyed' end def test_restore_frozen_state_after_double_destroy topic = Topic.create reply = topic.replies.create Topic.transaction do topic.destroy # calls #destroy on reply (since dependent: destroy) reply.destroy raise ActiveRecord::Rollback end assert_not reply.frozen? assert_not topic.frozen? end def test_rollback_of_frozen_records topic = Topic.create.freeze Topic.transaction do topic.destroy raise ActiveRecord::Rollback end assert topic.frozen?, 'frozen' end def test_rollback_for_freshly_persisted_records topic = Topic.create Topic.transaction do topic.destroy raise ActiveRecord::Rollback end assert topic.persisted?, 'persisted' end def test_sqlite_add_column_in_transaction return true unless current_adapter?(:SQLite3Adapter) # Test first if column creation/deletion works correctly when no # transaction is in place. # # We go back to the connection for the column queries because # Topic.columns is cached and won't report changes to the DB assert_nothing_raised do Topic.reset_column_information Topic.connection.add_column('topics', 'stuff', :string) assert Topic.column_names.include?('stuff') Topic.reset_column_information Topic.connection.remove_column('topics', 'stuff') assert !Topic.column_names.include?('stuff') end if Topic.connection.supports_ddl_transactions? assert_nothing_raised do Topic.transaction { Topic.connection.add_column('topics', 'stuff', :string) } end else Topic.transaction do assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) } raise ActiveRecord::Rollback end end ensure begin Topic.connection.remove_column('topics', 'stuff') rescue ensure Topic.reset_column_information end end def test_transactions_state_from_rollback connection = Topic.connection transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction assert transaction.open? assert !transaction.state.rolledback? assert !transaction.state.committed? transaction.rollback assert transaction.state.rolledback? assert !transaction.state.committed? end def test_transactions_state_from_commit connection = Topic.connection transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction assert transaction.open? assert !transaction.state.rolledback? assert !transaction.state.committed? transaction.commit assert !transaction.state.rolledback? assert transaction.state.committed? end def test_transaction_rollback_with_primarykeyless_tables connection = ActiveRecord::Base.connection connection.create_table(:transaction_without_primary_keys, force: true, id: false) do |t| t.integer :thing_id end klass = Class.new(ActiveRecord::Base) do self.table_name = 'transaction_without_primary_keys' after_commit { } # necessary to trigger the has_transactional_callbacks branch end assert_no_difference(-> { klass.count }) do ActiveRecord::Base.transaction do klass.create! raise ActiveRecord::Rollback end end ensure connection.drop_table("transaction_without_primary_keys") if connection.table_exists? "transaction_without_primary_keys" end private %w(validation save destroy).each do |filter| define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do |topic| meta = class << topic; self; end meta.send("define_method", "before_#{filter}_for_transaction") do Book.create false end end end end class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase self.use_transactional_fixtures = true fixtures :topics def test_automatic_savepoint_in_outer_transaction @first = Topic.find(1) begin Topic.transaction do @first.approved = true @first.save! raise end rescue assert !@first.reload.approved? end end def test_no_automatic_savepoint_for_inner_transaction @first = Topic.find(1) Topic.transaction do @first.approved = true @first.save! begin Topic.transaction do @first.approved = false @first.save! raise end rescue end end assert !@first.reload.approved? end end if Topic.connection.supports_savepoints? if current_adapter?(:PostgreSQLAdapter) class ConcurrentTransactionTest < TransactionTest # This will cause transactions to overlap and fail unless they are performed on # separate database connections. unless in_memory_db? def test_transaction_per_thread threads = 3.times.map do Thread.new do Topic.transaction do topic = Topic.find(1) topic.approved = !topic.approved? assert topic.save! topic.approved = !topic.approved? assert topic.save! end Topic.connection.close end end threads.each { |t| t.join } end end # Test for dirty reads among simultaneous transactions. def test_transaction_isolation__read_committed # Should be invariant. original_salary = Developer.find(1).salary temporary_salary = 200000 assert_nothing_raised do threads = (1..3).map do Thread.new do Developer.transaction do # Expect original salary. dev = Developer.find(1) assert_equal original_salary, dev.salary dev.salary = temporary_salary dev.save! # Expect temporary salary. dev = Developer.find(1) assert_equal temporary_salary, dev.salary dev.salary = original_salary dev.save! # Expect original salary. dev = Developer.find(1) assert_equal original_salary, dev.salary end Developer.connection.close end end # Keep our eyes peeled. threads << Thread.new do 10.times do sleep 0.05 Developer.transaction do # Always expect original salary. assert_equal original_salary, Developer.find(1).salary end end Developer.connection.close end threads.each { |t| t.join } end assert_equal original_salary, Developer.find(1).salary end end end rails-4.2.6/activerecord/test/cases/type/000077500000000000000000000000001266740050600203535ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/type/decimal_test.rb000066400000000000000000000035761266740050600233500ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module Type class DecimalTest < ActiveRecord::TestCase def test_type_cast_decimal type = Decimal.new assert_equal BigDecimal.new("0"), type.type_cast_from_user(BigDecimal.new("0")) assert_equal BigDecimal.new("123"), type.type_cast_from_user(123.0) assert_equal BigDecimal.new("1"), type.type_cast_from_user(:"1") end def test_type_cast_decimal_from_float_with_large_precision type = Decimal.new(precision: ::Float::DIG + 2) assert_equal BigDecimal.new("123.0"), type.type_cast_from_user(123.0) end def test_type_cast_from_float_with_unspecified_precision type = Decimal.new assert_equal 22.68.to_d, type.type_cast_from_user(22.68) end def test_type_cast_decimal_from_rational_with_precision type = Decimal.new(precision: 2) assert_equal BigDecimal("0.33"), type.type_cast_from_user(Rational(1, 3)) end def test_type_cast_decimal_from_rational_with_precision_and_scale type = Decimal.new(precision: 4, scale: 2) assert_equal BigDecimal("0.33"), type.type_cast_from_user(Rational(1, 3)) end def test_type_cast_decimal_from_rational_without_precision_defaults_to_18_36 type = Decimal.new assert_equal BigDecimal("0.333333333333333333E0"), type.type_cast_from_user(Rational(1, 3)) end def test_type_cast_decimal_from_object_responding_to_d value = Object.new def value.to_d BigDecimal.new("1") end type = Decimal.new assert_equal BigDecimal("1"), type.type_cast_from_user(value) end def test_changed? type = Decimal.new assert type.changed?(5.0, 5.0, '5.0wibble') assert_not type.changed?(5.0, 5.0, '5.0') assert_not type.changed?(-5.0, -5.0, '-5.0') end end end end rails-4.2.6/activerecord/test/cases/type/integer_test.rb000066400000000000000000000076071266740050600234060ustar00rootroot00000000000000require "cases/helper" require "models/company" module ActiveRecord module Type class IntegerTest < ActiveRecord::TestCase test "simple values" do type = Type::Integer.new assert_equal 1, type.type_cast_from_user(1) assert_equal 1, type.type_cast_from_user('1') assert_equal 1, type.type_cast_from_user('1ignore') assert_equal 0, type.type_cast_from_user('bad1') assert_equal 0, type.type_cast_from_user('bad') assert_equal 1, type.type_cast_from_user(1.7) assert_equal 0, type.type_cast_from_user(false) assert_equal 1, type.type_cast_from_user(true) assert_nil type.type_cast_from_user(nil) end test "random objects cast to nil" do type = Type::Integer.new assert_nil type.type_cast_from_user([1,2]) assert_nil type.type_cast_from_user({1 => 2}) assert_nil type.type_cast_from_user((1..2)) end test "casting ActiveRecord models" do type = Type::Integer.new firm = Firm.create(:name => 'Apple') assert_nil type.type_cast_from_user(firm) end test "casting objects without to_i" do type = Type::Integer.new assert_nil type.type_cast_from_user(::Object.new) end test "casting nan and infinity" do type = Type::Integer.new assert_nil type.type_cast_from_user(::Float::NAN) assert_nil type.type_cast_from_user(1.0/0.0) end test "changed?" do type = Type::Integer.new assert type.changed?(5, 5, '5wibble') assert_not type.changed?(5, 5, '5') assert_not type.changed?(5, 5, '5.0') assert_not type.changed?(-5, -5, '-5') assert_not type.changed?(-5, -5, '-5.0') assert_not type.changed?(nil, nil, nil) end test "values below int min value are out of range" do assert_raises(::RangeError) do Integer.new.type_cast_for_database(-2147483649) end end test "values above int max value are out of range" do assert_raises(::RangeError) do Integer.new.type_cast_for_database(2147483648) end end test "very small numbers are out of range" do assert_raises(::RangeError) do Integer.new.type_cast_for_database(-9999999999999999999999999999999) end end test "very large numbers are out of range" do assert_raises(::RangeError) do Integer.new.type_cast_for_database(9999999999999999999999999999999) end end test "normal numbers are in range" do type = Integer.new assert_equal(0, type.type_cast_for_database(0)) assert_equal(-1, type.type_cast_for_database(-1)) assert_equal(1, type.type_cast_for_database(1)) end test "int max value is in range" do assert_equal(2147483647, Integer.new.type_cast_for_database(2147483647)) end test "int min value is in range" do assert_equal(-2147483648, Integer.new.type_cast_for_database(-2147483648)) end test "columns with a larger limit have larger ranges" do type = Integer.new(limit: 8) assert_equal(9223372036854775807, type.type_cast_for_database(9223372036854775807)) assert_equal(-9223372036854775808, type.type_cast_for_database(-9223372036854775808)) assert_raises(::RangeError) do type.type_cast_for_database(-9999999999999999999999999999999) end assert_raises(::RangeError) do type.type_cast_for_database(9999999999999999999999999999999) end end test "values which are out of range can be re-assigned" do klass = Class.new(ActiveRecord::Base) do self.table_name = 'posts' attribute :foo, Type::Integer.new end model = klass.new model.foo = 2147483648 model.foo = 1 assert_equal 1, model.foo end end end end rails-4.2.6/activerecord/test/cases/type/string_test.rb000066400000000000000000000016341266740050600232510ustar00rootroot00000000000000require 'cases/helper' module ActiveRecord class StringTypeTest < ActiveRecord::TestCase test "type casting" do type = Type::String.new assert_equal "t", type.type_cast_from_user(true) assert_equal "f", type.type_cast_from_user(false) assert_equal "123", type.type_cast_from_user(123) end test "values are duped coming out" do s = "foo" type = Type::String.new assert_not_same s, type.type_cast_from_user(s) assert_not_same s, type.type_cast_from_database(s) end test "string mutations are detected" do klass = Class.new(Base) klass.table_name = 'authors' author = klass.create!(name: 'Sean') assert_not author.changed? author.name << ' Griffin' assert author.name_changed? author.save! author.reload assert_equal 'Sean Griffin', author.name assert_not author.changed? end end end rails-4.2.6/activerecord/test/cases/type/type_map_test.rb000066400000000000000000000111471266740050600235610ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module Type class TypeMapTest < ActiveRecord::TestCase def test_default_type mapping = TypeMap.new assert_kind_of Value, mapping.lookup(:undefined) end def test_registering_types boolean = Boolean.new mapping = TypeMap.new mapping.register_type(/boolean/i, boolean) assert_equal mapping.lookup('boolean'), boolean end def test_overriding_registered_types time = Time.new timestamp = DateTime.new mapping = TypeMap.new mapping.register_type(/time/i, time) mapping.register_type(/time/i, timestamp) assert_equal mapping.lookup('time'), timestamp end def test_fuzzy_lookup string = String.new mapping = TypeMap.new mapping.register_type(/varchar/i, string) assert_equal mapping.lookup('varchar(20)'), string end def test_aliasing_types string = String.new mapping = TypeMap.new mapping.register_type(/string/i, string) mapping.alias_type(/varchar/i, 'string') assert_equal mapping.lookup('varchar'), string end def test_changing_type_changes_aliases time = Time.new timestamp = DateTime.new mapping = TypeMap.new mapping.register_type(/timestamp/i, time) mapping.alias_type(/datetime/i, 'timestamp') mapping.register_type(/timestamp/i, timestamp) assert_equal mapping.lookup('datetime'), timestamp end def test_aliases_keep_metadata mapping = TypeMap.new mapping.register_type(/decimal/i) { |sql_type| sql_type } mapping.alias_type(/number/i, 'decimal') assert_equal mapping.lookup('number(20)'), 'decimal(20)' assert_equal mapping.lookup('number'), 'decimal' end def test_register_proc string = String.new binary = Binary.new mapping = TypeMap.new mapping.register_type(/varchar/i) do |type| if type.include?('(') string else binary end end assert_equal mapping.lookup('varchar(20)'), string assert_equal mapping.lookup('varchar'), binary end def test_additional_lookup_args mapping = TypeMap.new mapping.register_type(/varchar/i) do |type, limit| if limit > 255 'text' else 'string' end end mapping.alias_type(/string/i, 'varchar') assert_equal mapping.lookup('varchar', 200), 'string' assert_equal mapping.lookup('varchar', 400), 'text' assert_equal mapping.lookup('string', 400), 'text' end def test_requires_value_or_block mapping = TypeMap.new assert_raises(ArgumentError) do mapping.register_type(/only key/i) end end def test_lookup_non_strings mapping = HashLookupTypeMap.new mapping.register_type(1, 'string') mapping.register_type(2, 'int') mapping.alias_type(3, 1) assert_equal mapping.lookup(1), 'string' assert_equal mapping.lookup(2), 'int' assert_equal mapping.lookup(3), 'string' assert_kind_of Type::Value, mapping.lookup(4) end def test_fetch mapping = TypeMap.new mapping.register_type(1, "string") assert_equal "string", mapping.fetch(1) { "int" } assert_equal "int", mapping.fetch(2) { "int" } end def test_fetch_yields_args mapping = TypeMap.new assert_equal "foo-1-2-3", mapping.fetch("foo", 1, 2, 3) { |*args| args.join("-") } assert_equal "bar-1-2-3", mapping.fetch("bar", 1, 2, 3) { |*args| args.join("-") } end def test_fetch_memoizes mapping = TypeMap.new looked_up = false mapping.register_type(1) do fail if looked_up looked_up = true "string" end assert_equal "string", mapping.fetch(1) assert_equal "string", mapping.fetch(1) end def test_fetch_memoizes_on_args mapping = TypeMap.new mapping.register_type("foo") { |*args| args.join("-") } assert_equal "foo-1-2-3", mapping.fetch("foo", 1, 2, 3) { |*args| args.join("-") } assert_equal "foo-2-3-4", mapping.fetch("foo", 2, 3, 4) { |*args| args.join("-") } end def test_register_clears_cache mapping = TypeMap.new mapping.register_type(1, "string") mapping.lookup(1) mapping.register_type(1, "int") assert_equal "int", mapping.lookup(1) end end end end rails-4.2.6/activerecord/test/cases/type/unsigned_integer_test.rb000066400000000000000000000007251266740050600252740ustar00rootroot00000000000000require "cases/helper" require "models/company" module ActiveRecord module Type class UnsignedIntegerTest < ActiveRecord::TestCase test "unsigned int max value is in range" do assert_equal(4294967295, UnsignedInteger.new.type_cast_for_database(4294967295)) end test "minus value is out of range" do assert_raises(::RangeError) do UnsignedInteger.new.type_cast_for_database(-1) end end end end end rails-4.2.6/activerecord/test/cases/types_test.rb000066400000000000000000000120531266740050600221230ustar00rootroot00000000000000require "cases/helper" module ActiveRecord module ConnectionAdapters class TypesTest < ActiveRecord::TestCase def test_type_cast_boolean type = Type::Boolean.new assert type.type_cast_from_user('').nil? assert type.type_cast_from_user(nil).nil? assert type.type_cast_from_user(true) assert type.type_cast_from_user(1) assert type.type_cast_from_user('1') assert type.type_cast_from_user('t') assert type.type_cast_from_user('T') assert type.type_cast_from_user('true') assert type.type_cast_from_user('TRUE') assert type.type_cast_from_user('on') assert type.type_cast_from_user('ON') # explicitly check for false vs nil assert_equal false, type.type_cast_from_user(false) assert_equal false, type.type_cast_from_user(0) assert_equal false, type.type_cast_from_user('0') assert_equal false, type.type_cast_from_user('f') assert_equal false, type.type_cast_from_user('F') assert_equal false, type.type_cast_from_user('false') assert_equal false, type.type_cast_from_user('FALSE') assert_equal false, type.type_cast_from_user('off') assert_equal false, type.type_cast_from_user('OFF') assert_deprecated do assert_equal false, type.type_cast_from_user(' ') assert_equal false, type.type_cast_from_user("\u3000\r\n") assert_equal false, type.type_cast_from_user("\u0000") assert_equal false, type.type_cast_from_user('SOMETHING RANDOM') end end def test_type_cast_float type = Type::Float.new assert_equal 1.0, type.type_cast_from_user("1") end def test_changing_float type = Type::Float.new assert type.changed?(5.0, 5.0, '5wibble') assert_not type.changed?(5.0, 5.0, '5') assert_not type.changed?(5.0, 5.0, '5.0') assert_not type.changed?(nil, nil, nil) end def test_type_cast_binary type = Type::Binary.new assert_equal nil, type.type_cast_from_user(nil) assert_equal "1", type.type_cast_from_user("1") assert_equal 1, type.type_cast_from_user(1) end def test_type_cast_time type = Type::Time.new assert_equal nil, type.type_cast_from_user(nil) assert_equal nil, type.type_cast_from_user('') assert_equal nil, type.type_cast_from_user('ABC') time_string = Time.now.utc.strftime("%T") assert_equal time_string, type.type_cast_from_user(time_string).strftime("%T") end def test_type_cast_datetime_and_timestamp type = Type::DateTime.new assert_equal nil, type.type_cast_from_user(nil) assert_equal nil, type.type_cast_from_user('') assert_equal nil, type.type_cast_from_user(' ') assert_equal nil, type.type_cast_from_user('ABC') datetime_string = Time.now.utc.strftime("%FT%T") assert_equal datetime_string, type.type_cast_from_user(datetime_string).strftime("%FT%T") end def test_type_cast_date type = Type::Date.new assert_equal nil, type.type_cast_from_user(nil) assert_equal nil, type.type_cast_from_user('') assert_equal nil, type.type_cast_from_user(' ') assert_equal nil, type.type_cast_from_user('ABC') date_string = Time.now.utc.strftime("%F") assert_equal date_string, type.type_cast_from_user(date_string).strftime("%F") end def test_type_cast_duration_to_integer type = Type::Integer.new assert_equal 1800, type.type_cast_from_user(30.minutes) assert_equal 7200, type.type_cast_from_user(2.hours) end def test_string_to_time_with_timezone [:utc, :local].each do |zone| with_timezone_config default: zone do type = Type::DateTime.new assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.type_cast_from_user("Wed, 04 Sep 2013 03:00:00 EAT") end end end def test_type_equality assert_equal Type::Value.new, Type::Value.new assert_not_equal Type::Value.new, Type::Integer.new assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2) end if current_adapter?(:SQLite3Adapter) def test_binary_encoding type = SQLite3Binary.new utf8_string = "a string".encode(Encoding::UTF_8) type_cast = type.type_cast_from_user(utf8_string) assert_equal Encoding::ASCII_8BIT, type_cast.encoding end end def test_attributes_which_are_invalid_for_database_can_still_be_reassigned type_which_cannot_go_to_the_database = Type::Value.new def type_which_cannot_go_to_the_database.type_cast_for_database(*) raise end klass = Class.new(ActiveRecord::Base) do self.table_name = 'posts' attribute :foo, type_which_cannot_go_to_the_database end model = klass.new model.foo = "foo" model.foo = "bar" assert_equal "bar", model.foo end end end end rails-4.2.6/activerecord/test/cases/unconnected_test.rb000066400000000000000000000014371266740050600232700ustar00rootroot00000000000000require "cases/helper" class TestRecord < ActiveRecord::Base end class TestUnconnectedAdapter < ActiveRecord::TestCase self.use_transactional_fixtures = false def setup @underlying = ActiveRecord::Base.connection @specification = ActiveRecord::Base.remove_connection end teardown do @underlying = nil ActiveRecord::Base.establish_connection(@specification) load_schema if in_memory_db? end def test_connection_no_longer_established assert_raise(ActiveRecord::ConnectionNotEstablished) do TestRecord.find(1) end assert_raise(ActiveRecord::ConnectionNotEstablished) do TestRecord.new.save end end def test_underlying_adapter_no_longer_active assert !@underlying.active?, "Removed adapter should no longer be active" end end rails-4.2.6/activerecord/test/cases/validations/000077500000000000000000000000001266740050600217075ustar00rootroot00000000000000rails-4.2.6/activerecord/test/cases/validations/association_validation_test.rb000066400000000000000000000061611266740050600300250ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/reply' require 'models/man' require 'models/interest' class AssociationValidationTest < ActiveRecord::TestCase fixtures :topics repair_validations(Topic, Reply) def test_validates_associated_many Topic.validates_associated(:replies) Reply.validates_presence_of(:content) t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")] assert !t.valid? assert t.errors[:replies].any? assert_equal 1, r.errors.count # make sure all associated objects have been validated assert_equal 0, r2.errors.count assert_equal 1, r3.errors.count assert_equal 0, r4.errors.count r.content = r3.content = "non-empty" assert t.valid? end def test_validates_associated_one Reply.validates :topic, :associated => true Topic.validates_presence_of( :content ) r = Reply.new("title" => "A reply", "content" => "with content!") r.topic = Topic.create("title" => "uhohuhoh") assert !r.valid? assert r.errors[:topic].any? r.topic.content = "non-empty" assert r.valid? end def test_validates_associated_marked_for_destruction Topic.validates_associated(:replies) Reply.validates_presence_of(:content) t = Topic.new t.replies << Reply.new assert t.invalid? t.replies.first.mark_for_destruction assert t.valid? end def test_validates_associated_with_custom_message_using_quotes Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes" Topic.validates_presence_of :content r = Reply.create("title" => "A reply", "content" => "with content!") r.topic = Topic.create("title" => "uhohuhoh") assert !r.valid? assert_equal ["This string contains 'single' and \"double\" quotes"], r.errors[:topic] end def test_validates_associated_missing Reply.validates_presence_of(:topic) r = Reply.create("title" => "A reply", "content" => "with content!") assert !r.valid? assert r.errors[:topic].any? r.topic = Topic.first assert r.valid? end def test_validates_presence_of_belongs_to_association__parent_is_new_record repair_validations(Interest) do # Note that Interest and Man have the :inverse_of option set Interest.validates_presence_of(:man) man = Man.new(:name => 'John') interest = man.interests.build(:topic => 'Airplanes') assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" end end def test_validates_presence_of_belongs_to_association__existing_parent repair_validations(Interest) do Interest.validates_presence_of(:man) man = Man.create!(:name => 'John') interest = man.interests.build(:topic => 'Airplanes') assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" end end end rails-4.2.6/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb000066400000000000000000000067471266740050600316600ustar00rootroot00000000000000require "cases/helper" require 'models/topic' class I18nGenerateMessageValidationTest < ActiveRecord::TestCase def setup Topic.clear_validators! @topic = Topic.new I18n.backend = I18n::Backend::Simple.new end def reset_i18n_load_path @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new yield ensure I18n.load_path.replace @old_load_path I18n.backend = @old_backend end # validates_associated: generate_message(attr_name, :invalid, :message => custom_message, :value => value) def test_generate_message_invalid_with_default_message assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :value => 'title') end def test_generate_message_invalid_with_custom_message assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :message => 'custom message %{value}', :value => 'title') end # validates_uniqueness_of: generate_message(attr_name, :taken, :message => custom_message) def test_generate_message_taken_with_default_message assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :value => 'title') end def test_generate_message_taken_with_custom_message assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :message => 'custom message %{value}', :value => 'title') end # ActiveRecord#RecordInvalid exception test "RecordInvalid exception can be localized" do topic = Topic.new topic.errors.add(:title, :invalid) topic.errors.add(:title, :blank) assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message end test "RecordInvalid exception translation falls back to the :errors namespace" do reset_i18n_load_path do I18n.backend.store_translations 'en', :errors => {:messages => {:record_invalid => 'fallback message'}} topic = Topic.new topic.errors.add(:title, :blank) assert_equal "fallback message", ActiveRecord::RecordInvalid.new(topic).message end end test "translation for 'taken' can be overridden" do reset_i18n_load_path do I18n.backend.store_translations "en", {errors: {attributes: {title: {taken: "Custom taken message" }}}} assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, :value => 'title') end end test "translation for 'taken' can be overridden in activerecord scope" do reset_i18n_load_path do I18n.backend.store_translations "en", {activerecord: {errors: {messages: {taken: "Custom taken message" }}}} assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, :value => 'title') end end test "translation for 'taken' can be overridden in activerecord model scope" do reset_i18n_load_path do I18n.backend.store_translations "en", {activerecord: {errors: {models: {topic: {taken: "Custom taken message" }}}}} assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, :value => 'title') end end test "translation for 'taken' can be overridden in activerecord attributes scope" do reset_i18n_load_path do I18n.backend.store_translations "en", {activerecord: {errors: {models: {topic: {attributes: {title: {taken: "Custom taken message" }}}}}}} assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, :value => 'title') end end end rails-4.2.6/activerecord/test/cases/validations/i18n_validation_test.rb000066400000000000000000000065651266740050600263000ustar00rootroot00000000000000require "cases/helper" require 'models/topic' require 'models/reply' class I18nValidationTest < ActiveRecord::TestCase repair_validations(Topic, Reply) def setup repair_validations(Topic, Reply) Reply.validates_presence_of(:title) @topic = Topic.new @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}}) end teardown do I18n.load_path.replace @old_load_path I18n.backend = @old_backend end def unique_topic @unique ||= Topic.create :title => 'unique!' end def replied_topic @replied_topic ||= begin topic = Topic.create(:title => "topic") topic.replies << Reply.new topic end end # A set of common cases for ActiveModel::Validations message generation that # are used to generate tests to keep things DRY # COMMON_CASES = [ # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], [ "given custom message", {:message => "custom"}, {:message => "custom"}], [ "given if condition", {:if => lambda { true }}, {}], [ "given unless condition", {:unless => lambda { false }}, {}], [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }] # TODO Add :on case, but below doesn't work, because then the validation isn't run for some reason # even when using .save instead .valid? # [ "given on condition", {on: :save}, {}] ] # validates_uniqueness_of w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_uniqueness_of on generated message #{name}" do Topic.validates_uniqueness_of :title, validation_options @topic.title = unique_topic.title @topic.errors.expects(:generate_message).with(:title, :taken, generate_message_options.merge(:value => 'unique!')) @topic.valid? end end # validates_associated w/ mocha COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_associated on generated message #{name}" do Topic.validates_associated :replies, validation_options replied_topic.errors.expects(:generate_message).with(:replies, :invalid, generate_message_options.merge(:value => replied_topic.replies)) replied_topic.save end end # validates_associated w/o mocha def test_validates_associated_finds_custom_model_key_translation I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}} I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} Topic.validates_associated :replies replied_topic.valid? assert_equal ['custom message'], replied_topic.errors[:replies].uniq end def test_validates_associated_finds_global_default_translation I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} Topic.validates_associated :replies replied_topic.valid? assert_equal ['global message'], replied_topic.errors[:replies] end end rails-4.2.6/activerecord/test/cases/validations/length_validation_test.rb000066400000000000000000000024601266740050600267700ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "cases/helper" require 'models/owner' require 'models/pet' class LengthValidationTest < ActiveRecord::TestCase fixtures :owners repair_validations(Owner) def test_validates_size_of_association repair_validations Owner do assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } o = Owner.new('name' => 'nopets') assert !o.save assert o.errors[:pets].any? o.pets.build('name' => 'apet') assert o.valid? end end def test_validates_size_of_association_using_within repair_validations Owner do assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 } o = Owner.new('name' => 'nopets') assert !o.save assert o.errors[:pets].any? o.pets.build('name' => 'apet') assert o.valid? 2.times { o.pets.build('name' => 'apet') } assert !o.save assert o.errors[:pets].any? end end def test_validates_size_of_association_utf8 repair_validations Owner do assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } o = Owner.new('name' => 'ã‚ã„ã†ãˆãŠã‹ããã‘ã“') assert !o.save assert o.errors[:pets].any? o.pets.build('name' => 'ã‚ã„ã†ãˆãŠã‹ããã‘ã“') assert o.valid? end end end rails-4.2.6/activerecord/test/cases/validations/presence_validation_test.rb000066400000000000000000000030631266740050600273130ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'models/man' require 'models/face' require 'models/interest' require 'models/speedometer' require 'models/dashboard' class PresenceValidationTest < ActiveRecord::TestCase class Boy < Man; end repair_validations(Boy) def test_validates_presence_of_non_association Boy.validates_presence_of(:name) b = Boy.new assert b.invalid? b.name = "Alex" assert b.valid? end def test_validates_presence_of_has_one Boy.validates_presence_of(:face) b = Boy.new assert b.invalid?, "should not be valid if has_one association missing" assert_equal 1, b.errors[:face].size, "validates_presence_of should only add one error" end def test_validates_presence_of_has_one_marked_for_destruction Boy.validates_presence_of(:face) b = Boy.new f = Face.new b.face = f assert b.valid? f.mark_for_destruction assert b.invalid? end def test_validates_presence_of_has_many_marked_for_destruction Boy.validates_presence_of(:interests) b = Boy.new b.interests << [i1 = Interest.new, i2 = Interest.new] assert b.valid? i1.mark_for_destruction assert b.valid? i2.mark_for_destruction assert b.invalid? end def test_validates_presence_doesnt_convert_to_array speedometer = Class.new(Speedometer) speedometer.validates_presence_of :dashboard dash = Dashboard.new # dashboard has to_a method def dash.to_a; ['(/)', '(\)']; end s = speedometer.new s.dashboard = dash assert_nothing_raised { s.valid? } end end rails-4.2.6/activerecord/test/cases/validations/uniqueness_validation_test.rb000066400000000000000000000366261266740050600277210ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'models/topic' require 'models/reply' require 'models/warehouse_thing' require 'models/guid' require 'models/event' require 'models/dashboard' class Wizard < ActiveRecord::Base self.abstract_class = true validates_uniqueness_of :name end class IneptWizard < Wizard validates_uniqueness_of :city end class Conjurer < IneptWizard end class Thaumaturgist < IneptWizard end class ReplyTitle; end class ReplyWithTitleObject < Reply validates_uniqueness_of :content, :scope => :title def title; ReplyTitle.new; end end class Employee < ActiveRecord::Base self.table_name = 'postgresql_arrays' validates_uniqueness_of :nicknames end class TopicWithUniqEvent < Topic belongs_to :event, foreign_key: :parent_id validates :event, uniqueness: true end class BigIntTest < ActiveRecord::Base INT_MAX_VALUE = 2147483647 self.table_name = 'cars' validates :engines_count, uniqueness: true, inclusion: { in: 0..INT_MAX_VALUE } end class BigIntReverseTest < ActiveRecord::Base INT_MAX_VALUE = 2147483647 self.table_name = 'cars' validates :engines_count, inclusion: { in: 0..INT_MAX_VALUE } validates :engines_count, uniqueness: true end class UniquenessValidationTest < ActiveRecord::TestCase INT_MAX_VALUE = 2147483647 fixtures :topics, 'warehouse-things' repair_validations(Topic, Reply) def test_validate_uniqueness Topic.validates_uniqueness_of(:title) t = Topic.new("title" => "I'm uniqué!") assert t.save, "Should save t as unique" t.content = "Remaining unique" assert t.save, "Should still save t as unique" t2 = Topic.new("title" => "I'm uniqué!") assert !t2.valid?, "Shouldn't be valid" assert !t2.save, "Shouldn't save t2 as unique" assert_equal ["has already been taken"], t2.errors[:title] t2.title = "Now I am really also unique" assert t2.save, "Should now save t2 as unique" end def test_validate_uniqueness_with_alias_attribute Topic.alias_attribute :new_title, :title Topic.validates_uniqueness_of(:new_title) topic = Topic.new(new_title: 'abc') assert topic.valid? end def test_validates_uniqueness_with_nil_value Topic.validates_uniqueness_of(:title) t = Topic.new("title" => nil) assert t.save, "Should save t as unique" t2 = Topic.new("title" => nil) assert !t2.valid?, "Shouldn't be valid" assert !t2.save, "Shouldn't save t2 as unique" assert_equal ["has already been taken"], t2.errors[:title] end def test_validates_uniqueness_with_validates Topic.validates :title, :uniqueness => true Topic.create!('title' => 'abc') t2 = Topic.new('title' => 'abc') assert !t2.valid? assert t2.errors[:title] end def test_validate_uniqueness_when_integer_out_of_range entry = BigIntTest.create(engines_count: INT_MAX_VALUE + 1) assert_equal entry.errors[:engines_count], ['is not included in the list'] end def test_validate_uniqueness_when_integer_out_of_range_show_order_does_not_matter entry = BigIntReverseTest.create(engines_count: INT_MAX_VALUE + 1) assert_equal entry.errors[:engines_count], ['is not included in the list'] end def test_validates_uniqueness_with_newline_chars Topic.validates_uniqueness_of(:title, :case_sensitive => false) t = Topic.new("title" => "new\nline") assert t.save, "Should save t as unique" end def test_validate_uniqueness_with_scope Reply.validates_uniqueness_of(:content, :scope => "parent_id") t = Topic.create("title" => "I'm unique!") r1 = t.replies.create "title" => "r1", "content" => "hello world" assert r1.valid?, "Saving r1" r2 = t.replies.create "title" => "r2", "content" => "hello world" assert !r2.valid?, "Saving r2 first time" r2.content = "something else" assert r2.save, "Saving r2 second time" t2 = Topic.create("title" => "I'm unique too!") r3 = t2.replies.create "title" => "r3", "content" => "hello world" assert r3.valid?, "Saving r3" end def test_validate_uniqueness_with_object_scope Reply.validates_uniqueness_of(:content, :scope => :topic) t = Topic.create("title" => "I'm unique!") r1 = t.replies.create "title" => "r1", "content" => "hello world" assert r1.valid?, "Saving r1" r2 = t.replies.create "title" => "r2", "content" => "hello world" assert !r2.valid?, "Saving r2 first time" end def test_validate_uniqueness_with_composed_attribute_scope r1 = ReplyWithTitleObject.create "title" => "r1", "content" => "hello world" assert r1.valid?, "Saving r1" r2 = ReplyWithTitleObject.create "title" => "r1", "content" => "hello world" assert !r2.valid?, "Saving r2 first time" end def test_validate_uniqueness_with_object_arg Reply.validates_uniqueness_of(:topic) t = Topic.create("title" => "I'm unique!") r1 = t.replies.create "title" => "r1", "content" => "hello world" assert r1.valid?, "Saving r1" r2 = t.replies.create "title" => "r2", "content" => "hello world" assert !r2.valid?, "Saving r2 first time" end def test_validate_uniqueness_scoped_to_defining_class t = Topic.create("title" => "What, me worry?") r1 = t.unique_replies.create "title" => "r1", "content" => "a barrel of fun" assert r1.valid?, "Saving r1" r2 = t.silly_unique_replies.create "title" => "r2", "content" => "a barrel of fun" assert !r2.valid?, "Saving r2" # Should succeed as validates_uniqueness_of only applies to # UniqueReply and its subclasses r3 = t.replies.create "title" => "r2", "content" => "a barrel of fun" assert r3.valid?, "Saving r3" end def test_validate_uniqueness_with_scope_array Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id]) t = Topic.create("title" => "The earth is actually flat!") r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply" assert r1.valid?, "Saving r1" r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..." assert !r2.valid?, "Saving r2. Double reply by same author." r2.author_email_address = "jeremy_alt_email@rubyonrails.com" assert r2.save, "Saving r2 the second time." r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic" assert !r3.valid?, "Saving r3" r3.author_name = "jj" assert r3.save, "Saving r3 the second time." r3.author_name = "jeremy" assert !r3.save, "Saving r3 the third time." end def test_validate_case_insensitive_uniqueness Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true) t = Topic.new("title" => "I'm unique!", :parent_id => 2) assert t.save, "Should save t as unique" t.content = "Remaining unique" assert t.save, "Should still save t as unique" t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1) assert !t2.valid?, "Shouldn't be valid" assert !t2.save, "Shouldn't save t2 as unique" assert t2.errors[:title].any? assert t2.errors[:parent_id].any? assert_equal ["has already been taken"], t2.errors[:title] t2.title = "I'm truly UNIQUE!" assert !t2.valid?, "Shouldn't be valid" assert !t2.save, "Shouldn't save t2 as unique" assert t2.errors[:title].empty? assert t2.errors[:parent_id].any? t2.parent_id = 4 assert t2.save, "Should now save t2 as unique" t2.parent_id = nil t2.title = nil assert t2.valid?, "should validate with nil" assert t2.save, "should save with nil" t_utf8 = Topic.new("title" => "Я тоже уникальный!") assert t_utf8.save, "Should save t_utf8 as unique" # If database hasn't UTF-8 character set, this test fails if Topic.all.merge!(:select => 'LOWER(title) AS title').find(t_utf8.id).title == "Ñ Ñ‚Ð¾Ð¶Ðµ уникальный!" t2_utf8 = Topic.new("title" => "Ñ Ñ‚Ð¾Ð¶Ðµ УÐИКÐЛЬÐЫЙ!") assert !t2_utf8.valid?, "Shouldn't be valid" assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" end end def test_validate_case_sensitive_uniqueness_with_special_sql_like_chars Topic.validates_uniqueness_of(:title, :case_sensitive => true) t = Topic.new("title" => "I'm unique!") assert t.save, "Should save t as unique" t2 = Topic.new("title" => "I'm %") assert t2.save, "Should save t2 as unique" t3 = Topic.new("title" => "I'm uniqu_!") assert t3.save, "Should save t3 as unique" end def test_validate_case_insensitive_uniqueness_with_special_sql_like_chars Topic.validates_uniqueness_of(:title, :case_sensitive => false) t = Topic.new("title" => "I'm unique!") assert t.save, "Should save t as unique" t2 = Topic.new("title" => "I'm %") assert t2.save, "Should save t2 as unique" t3 = Topic.new("title" => "I'm uniqu_!") assert t3.save, "Should save t3 as unique" end def test_validate_case_sensitive_uniqueness Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true) t = Topic.new("title" => "I'm unique!") assert t.save, "Should save t as unique" t.content = "Remaining unique" assert t.save, "Should still save t as unique" t2 = Topic.new("title" => "I'M UNIQUE!") assert t2.valid?, "Should be valid" assert t2.save, "Should save t2 as unique" assert t2.errors[:title].empty? assert t2.errors[:parent_id].empty? assert_not_equal ["has already been taken"], t2.errors[:title] t3 = Topic.new("title" => "I'M uNiQUe!") assert t3.valid?, "Should be valid" assert t3.save, "Should save t2 as unique" assert t3.errors[:title].empty? assert t3.errors[:parent_id].empty? assert_not_equal ["has already been taken"], t3.errors[:title] end def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer Topic.validates_uniqueness_of(:title, :case_sensitive => true) Topic.create!('title' => 101) t2 = Topic.new('title' => 101) assert !t2.valid? assert t2.errors[:title] end def test_validate_uniqueness_with_non_standard_table_names i1 = WarehouseThing.create(:value => 1000) assert !i1.valid?, "i1 should not be valid" assert i1.errors[:value].any?, "Should not be empty" end def test_validates_uniqueness_inside_scoping Topic.validates_uniqueness_of(:title) Topic.where(:author_name => "David").scoping do t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary") assert t1.save t2 = Topic.new("title" => "I'm unique!", "author_name" => "David") assert !t2.valid? end end def test_validate_uniqueness_with_columns_which_are_sql_keywords repair_validations(Guid) do Guid.validates_uniqueness_of :key g = Guid.new g.key = "foo" assert_nothing_raised { !g.valid? } end end def test_validate_uniqueness_with_limit # Event.title is limited to 5 characters e1 = Event.create(:title => "abcde") assert e1.valid?, "Could not create an event with a unique, 5 character title" e2 = Event.create(:title => "abcdefgh") assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" end def test_validate_uniqueness_with_limit_and_utf8 # Event.title is limited to 5 characters e1 = Event.create(:title => "一二三四五") assert e1.valid?, "Could not create an event with a unique, 5 character title" e2 = Event.create(:title => "一二三四五六七八") assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" end def test_validate_straight_inheritance_uniqueness w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork") assert w1.valid?, "Saving w1" # Should use validation from base class (which is abstract) w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm") assert !w2.valid?, "w2 shouldn't be valid" assert w2.errors[:name].any?, "Should have errors for name" assert_equal ["has already been taken"], w2.errors[:name], "Should have uniqueness message for name" w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm") assert !w3.valid?, "w3 shouldn't be valid" assert w3.errors[:name].any?, "Should have errors for name" assert_equal ["has already been taken"], w3.errors[:name], "Should have uniqueness message for name" w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm") assert w4.valid?, "Saving w4" w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre") assert !w5.valid?, "w5 shouldn't be valid" assert w5.errors[:name].any?, "Should have errors for name" assert_equal ["has already been taken"], w5.errors[:name], "Should have uniqueness message for name" w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm") assert !w6.valid?, "w6 shouldn't be valid" assert w6.errors[:city].any?, "Should have errors for city" assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city" end def test_validate_uniqueness_with_conditions Topic.validates_uniqueness_of :title, conditions: -> { where(approved: true) } Topic.create("title" => "I'm a topic", "approved" => true) Topic.create("title" => "I'm an unapproved topic", "approved" => false) t3 = Topic.new("title" => "I'm a topic", "approved" => true) assert !t3.valid?, "t3 shouldn't be valid" t4 = Topic.new("title" => "I'm an unapproved topic", "approved" => false) assert t4.valid?, "t4 should be valid" end def test_validate_uniqueness_with_non_callable_conditions_is_not_supported assert_raises(ArgumentError) { Topic.validates_uniqueness_of :title, conditions: Topic.where(approved: true) } end if current_adapter? :PostgreSQLAdapter def test_validate_uniqueness_with_array_column e1 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [1000, 1200]) assert e1.persisted?, "Saving e1" e2 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [2200]) assert !e2.persisted?, "e2 shouldn't be valid" assert e2.errors[:nicknames].any?, "Should have errors for nicknames" assert_equal ["has already been taken"], e2.errors[:nicknames], "Should have uniqueness message for nicknames" end end def test_validate_uniqueness_on_existing_relation event = Event.create assert TopicWithUniqEvent.create(event: event).valid? topic = TopicWithUniqEvent.new(event: event) assert_not topic.valid? assert_equal ['has already been taken'], topic.errors[:event] end def test_validate_uniqueness_on_empty_relation topic = TopicWithUniqEvent.new assert topic.valid? end def test_validate_uniqueness_without_primary_key klass = Class.new(ActiveRecord::Base) do self.table_name = "dashboards" validates_uniqueness_of :dashboard_id def self.name; "Dashboard" end end abc = klass.create!(dashboard_id: "abc") assert klass.new(dashboard_id: "xyz").valid? assert_not klass.new(dashboard_id: "abc").valid? abc.dashboard_id = "def" e = assert_raises ActiveRecord::UnknownPrimaryKey do abc.save! end assert_match(/\AUnknown primary key for table dashboards in model/, e.message) assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message) end end rails-4.2.6/activerecord/test/cases/validations_repair_helper.rb000066400000000000000000000007141266740050600251370ustar00rootroot00000000000000module ActiveRecord module ValidationsRepairHelper extend ActiveSupport::Concern module ClassMethods def repair_validations(*model_classes) teardown do model_classes.each do |k| k.clear_validators! end end end end def repair_validations(*model_classes) yield if block_given? ensure model_classes.each do |k| k.clear_validators! end end end end rails-4.2.6/activerecord/test/cases/validations_test.rb000066400000000000000000000112301266740050600232700ustar00rootroot00000000000000# encoding: utf-8 require "cases/helper" require 'models/topic' require 'models/reply' require 'models/person' require 'models/developer' require 'models/computer' require 'models/parrot' require 'models/company' class ValidationsTest < ActiveRecord::TestCase fixtures :topics, :developers # Most of the tests mess with the validations of Topic, so lets repair it all the time. # Other classes we mess with will be dealt with in the specific tests repair_validations(Topic) def test_valid_uses_create_context_when_new r = WrongReply.new r.title = "Wrong Create" assert_not r.valid? assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid" assert_equal ["is Wrong Create"], r.errors[:title], "A reply with a bad content should contain an error" end def test_valid_uses_update_context_when_persisted r = WrongReply.new r.title = "Bad" r.content = "Good" assert r.save, "First validation should be successful" r.title = "Wrong Update" assert_not r.valid?, "Second validation should fail" assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid" assert_equal ["is Wrong Update"], r.errors[:title], "A reply with a bad content should contain an error" end def test_valid_using_special_context r = WrongReply.new(:title => "Valid title") assert !r.valid?(:special_case) assert_equal "Invalid", r.errors[:author_name].join r.author_name = "secret" r.content = "Good" assert r.valid?(:special_case) r.author_name = nil assert_not r.valid?(:special_case) assert_equal "Invalid", r.errors[:author_name].join r.author_name = "secret" assert r.valid?(:special_case) end def test_validate r = WrongReply.new r.validate assert_empty r.errors[:author_name] r.validate(:special_case) assert_not_empty r.errors[:author_name] r.author_name = "secret" r.validate(:special_case) assert_empty r.errors[:author_name] end def test_invalid_record_exception assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! } assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! } r = WrongReply.new invalid = assert_raise ActiveRecord::RecordInvalid do r.save! end assert_equal r, invalid.record end def test_validate_with_bang assert_raise(ActiveRecord::RecordInvalid) do WrongReply.new.validate! end end def test_validate_with_bang_and_context assert_raise(ActiveRecord::RecordInvalid) do WrongReply.new.validate!(:special_case) end r = WrongReply.new(:title => "Valid title", :author_name => "secret", :content => "Good") assert r.validate!(:special_case) end def test_exception_on_create_bang_many assert_raise(ActiveRecord::RecordInvalid) do WrongReply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }]) end end def test_exception_on_create_bang_with_block assert_raise(ActiveRecord::RecordInvalid) do WrongReply.create!({ "title" => "OK" }) do |r| r.content = nil end end end def test_exception_on_create_bang_many_with_block assert_raise(ActiveRecord::RecordInvalid) do WrongReply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r| r.content = nil end end end def test_save_without_validation reply = WrongReply.new assert !reply.save assert reply.save(:validate => false) end def test_validates_acceptance_of_with_non_existent_table Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base) assert_nothing_raised ActiveRecord::StatementInvalid do IncorporealModel.validates_acceptance_of(:incorporeal_column) end end def test_throw_away_typing d = Developer.new("name" => "David", "salary" => "100,000") assert !d.valid? assert_equal 100, d.salary assert_equal "100,000", d.salary_before_type_cast end def test_validates_acceptance_of_as_database_column Topic.validates_acceptance_of(:approved) topic = Topic.create("approved" => true) assert topic["approved"] end def test_validators assert_equal 1, Parrot.validators.size assert_equal 1, Company.validators.size assert_equal 1, Parrot.validators_on(:name).size assert_equal 1, Company.validators_on(:name).size end def test_numericality_validation_with_mutation Topic.class_eval do attribute :wibble, ActiveRecord::Type::String.new validates_numericality_of :wibble, only_integer: true end topic = Topic.new(wibble: '123-4567') topic.wibble.gsub!('-', '') assert topic.valid? ensure Topic.reset_column_information end end rails-4.2.6/activerecord/test/cases/view_test.rb000066400000000000000000000056631266740050600217420ustar00rootroot00000000000000require "cases/helper" require "models/book" module ViewBehavior extend ActiveSupport::Concern included do fixtures :books end class Ebook < ActiveRecord::Base self.primary_key = "id" end def setup super @connection = ActiveRecord::Base.connection create_view "ebooks", <<-SQL SELECT id, name, status FROM books WHERE format = 'ebook' SQL end def teardown super drop_view "ebooks" end def test_reading books = Ebook.all assert_equal [books(:rfr).id], books.map(&:id) assert_equal ["Ruby for Rails"], books.map(&:name) end def test_table_exists view_name = Ebook.table_name # TODO: switch this assertion around once we changed #tables to not return views. assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" end def test_views_ara_valid_data_sources view_name = Ebook.table_name assert @connection.data_source_exists?(view_name), "'#{view_name}' should be a data source" end def test_column_definitions assert_equal([["id", :integer], ["name", :string], ["status", :integer]], Ebook.columns.map { |c| [c.name, c.type] }) end def test_attributes assert_equal({"id" => 2, "name" => "Ruby for Rails", "status" => 0}, Ebook.first.attributes) end def test_does_not_assume_id_column_as_primary_key model = Class.new(ActiveRecord::Base) do self.table_name = "ebooks" end assert_nil model.primary_key end end if ActiveRecord::Base.connection.supports_views? class ViewWithPrimaryKeyTest < ActiveRecord::TestCase include ViewBehavior private def create_view(name, query) @connection.execute "CREATE VIEW #{name} AS #{query}" end def drop_view(name) @connection.execute "DROP VIEW #{name}" if @connection.table_exists? name end end class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase fixtures :books class Paperback < ActiveRecord::Base; end setup do @connection = ActiveRecord::Base.connection @connection.execute <<-SQL CREATE VIEW paperbacks AS SELECT name, status FROM books WHERE format = 'paperback' SQL end teardown do @connection.execute "DROP VIEW paperbacks" if @connection.table_exists? "paperbacks" end def test_reading books = Paperback.all assert_equal ["Agile Web Development with Rails"], books.map(&:name) end def test_table_exists view_name = Paperback.table_name assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" end def test_column_definitions assert_equal([["name", :string], ["status", :integer]], Paperback.columns.map { |c| [c.name, c.type] }) end def test_attributes assert_equal({"name" => "Agile Web Development with Rails", "status" => 0}, Paperback.first.attributes) end def test_does_not_have_a_primary_key assert_nil Paperback.primary_key end end end rails-4.2.6/activerecord/test/cases/xml_serialization_test.rb000066400000000000000000000402301266740050600245120ustar00rootroot00000000000000require "cases/helper" require "rexml/document" require 'models/contact' require 'models/post' require 'models/author' require 'models/comment' require 'models/company_in_module' require 'models/toy' require 'models/topic' require 'models/reply' require 'models/company' class XmlSerializationTest < ActiveRecord::TestCase def test_should_serialize_default_root @xml = Contact.new.to_xml assert_match %r{^}, @xml assert_match %r{$}, @xml end def test_should_serialize_default_root_with_namespace @xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact" assert_match %r{^}, @xml assert_match %r{$}, @xml end def test_should_serialize_custom_root @xml = Contact.new.to_xml :root => 'xml_contact' assert_match %r{^}, @xml assert_match %r{$}, @xml end def test_should_allow_undasherized_tags @xml = Contact.new.to_xml :root => 'xml_contact', :dasherize => false assert_match %r{^}, @xml assert_match %r{$}, @xml assert_match %r{ 'xml_contact', :camelize => true assert_match %r{^}, @xml assert_match %r{$}, @xml assert_match %r{ 25).to_xml :skip_types => true assert %r{25}.match(@xml) end def test_should_include_yielded_additions @xml = Contact.new.to_xml do |xml| xml.creator "David" end assert_match %r{David}, @xml end def test_to_xml_with_block value = "Rockin' the block" xml = Contact.new.to_xml(:skip_instruct => true) do |_xml| _xml.tag! "arbitrary-element", value end assert_equal "", xml.first(9) assert xml.include?(%(#{value})) end def test_should_skip_instruct_for_included_records @contact = Contact.new @contact.alternative = Contact.new(:name => 'Copa Cabana') @xml = @contact.to_xml(:include => [ :alternative ]) assert_equal @xml.index(' 'aaron stack', :age => 25, :avatar => 'binarydata', :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => 'ruby' } ) end def test_should_serialize_string assert_match %r{aaron stack}, @contact.to_xml end def test_should_serialize_integer assert_match %r{25}, @contact.to_xml end def test_should_serialize_binary xml = @contact.to_xml assert_match %r{YmluYXJ5ZGF0YQ==\n}, xml assert_match %r{2006-08-01T00:00:00Z}, @contact.to_xml end def test_should_serialize_boolean assert_match %r{false}, @contact.to_xml end def test_should_serialize_hash assert_match %r{\s*ruby\s*}m, @contact.to_xml end def test_uses_serializable_hash_with_only_option def @contact.serializable_hash(options=nil) super(only: %w(name)) end xml = @contact.to_xml assert_match %r{aaron stack}, xml assert_no_match %r{age}, xml assert_no_match %r{awesome}, xml end def test_uses_serializable_hash_with_except_option def @contact.serializable_hash(options=nil) super(except: %w(age)) end xml = @contact.to_xml assert_match %r{aaron stack}, xml assert_match %r{false}, xml assert_no_match %r{age}, xml end def test_does_not_include_inheritance_column_from_sti @contact = ContactSti.new(@contact.attributes) assert_equal 'ContactSti', @contact.type xml = @contact.to_xml assert_match %r{aaron stack}, xml assert_no_match %r{aaron stack}, xml assert_no_match %r{age}, xml assert_no_match %r{ 'Mickey', :updated_at => Time.utc(2006, 8, 1)) assert_match %r{2006-07-31T17:00:00-07:00}, toy.to_xml end end def test_should_serialize_datetime_with_timezone_reloaded with_timezone_config zone: "Pacific Time (US & Canada)" do toy = Toy.create(:name => 'Minnie', :updated_at => Time.utc(2006, 8, 1)).reload assert_match %r{2006-07-31T17:00:00-07:00}, toy.to_xml end end end class NilXmlSerializationTest < ActiveRecord::TestCase def setup @xml = Contact.new.to_xml(:root => 'xml_contact') end def test_should_serialize_string assert_match %r{}, @xml end def test_should_serialize_integer assert %r{}.match(@xml) attributes = $1 assert_match %r{nil="true"}, attributes assert_match %r{type="integer"}, attributes end def test_should_serialize_binary assert %r{}.match(@xml) attributes = $1 assert_match %r{type="binary"}, attributes assert_match %r{encoding="base64"}, attributes assert_match %r{nil="true"}, attributes end def test_should_serialize_datetime assert %r{}.match(@xml) attributes = $1 assert_match %r{nil="true"}, attributes assert_match %r{type="dateTime"}, attributes end def test_should_serialize_boolean assert %r{}.match(@xml) attributes = $1 assert_match %r{type="boolean"}, attributes assert_match %r{nil="true"}, attributes end def test_should_serialize_yaml assert_match %r{}, @xml end end class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase fixtures :topics, :companies, :accounts, :authors, :posts, :projects def test_to_xml xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema written_on_in_current_timezone = topics(:first).written_on.xmlschema assert_equal "topic", xml.root.name assert_equal "The First Topic" , xml.elements["//title"].text assert_equal "David" , xml.elements["//author-name"].text assert_match "Have a nice day", xml.elements["//content"].text assert_equal "1", xml.elements["//id"].text assert_equal "integer" , xml.elements["//id"].attributes['type'] assert_equal "1", xml.elements["//replies-count"].text assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text assert_equal "dateTime" , xml.elements["//written-on"].attributes['type'] assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text assert_equal nil, xml.elements["//parent-id"].text assert_equal "integer", xml.elements["//parent-id"].attributes['type'] assert_equal "true", xml.elements["//parent-id"].attributes['nil'] # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) assert_equal "2004-04-15", xml.elements["//last-read"].text assert_equal "date" , xml.elements["//last-read"].attributes['type'] # Oracle and DB2 don't have true boolean or time-only fields unless current_adapter?(:OracleAdapter, :DB2Adapter) assert_equal "false", xml.elements["//approved"].text assert_equal "boolean" , xml.elements["//approved"].attributes['type'] assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text assert_equal "dateTime" , xml.elements["//bonus-time"].attributes['type'] end end def test_except_option xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) assert_equal "", xml.first(7) assert !xml.include?(%(The First Topic)) assert xml.include?(%(David)) xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) assert !xml.include?(%(The First Topic)) assert !xml.include?(%(David)) end # to_xml used to mess with the hash the user provided which # caused the builder to be reused. This meant the document kept # getting appended to. def test_modules projects = MyApplication::Business::Project.all xml = projects.to_xml root = projects.first.class.to_s.underscore.pluralize.tr('/','_').dasherize assert_match "<#{root} type=\"array\">", xml assert_match "", xml end def test_passing_hash_shouldnt_reuse_builder options = {:include=>:posts} david = authors(:david) first_xml_size = david.to_xml(options).size second_xml_size = david.to_xml(options).size assert_equal first_xml_size, second_xml_size end def test_include_uses_association_name xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0 assert_match %r{}, xml assert_match %r{}, xml assert_match %r{}, xml end def test_included_associations_should_skip_types xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0, :skip_types => true assert_match %r{}, xml assert_match %r{}, xml assert_match %r{}, xml end def test_including_has_many_association xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) assert_equal "", xml.first(7) assert xml.include?(%()) assert xml.include?(%(The Second Topic of the day)) end def test_including_belongs_to_association xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) assert !xml.include?("") xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) assert xml.include?("") end def test_including_multiple_associations xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) assert_equal "", xml.first(6) assert xml.include?(%()) assert xml.include?(%()) end def test_including_association_with_options xml = companies(:first_firm).to_xml( :indent => 0, :skip_instruct => true, :include => { :clients => { :only => :name } } ) assert_equal "", xml.first(6) assert xml.include?(%(Summit)) assert xml.include?(%()) end def test_methods_are_called_on_object xml = authors(:david).to_xml :methods => :label, :indent => 0 assert_match %r{}, xml end def test_should_not_call_methods_on_associations_that_dont_respond xml = authors(:david).to_xml :include=>:hello_posts, :methods => :label, :indent => 2 assert !authors(:david).hello_posts.first.respond_to?(:label) assert_match %r{^ }, xml assert_no_match %r{^