diff options
author | José Valim <jose.valim@gmail.com> | 2011-12-24 10:40:01 +0100 |
---|---|---|
committer | José Valim <jose.valim@gmail.com> | 2011-12-24 10:40:06 +0100 |
commit | 0152fe977343dc348f9a33493652b3f923e12397 (patch) | |
tree | 9aac1ae030dfc9ad9e7cef22e5d05e35011e5af1 | |
parent | 7c42b9321a8c7e47304f62e9cec8ad2d9019decf (diff) | |
parent | b4e97ea2d961c7ed99dcfa48044f4922378ff9cf (diff) | |
download | rails-0152fe977343dc348f9a33493652b3f923e12397.tar.gz rails-0152fe977343dc348f9a33493652b3f923e12397.tar.bz2 rails-0152fe977343dc348f9a33493652b3f923e12397.zip |
Merge branch 'gzip-index' which contains two features:
1) Adding gzip to pages cache, closes #4124
2) Allow scaffold/model/migration generators to accept a "index" and "uniq"
modifiers, as in: "tracking_id:integer:uniq" in order to generate (unique)
indexes. Some types also accept custom options, for instance, you can
specify the precision and scale for decimals as "price:decimal{7,2}".
This feature closes #2555.
18 files changed, 338 insertions, 53 deletions
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index d95770c049..9019c07bca 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -35,7 +35,7 @@ module AbstractController include AbstractController::ViewPaths included do - config_accessor :protected_instance_variables, :instance_reader => false + class_attribute :protected_instance_variables self.protected_instance_variables = [] end @@ -59,12 +59,6 @@ module AbstractController attr_internal_writer :view_context_class - # Explicitly define protected_instance_variables so it can be - # inherited and overwritten by other modules if needed. - def protected_instance_variables - config.protected_instance_variables - end - def view_context_class @_view_context_class ||= self.class.view_context_class end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 957bb7de6b..159f718029 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -16,7 +16,7 @@ module ActionController #:nodoc: # caches_page :show, :new # end # - # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used + # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used # that would normally trigger dynamic page generation. Page caching works by configuring a web server to first check for the # existence of files on disk, and to serve them directly when found, without passing the request through to Action Pack. # This is much faster than handling the full dynamic request in the usual way. @@ -38,23 +38,25 @@ module ActionController #:nodoc: extend ActiveSupport::Concern included do - ## - # :singleton-method: # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>. # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>Rails.root + "/public"</tt>). Changing # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your # web server to look in the new location for cached files. - config_accessor :page_cache_directory + class_attribute :page_cache_directory self.page_cache_directory ||= '' - ## - # :singleton-method: # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>. # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps. - config_accessor :page_cache_extension + class_attribute :page_cache_extension self.page_cache_extension ||= '.html' + + # The compression used for gzip. If false (default), the page is not compressed. + # If can be a symbol showing the ZLib compression method, for example, :best_compression + # or :best_speed or an integer configuring the compression level. + class_attribute :page_cache_compression + self.page_cache_compression ||= false end module ClassMethods @@ -66,24 +68,31 @@ module ActionController #:nodoc: instrument_page_cache :expire_page, path do File.delete(path) if File.exist?(path) + File.delete(path + '.gz') if File.exist?(path + '.gz') end end # Manually cache the +content+ in the key determined by +path+. Example: # cache_page "I'm the cached content", "/lists/show" - def cache_page(content, path, extension = nil) + def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION) return unless perform_caching path = page_cache_path(path, extension) instrument_page_cache :write_page, path do FileUtils.makedirs(File.dirname(path)) File.open(path, "wb+") { |f| f.write(content) } + if gzip + Zlib::GzipWriter.open(path + '.gz', gzip) { |f| f.write(content) } + end end end - # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that + # Caches the +actions+ using the page-caching approach that'll store + # the cache in a path within the page_cache_directory that # matches the triggering url. # + # You can also pass a :gzip option to override the class configuration one. + # # Usage: # # # cache the index action @@ -91,10 +100,28 @@ module ActionController #:nodoc: # # # cache the index action except for JSON requests # caches_page :index, :if => Proc.new { |c| !c.request.format.json? } + # + # # don't gzip images + # caches_page :image, :gzip => false def caches_page(*actions) return unless perform_caching options = actions.extract_options! - after_filter({:only => actions}.merge(options)) { |c| c.cache_page } + + gzip_level = options.fetch(:gzip, page_cache_compression) + gzip_level = case gzip_level + when Symbol + Zlib.const_get(gzip_level.to_s.upcase) + when Fixnum + gzip_level + when false + nil + else + Zlib::BEST_COMPRESSION + end + + after_filter({:only => actions}.merge(options)) do |c| + c.cache_page(nil, nil, gzip_level) + end end private @@ -136,7 +163,7 @@ module ActionController #:nodoc: # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used. # If no options are provided, the url of the current request being handled is used. Example: # cache_page "I'm the cached content", :controller => "lists", :action => "show" - def cache_page(content = nil, options = nil) + def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION) return unless self.class.perform_caching && caching_allowed? path = case options @@ -152,7 +179,7 @@ module ActionController #:nodoc: extension = ".#{type_symbol}" end - self.class.cache_page(content || response.body, path, extension) + self.class.cache_page(content || response.body, path, extension, gzip) end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index bd515bba82..50d7aac300 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -56,7 +56,7 @@ module ActionController include AbstractController::Helpers included do - config_accessor :helpers_path, :include_all_helpers + class_attribute :helpers_path, :include_all_helpers self.helpers_path ||= [] self.include_all_helpers = true end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 015e6b9955..34a38a5567 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -14,10 +14,14 @@ class CachingController < ActionController::Base end class PageCachingTestController < CachingController + self.page_cache_compression = :best_compression + caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } caches_page :found, :not_found caches_page :about_me - + caches_page :default_gzip + caches_page :no_gzip, :gzip => false + caches_page :gzip_level, :gzip => :best_speed def ok head :ok @@ -40,6 +44,18 @@ class PageCachingTestController < CachingController cache_page("Super soaker", "/index.html") end + def default_gzip + render :text => "Text" + end + + def no_gzip + render :text => "PNG" + end + + def gzip_level + render :text => "Big text" + end + def expire_custom_path expire_page("/index.html") head :ok @@ -115,6 +131,30 @@ class PageCachingTest < ActionController::TestCase assert !File.exist?("#{FILE_STORE_PATH}/index.html") end + def test_should_gzip_cache + get :custom_path + assert File.exist?("#{FILE_STORE_PATH}/index.html.gz") + + get :expire_custom_path + assert !File.exist?("#{FILE_STORE_PATH}/index.html.gz") + end + + def test_should_allow_to_disable_gzip + get :no_gzip + assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html") + assert !File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html.gz") + end + + def test_should_use_config_gzip_by_default + @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION) + get :default_gzip + end + + def test_should_set_gzip_level + @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED) + get :gzip_level + end + def test_should_cache_without_trailing_slash_on_url @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash' assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") @@ -194,7 +234,7 @@ class ActionCachingTestController < CachingController caches_action :show, :cache_path => 'http://test.host/custom/show' caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } caches_action :with_layout - caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } } + caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } } caches_action :layout_false, :layout => false caches_action :record_not_found, :four_oh_four, :simple_runtime_error @@ -224,7 +264,7 @@ class ActionCachingTestController < CachingController @cache_this = MockTime.now.to_f.to_s render :text => @cache_this end - + def record_not_found raise ActiveRecord::RecordNotFound, "oops!" end diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index f6159deeeb..1509e34473 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -3,7 +3,7 @@ require 'rails/generators/active_record' module ActiveRecord module Generators class MigrationGenerator < Base - argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" def create_migration_file set_local_assigns! diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index ce8d7eed42..d084a00ed7 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -2,14 +2,20 @@ class <%= migration_class_name %> < ActiveRecord::Migration <%- if migration_action == 'add' -%> def change <% attributes.each do |attribute| -%> - add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %> + 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 <%- else -%> def up <% attributes.each do |attribute| -%> <%- if migration_action -%> - <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %> + <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %> + <% if attribute.has_index? && migration_action == 'add' %> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <% end -%> <%- end -%> <%- end -%> end @@ -17,7 +23,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration def down <% attributes.reverse.each do |attribute| -%> <%- if migration_action -%> - <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %> + <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %> <%- end -%> <%- end -%> end diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb index f7caa43ac8..99a022461e 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -3,7 +3,7 @@ require 'rails/generators/active_record' module ActiveRecord module Generators class ModelGenerator < Base - argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" check_class_collision @@ -26,6 +26,10 @@ module ActiveRecord template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke end + def attributes_with_index + attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) } + end + hook_for :test_framework protected diff --git a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb index 851930344a..3a3cf86d73 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb @@ -2,16 +2,14 @@ class <%= migration_class_name %> < ActiveRecord::Migration def change create_table :<%= table_name %> do |t| <% attributes.each do |attribute| -%> - t.<%= attribute.type %> :<%= attribute.name %> + t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> <% end -%> <% if options[:timestamps] %> t.timestamps <% end -%> end -<% if options[:indexes] -%> -<% attributes.select {|attr| attr.reference? }.each do |attribute| -%> - add_index :<%= table_name %>, :<%= attribute.name %>_id -<% end -%> +<% attributes_with_index.each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <% end -%> end end diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index ec9bfd4d40..0bf9ca8887 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -64,6 +64,28 @@ end If you want a more complicated expiration scheme, you can use cache sweepers to expire cached objects when things change. This is covered in the section on Sweepers. +By default, page caching automatically gzips file (for example, to +products.html.gz+ if user requests +/products+) to reduce size of transmitted data (web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, compression ration is maximum). + +Nginx is able to serve compressed content directly from disk by enabling +gzip_static+: + +<plain> +location / { + gzip_static on; # to serve pre-gzipped version +} +</plain> + +You can disable gzipping by setting +:gzip+ option to false (for example, if action returns image): + +<ruby> + caches_page :image, :gzip => false +</ruby> + +Or, you can set custom gzip compression level (level names are taken from +Zlib+ constants): + +<ruby> + caches_page :image, :gzip => :best_speed +</ruby> + NOTE: Page caching ignores all parameters. For example +/products?page=1+ will be written out to the filesystem as +products.html+ with no reference to the +page+ parameter. Thus, if someone requests +/products?page=2+ later, they will get the cached first page. A workaround for this limitation is to include the parameters in the page's path, e.g. +/productions/page/1+. INFO: Page caching runs in an after filter. Thus, invalid requests won't generate spurious cache entries as long as you halt them. Typically, a redirection in some before filter that checks request preconditions does the job. diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 816d82cac3..132145474b 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -1,14 +1,48 @@ require 'active_support/time' require 'active_support/core_ext/object/inclusion' +require 'active_support/core_ext/object/blank' module Rails module Generators class GeneratedAttribute attr_accessor :name, :type + attr_reader :attr_options - def initialize(name, type) - type = :string if type.blank? - @name, @type = name, type.to_sym + class << self + def parse(column_definition) + name, type, has_index = column_definition.split(':') + + # if user provided "name:index" instead of "name:string:index" + # type should be set blank so GeneratedAttribute's constructor + # could set it to :string + has_index, type = type, nil if %w(index uniq).include?(type) + + type, attr_options = *parse_type_and_options(type) + new(name, type, has_index, attr_options) + end + + private + + # parse possible attribute options like :limit for string/text/binary/integer or :precision/:scale for decimals + # when declaring options curly brackets should be used + def parse_type_and_options(type) + case type + when /(string|text|binary|integer){(\d+)}/ + return $1, :limit => $2.to_i + when /decimal{(\d+),(\d+)}/ + return :decimal, :precision => $1.to_i, :scale => $2.to_i + else + return type, {} + end + end + end + + def initialize(name, type=nil, index_type=false, attr_options={}) + @name = name + @type = (type.presence || :string).to_sym + @has_index = %w(index uniq).include?(index_type) + @has_uniq_index = %w(uniq).include?(index_type) + @attr_options = attr_options end def field_type @@ -45,9 +79,29 @@ module Rails name.to_s.humanize end + def index_name + reference? ? "#{name}_id" : name + end + def reference? self.type.in?([:references, :belongs_to]) end + + def has_index? + @has_index + end + + def has_uniq_index? + @has_uniq_index + end + + def inject_options + @attr_options.blank? ? '' : ", #{@attr_options.to_s.gsub(/[{}]/, '')}" + end + + def inject_index_options + has_uniq_index? ? ", :unique => true" : '' + end end end end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index e96fc63ee9..9cef55e0a6 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -150,9 +150,8 @@ module Rails # Convert attributes array into GeneratedAttribute objects. def parse_attributes! #:nodoc: - self.attributes = (attributes || []).map do |key_value| - name, type = key_value.split(':') - Rails::Generators::GeneratedAttribute.new(name, type) + self.attributes = (attributes || []).map do |attr| + Rails::Generators::GeneratedAttribute.parse(attr) end end diff --git a/railties/lib/rails/generators/rails/migration/migration_generator.rb b/railties/lib/rails/generators/rails/migration/migration_generator.rb index 39fa5b63b1..f87dce1502 100644 --- a/railties/lib/rails/generators/rails/migration/migration_generator.rb +++ b/railties/lib/rails/generators/rails/migration/migration_generator.rb @@ -1,7 +1,7 @@ module Rails module Generators class MigrationGenerator < NamedBase #metagenerator - argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" hook_for :orm, :required => true end end diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb index 629d5eed3f..9bb29b784e 100644 --- a/railties/lib/rails/generators/rails/model/model_generator.rb +++ b/railties/lib/rails/generators/rails/model/model_generator.rb @@ -1,7 +1,7 @@ module Rails module Generators class ModelGenerator < NamedBase #metagenerator - argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" hook_for :orm, :required => true end end diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE index be1d113ed8..4a3eb2c7c7 100644 --- a/railties/lib/rails/generators/rails/scaffold/USAGE +++ b/railties/lib/rails/generators/rails/scaffold/USAGE @@ -7,23 +7,29 @@ Description: under_scored, as the first argument, and an optional list of attribute pairs. - Attribute pairs are field:type arguments specifying the - model's attributes. Timestamps are added by default, so you don't have to - specify them by hand as 'created_at:datetime updated_at:datetime'. + Attributes are field arguments specifying the model's attributes. You can + optionally pass the type and an index to each field. For instance: + "title body:text tracking_id:integer:uniq" will generate a title field of + string type, a body with text type and a tracking_id as an integer with an + unique index. "index" could also be given instead of "uniq" if one desires + a non unique index. + + Timestamps are added by default, so you don't have to specify them by hand + as 'created_at:datetime updated_at:datetime'. You don't have to think up every attribute up front, but it helps to sketch out a few so you can start working with the resource immediately. - For example, 'scaffold post title:string body:text published:boolean' - gives you a model with those three attributes, a controller that handles + For example, 'scaffold post title body:text published:boolean' gives + you a model with those three attributes, a controller that handles the create/show/update/destroy, forms to create and edit your posts, and - an index that lists them all, as well as a resources :posts - declaration in config/routes.rb. + an index that lists them all, as well as a resources :posts declaration + in config/routes.rb. If you want to remove all the generated files, run 'rails destroy scaffold ModelName'. Examples: `rails generate scaffold post` - `rails generate scaffold post title:string body:text published:boolean` - `rails generate scaffold purchase order_id:integer amount:decimal` + `rails generate scaffold post title body:text published:boolean` + `rails generate scaffold purchase amount:decimal tracking_id:integer:uniq` diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb index 7319fb79f6..d81c4c3e1d 100644 --- a/railties/lib/rails/generators/test_case.rb +++ b/railties/lib/rails/generators/test_case.rb @@ -218,8 +218,8 @@ module Rails # # create_generated_attribute(:string, 'name') # - def create_generated_attribute(attribute_type, name = 'test') - Rails::Generators::GeneratedAttribute.new(name, attribute_type.to_s) + def create_generated_attribute(attribute_type, name = 'test', index = nil) + Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':')) end protected diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index a85829085c..6e3fc84781 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -69,7 +69,7 @@ class GeneratedAttributeTest < Rails::Generators::TestCase end def test_default_value_for_type - att = Rails::Generators::GeneratedAttribute.new("type", "string") + att = Rails::Generators::GeneratedAttribute.parse("type:string") assert_equal("", att.default) end @@ -122,4 +122,9 @@ class GeneratedAttributeTest < Rails::Generators::TestCase assert_equal :string, create_generated_attribute(nil, 'title').type assert_equal :string, create_generated_attribute("", 'title').type end + + def test_handles_index_names_for_references + assert_equal "post", create_generated_attribute('string', 'post').index_name + assert_equal "post_id", create_generated_attribute('references', 'post').index_name + end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 337257df7d..9da1b53c96 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -58,6 +58,68 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_add_migration_with_attributes_and_indices + migration = "add_title_with_index_and_body_to_posts" + run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_column :posts, :title, :string/, up) + assert_match(/add_column :posts, :body, :text/, up) + assert_match(/add_column :posts, :user_id, :integer/, up) + end + assert_match(/add_index :posts, :title/, content) + assert_match(/add_index :posts, :user_id, :unique => true/, content) + end + end + + def test_add_migration_with_attributes_and_wrong_index_declaration + migration = "add_title_and_content_to_books" + run_generator [migration, "title:string:inex", "content:text", "user_id:integer:unik"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_column :books, :title, :string/, up) + assert_match(/add_column :books, :content, :text/, up) + assert_match(/add_column :books, :user_id, :integer/, up) + end + assert_not_match(/add_index :books, :title/, content) + assert_not_match(/add_index :books, :user_id/, content) + end + end + + def test_add_migration_with_attributes_without_type_and_index + migration = "add_title_with_index_and_body_to_posts" + run_generator [migration, "title:index", "body:text", "user_uuid:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_column :posts, :title, :string/, up) + assert_match(/add_column :posts, :body, :text/, up) + assert_match(/add_column :posts, :user_uuid, :string/, up) + end + assert_match(/add_index :posts, :title/, content) + assert_match(/add_index :posts, :user_uuid, :unique => true/, content) + end + end + + def test_add_migration_with_attributes_index_declaration_and_attribute_options + migration = "add_title_and_content_to_books" + run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{3,2}:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_column :books, :title, :string, :limit=>40/, up) + assert_match(/add_column :books, :content, :string, :limit=>255/, up) + assert_match(/add_column :books, :price, :decimal, :precision=>5, :scale=>2/, up) + assert_match(/add_column :books, :discount, :decimal, :precision=>3, :scale=>2/, up) + end + assert_match(/add_index :books, :title/, content) + assert_match(/add_index :books, :price/, content) + assert_match(/add_index :books, :discount, :unique => true/, content) + end + end + def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove migration = "create_books" run_generator [migration, "title:string", "content:text"] diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 1b0cb425c6..c063d4d687 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -113,6 +113,74 @@ class ModelGeneratorTest < Rails::Generators::TestCase end end + def test_migration_with_attributes_and_with_index + run_generator ["product", "name:string:index", "supplier_id:integer:index", "user_id:integer:uniq", "order_id:uniq"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + assert_match(/t\.integer :user_id/, up) + assert_match(/t\.string :order_id/, up) + + assert_match(/add_index :products, :name/, up) + assert_match(/add_index :products, :supplier_id/, up) + assert_match(/add_index :products, :user_id, :unique => true/, up) + assert_match(/add_index :products, :order_id, :unique => true/, up) + end + end + end + + def test_migration_with_attributes_and_with_wrong_index_declaration + run_generator ["product", "name:string", "supplier_id:integer:inex", "user_id:integer:unqu"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + assert_match(/t\.integer :user_id/, up) + + assert_not_match(/add_index :products, :name/, up) + assert_not_match(/add_index :products, :supplier_id/, up) + assert_not_match(/add_index :products, :user_id/, up) + end + end + end + + def test_migration_with_missing_attribute_type_and_with_index + run_generator ["product", "name:index", "supplier_id:integer:index", "year:integer"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + + assert_match(/add_index :products, :name/, up) + assert_match(/add_index :products, :supplier_id/, up) + assert_not_match(/add_index :products, :year/, up) + end + end + end + + def test_add_migration_with_attributes_index_declaration_and_attribute_options + run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq"] + + assert_migration "db/migrate/create_products.rb" do |content| + assert_method :change, content do |up| + assert_match(/create_table :products/, up) + assert_match(/t.string :title, :limit=>40/, up) + assert_match(/t.string :content, :limit=>255/, up) + assert_match(/t.decimal :price, :precision=>5, :scale=>2/, up) + end + assert_match(/add_index :products, :title/, content) + assert_match(/add_index :products, :price/, content) + assert_match(/add_index :products, :discount, :unique => true/, content) + end + end + def test_migration_without_timestamps ActiveRecord::Base.timestamped_migrations = false run_generator ["account"] |