actionview 4.2.11 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (68) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +304 -184
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/action_view.rb +1 -1
  6. data/lib/action_view/base.rb +14 -2
  7. data/lib/action_view/dependency_tracker.rb +51 -18
  8. data/lib/action_view/digestor.rb +83 -81
  9. data/lib/action_view/flows.rb +4 -5
  10. data/lib/action_view/gem_version.rb +3 -3
  11. data/lib/action_view/helpers/asset_tag_helper.rb +15 -5
  12. data/lib/action_view/helpers/asset_url_helper.rb +51 -12
  13. data/lib/action_view/helpers/atom_feed_helper.rb +6 -5
  14. data/lib/action_view/helpers/cache_helper.rb +62 -21
  15. data/lib/action_view/helpers/capture_helper.rb +5 -4
  16. data/lib/action_view/helpers/controller_helper.rb +11 -2
  17. data/lib/action_view/helpers/date_helper.rb +59 -13
  18. data/lib/action_view/helpers/debug_helper.rb +1 -1
  19. data/lib/action_view/helpers/form_helper.rb +74 -72
  20. data/lib/action_view/helpers/form_options_helper.rb +79 -39
  21. data/lib/action_view/helpers/form_tag_helper.rb +74 -44
  22. data/lib/action_view/helpers/javascript_helper.rb +4 -4
  23. data/lib/action_view/helpers/number_helper.rb +28 -13
  24. data/lib/action_view/helpers/output_safety_helper.rb +32 -2
  25. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  26. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  27. data/lib/action_view/helpers/sanitize_helper.rb +1 -2
  28. data/lib/action_view/helpers/tag_helper.rb +19 -11
  29. data/lib/action_view/helpers/tags/base.rb +45 -29
  30. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -28
  31. data/lib/action_view/helpers/tags/collection_helpers.rb +32 -0
  32. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -9
  33. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  34. data/lib/action_view/helpers/tags/label.rb +1 -1
  35. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  36. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  37. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  38. data/lib/action_view/helpers/tags/translator.rb +1 -1
  39. data/lib/action_view/helpers/text_helper.rb +27 -11
  40. data/lib/action_view/helpers/translation_helper.rb +56 -26
  41. data/lib/action_view/helpers/url_helper.rb +108 -79
  42. data/lib/action_view/layouts.rb +11 -10
  43. data/lib/action_view/log_subscriber.rb +35 -1
  44. data/lib/action_view/lookup_context.rb +69 -48
  45. data/lib/action_view/model_naming.rb +1 -1
  46. data/lib/action_view/path_set.rb +9 -0
  47. data/lib/action_view/railtie.rb +18 -3
  48. data/lib/action_view/record_identifier.rb +45 -19
  49. data/lib/action_view/renderer/abstract_renderer.rb +7 -3
  50. data/lib/action_view/renderer/partial_renderer.rb +38 -37
  51. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +49 -0
  52. data/lib/action_view/renderer/renderer.rb +2 -6
  53. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  54. data/lib/action_view/renderer/template_renderer.rb +11 -10
  55. data/lib/action_view/rendering.rb +15 -7
  56. data/lib/action_view/routing_url_for.rb +18 -6
  57. data/lib/action_view/tasks/{dependencies.rake → cache_digests.rake} +2 -2
  58. data/lib/action_view/template.rb +36 -12
  59. data/lib/action_view/template/error.rb +20 -9
  60. data/lib/action_view/template/handlers.rb +6 -4
  61. data/lib/action_view/template/handlers/html.rb +9 -0
  62. data/lib/action_view/template/handlers/raw.rb +1 -3
  63. data/lib/action_view/template/resolver.rb +49 -42
  64. data/lib/action_view/template/types.rb +14 -16
  65. data/lib/action_view/test_case.rb +15 -9
  66. data/lib/action_view/testing/resolvers.rb +1 -2
  67. data/lib/action_view/view_paths.rb +6 -24
  68. metadata +16 -20
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2014 David Heinemeier Hansson
1
+ Copyright (c) 2004-2016 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -9,11 +9,11 @@ used to inline short Ruby snippets inside HTML), and XML Builder.
9
9
 
10
10
  The latest version of Action View can be installed with RubyGems:
11
11
 
12
- % [sudo] gem install actionview
12
+ $ gem install actionview
13
13
 
14
14
  Source code can be downloaded as part of the Rails project on GitHub
15
15
 
16
- * https://github.com/rails/rails/tree/4-2-stable/actionview
16
+ * https://github.com/rails/rails/tree/5-0-stable/actionview
17
17
 
18
18
 
19
19
  == License
@@ -36,4 +36,3 @@ Bug reports can be filed for the Ruby on Rails project here:
36
36
  Feature requests should be discussed on the rails-core mailing list here:
37
37
 
38
38
  * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
39
-
data/lib/action_view.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004-2014 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2016 David Heinemeier Hansson
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -70,6 +70,14 @@ module ActionView #:nodoc:
70
70
  # Headline: <%= headline %>
71
71
  # First name: <%= person.first_name %>
72
72
  #
73
+ # The local variables passed to sub templates can be accessed as a hash using the <tt>local_assigns</tt> hash. This lets you access the
74
+ # variables as:
75
+ #
76
+ # Headline: <%= local_assigns[:headline] %>
77
+ #
78
+ # This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use
79
+ # <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
80
+ #
73
81
  # === Template caching
74
82
  #
75
83
  # By default, Rails will compile each template to a method in order to render it. When you alter a template,
@@ -126,8 +134,8 @@ module ActionView #:nodoc:
126
134
  # end
127
135
  # end
128
136
  #
129
- # For more information on Builder please consult the [source
130
- # code](https://github.com/jimweirich/builder).
137
+ # For more information on Builder please consult the {source
138
+ # code}[https://github.com/jimweirich/builder].
131
139
  class Base
132
140
  include Helpers, ::ERB::Util, Context
133
141
 
@@ -153,6 +161,10 @@ module ActionView #:nodoc:
153
161
  cattr_accessor :raise_on_missing_translations
154
162
  @@raise_on_missing_translations = false
155
163
 
164
+ # Specify whether submit_tag should automatically disable on click
165
+ cattr_accessor :automatically_disable_submit_tag
166
+ @@automatically_disable_submit_tag = true
167
+
156
168
  class_attribute :_routes
157
169
  class_attribute :logger
158
170
 
@@ -1,22 +1,26 @@
1
- require 'thread_safe'
1
+ require 'concurrent/map'
2
+ require 'action_view/path_set'
2
3
 
3
4
  module ActionView
4
5
  class DependencyTracker # :nodoc:
5
- @trackers = ThreadSafe::Cache.new
6
+ @trackers = Concurrent::Map.new
6
7
 
7
- def self.find_dependencies(name, template)
8
+ def self.find_dependencies(name, template, view_paths = nil)
8
9
  tracker = @trackers[template.handler]
10
+ return [] unless tracker
9
11
 
10
- if tracker.present?
11
- tracker.call(name, template)
12
- else
13
- []
14
- end
12
+ tracker.call(name, template, view_paths)
15
13
  end
16
14
 
17
15
  def self.register_tracker(extension, tracker)
18
16
  handler = Template.handler_for_extension(extension)
19
- @trackers[handler] = tracker
17
+ if tracker.respond_to?(:supports_view_paths?)
18
+ @trackers[handler] = tracker
19
+ else
20
+ @trackers[handler] = lambda { |name, template, _|
21
+ tracker.call(name, template)
22
+ }
23
+ end
20
24
  end
21
25
 
22
26
  def self.remove_tracker(handler)
@@ -76,12 +80,22 @@ module ActionView
76
80
  (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
77
81
  /xm
78
82
 
79
- def self.call(name, template)
80
- new(name, template).dependencies
83
+ LAYOUT_DEPENDENCY = /\A
84
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
85
+ (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
86
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
87
+ /xm
88
+
89
+ def self.supports_view_paths? # :nodoc:
90
+ true
91
+ end
92
+
93
+ def self.call(name, template, view_paths = nil)
94
+ new(name, template, view_paths).dependencies
81
95
  end
82
96
 
83
- def initialize(name, template)
84
- @name, @template = name, template
97
+ def initialize(name, template, view_paths = nil)
98
+ @name, @template, @view_paths = name, template, view_paths
85
99
  end
86
100
 
87
101
  def dependencies
@@ -106,15 +120,20 @@ module ActionView
106
120
  render_calls = source.split(/\brender\b/).drop(1)
107
121
 
108
122
  render_calls.each do |arguments|
109
- arguments.scan(RENDER_ARGUMENTS) do
110
- add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
111
- add_static_dependency(render_dependencies, Regexp.last_match[:static])
112
- end
123
+ add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
124
+ add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
113
125
  end
114
126
 
115
127
  render_dependencies.uniq
116
128
  end
117
129
 
130
+ def add_dependencies(render_dependencies, arguments, pattern)
131
+ arguments.scan(pattern) do
132
+ add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
133
+ add_static_dependency(render_dependencies, Regexp.last_match[:static])
134
+ end
135
+ end
136
+
118
137
  def add_dynamic_dependency(dependencies, dependency)
119
138
  if dependency
120
139
  dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
@@ -131,8 +150,22 @@ module ActionView
131
150
  end
132
151
  end
133
152
 
153
+ def resolve_directories(wildcard_dependencies)
154
+ return [] unless @view_paths
155
+
156
+ wildcard_dependencies.flat_map { |query, templates|
157
+ @view_paths.find_all_with_query(query).map do |template|
158
+ "#{File.dirname(query)}/#{File.basename(template).split('.').first}"
159
+ end
160
+ }.sort
161
+ end
162
+
134
163
  def explicit_dependencies
135
- source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
164
+ dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
165
+
166
+ wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == '*' }
167
+
168
+ (explicits + resolve_directories(wildcards)).uniq
136
169
  end
137
170
  end
138
171
 
@@ -1,123 +1,125 @@
1
- require 'thread_safe'
1
+ require 'concurrent/map'
2
2
  require 'action_view/dependency_tracker'
3
3
  require 'monitor'
4
4
 
5
5
  module ActionView
6
6
  class Digestor
7
- cattr_reader(:cache)
8
- @@cache = ThreadSafe::Cache.new
9
- @@digest_monitor = Monitor.new
7
+ @@digest_mutex = Mutex.new
8
+
9
+ module PerExecutionDigestCacheExpiry
10
+ def self.before(target)
11
+ ActionView::LookupContext::DetailsKey.clear
12
+ end
13
+ end
10
14
 
11
15
  class << self
12
16
  # Supported options:
13
17
  #
14
18
  # * <tt>name</tt> - Template name
15
- # * <tt>finder</tt> - An instance of ActionView::LookupContext
19
+ # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
16
20
  # * <tt>dependencies</tt> - An array of dependent views
17
21
  # * <tt>partial</tt> - Specifies whether the template is a partial
18
- def digest(options)
19
- options.assert_valid_keys(:name, :finder, :dependencies, :partial)
20
-
21
- cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.')
22
+ def digest(name:, finder:, dependencies: [])
23
+ dependencies ||= []
24
+ cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join('.')
22
25
 
23
26
  # this is a correctly done double-checked locking idiom
24
- # (ThreadSafe::Cache's lookups have volatile semantics)
25
- @@cache[cache_key] || @@digest_monitor.synchronize do
26
- @@cache.fetch(cache_key) do # re-check under lock
27
- compute_and_store_digest(cache_key, options)
27
+ # (Concurrent::Map's lookups have volatile semantics)
28
+ finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
29
+ finder.digest_cache.fetch(cache_key) do # re-check under lock
30
+ partial = name.include?("/_")
31
+ root = tree(name, finder, partial)
32
+ dependencies.each do |injected_dep|
33
+ root.children << Injected.new(injected_dep, nil, nil)
34
+ end
35
+ finder.digest_cache[cache_key] = root.digest(finder)
28
36
  end
29
37
  end
30
38
  end
31
39
 
32
- private
33
- def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock
34
- klass = if options[:partial] || options[:name].include?("/_")
35
- # Prevent re-entry or else recursive templates will blow the stack.
36
- # There is no need to worry about other threads seeing the +false+ value,
37
- # as they will then have to wait for this thread to let go of the @@digest_monitor lock.
38
- pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
39
- PartialDigestor
40
- else
41
- Digestor
42
- end
43
-
44
- digest = klass.new(options).digest
45
- # Store the actual digest if config.cache_template_loading is true
46
- @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching?
47
- digest
48
- ensure
49
- # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
50
- @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
51
- end
52
- end
40
+ def logger
41
+ ActionView::Base.logger || NullLogger
42
+ end
53
43
 
54
- attr_reader :name, :finder, :options
44
+ # Create a dependency tree for template named +name+.
45
+ def tree(name, finder, partial = false, seen = {})
46
+ logical_name = name.gsub(%r|/_|, "/")
55
47
 
56
- def initialize(options)
57
- @name, @finder = options.values_at(:name, :finder)
58
- @options = options.except(:name, :finder)
59
- end
48
+ options = {}
49
+ options[:formats] = [finder.rendered_format] if finder.rendered_format
60
50
 
61
- def digest
62
- Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
63
- logger.try :debug, " Cache digest for #{template.inspect}: #{digest}"
64
- end
65
- rescue ActionView::MissingTemplate
66
- logger.try :error, " Couldn't find template for digesting: #{name}"
67
- ''
68
- end
51
+ if template = finder.disable_cache { finder.find_all(logical_name, [], partial, [], options).first }
52
+ finder.rendered_format ||= template.formats.first
69
53
 
70
- def dependencies
71
- DependencyTracker.find_dependencies(name, template)
72
- rescue ActionView::MissingTemplate
73
- logger.try :error, " '#{name}' file doesn't exist, so no dependencies"
74
- []
75
- end
54
+ if node = seen[template.identifier] # handle cycles in the tree
55
+ node
56
+ else
57
+ node = seen[template.identifier] = Node.create(name, logical_name, template, partial)
76
58
 
77
- def nested_dependencies
78
- dependencies.collect do |dependency|
79
- dependencies = PartialDigestor.new(name: dependency, finder: finder).nested_dependencies
80
- dependencies.any? ? { dependency => dependencies } : dependency
59
+ deps = DependencyTracker.find_dependencies(name, template, finder.view_paths)
60
+ deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file|
61
+ node.children << tree(dep_file, finder, true, seen)
62
+ end
63
+ node
64
+ end
65
+ else
66
+ logger.error " '#{name}' file doesn't exist, so no dependencies"
67
+ logger.error " Couldn't find template for digesting: #{name}"
68
+ seen[name] ||= Missing.new(name, logical_name, nil)
69
+ end
81
70
  end
82
71
  end
83
72
 
84
- private
85
- def logger
86
- ActionView::Base.logger
73
+ class Node
74
+ attr_reader :name, :logical_name, :template, :children
75
+
76
+ def self.create(name, logical_name, template, partial)
77
+ klass = partial ? Partial : Node
78
+ klass.new(name, logical_name, template, [])
87
79
  end
88
80
 
89
- def logical_name
90
- name.gsub(%r|/_|, "/")
81
+ def initialize(name, logical_name, template, children = [])
82
+ @name = name
83
+ @logical_name = logical_name
84
+ @template = template
85
+ @children = children
91
86
  end
92
87
 
93
- def partial?
94
- false
88
+ def digest(finder, stack = [])
89
+ Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
95
90
  end
96
91
 
97
- def template
98
- @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) }
92
+ def dependency_digest(finder, stack)
93
+ children.map do |node|
94
+ if stack.include?(node)
95
+ false
96
+ else
97
+ finder.digest_cache[node.name] ||= begin
98
+ stack.push node
99
+ node.digest(finder, stack).tap { stack.pop }
100
+ end
101
+ end
102
+ end.join("-")
99
103
  end
100
104
 
101
- def source
102
- template.source
105
+ def to_dep_map
106
+ children.any? ? { name => children.map(&:to_dep_map) } : name
103
107
  end
108
+ end
104
109
 
105
- def dependency_digest
106
- template_digests = dependencies.collect do |template_name|
107
- Digestor.digest(name: template_name, finder: finder, partial: true)
108
- end
110
+ class Partial < Node; end
109
111
 
110
- (template_digests + injected_dependencies).join("-")
111
- end
112
+ class Missing < Node
113
+ def digest(finder, _ = []) '' end
114
+ end
112
115
 
113
- def injected_dependencies
114
- Array.wrap(options[:dependencies])
115
- end
116
- end
116
+ class Injected < Node
117
+ def digest(finder, _ = []) name end
118
+ end
117
119
 
118
- class PartialDigestor < Digestor # :nodoc:
119
- def partial?
120
- true
120
+ class NullLogger
121
+ def self.debug(_); end
122
+ def self.error(_); end
121
123
  end
122
124
  end
123
125
  end
@@ -37,9 +37,8 @@ module ActionView
37
37
  end
38
38
 
39
39
  # Try to get stored content. If the content
40
- # is not available and we are inside the layout
41
- # fiber, we set that we are waiting for the given
42
- # key and yield.
40
+ # is not available and we're inside the layout fiber,
41
+ # then it will begin waiting for the given key and yield.
43
42
  def get(key)
44
43
  return super if @content.key?(key)
45
44
 
@@ -60,8 +59,8 @@ module ActionView
60
59
  end
61
60
 
62
61
  # Appends the contents for the given key. This is called
63
- # by provides and resumes back to the fiber if it is
64
- # the key it is waiting for.
62
+ # by providing and resuming back to the fiber,
63
+ # if that's the key it's waiting for.
65
64
  def append!(key, value)
66
65
  super
67
66
  @fiber.resume if @waiting_for == key
@@ -5,9 +5,9 @@ module ActionView
5
5
  end
6
6
 
7
7
  module VERSION
8
- MAJOR = 4
9
- MINOR = 2
10
- TINY = 11
8
+ MAJOR = 5
9
+ MINOR = 0
10
+ TINY = 7
11
11
  PRE = nil
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -55,12 +55,12 @@ module ActionView
55
55
  # # => <script src="http://www.example.com/xmlhr.js"></script>
56
56
  def javascript_include_tag(*sources)
57
57
  options = sources.extract_options!.stringify_keys
58
- path_options = options.extract!('protocol', 'extname').symbolize_keys
58
+ path_options = options.extract!('protocol', 'extname', 'host').symbolize_keys
59
59
  sources.uniq.map { |source|
60
60
  tag_options = {
61
61
  "src" => path_to_javascript(source, path_options)
62
62
  }.merge!(options)
63
- content_tag(:script, "", tag_options)
63
+ content_tag("script".freeze, "", tag_options)
64
64
  }.join("\n").html_safe
65
65
  end
66
66
 
@@ -91,7 +91,7 @@ module ActionView
91
91
  # # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
92
92
  def stylesheet_link_tag(*sources)
93
93
  options = sources.extract_options!.stringify_keys
94
- path_options = options.extract!('protocol').symbolize_keys
94
+ path_options = options.extract!('protocol', 'host').symbolize_keys
95
95
 
96
96
  sources.uniq.map { |source|
97
97
  tag_options = {
@@ -136,7 +136,7 @@ module ActionView
136
136
  tag(
137
137
  "link",
138
138
  "rel" => tag_options[:rel] || "alternate",
139
- "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
139
+ "type" => tag_options[:type] || Template::Types[type].to_s,
140
140
  "title" => tag_options[:title] || type.to_s.upcase,
141
141
  "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
142
142
  )
@@ -205,8 +205,11 @@ module ActionView
205
205
  # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
206
206
  # image_tag("/icons/icon.gif", class: "menu_icon")
207
207
  # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
208
+ # image_tag("/icons/icon.gif", data: { title: 'Rails Application' })
209
+ # # => <img data-title="Rails Application" src="/icons/icon.gif" />
208
210
  def image_tag(source, options={})
209
211
  options = options.symbolize_keys
212
+ check_for_image_tag_errors(options)
210
213
 
211
214
  src = options[:src] = path_to_image(source)
212
215
 
@@ -236,7 +239,7 @@ module ActionView
236
239
  # image_alt('underscored_file_name.png')
237
240
  # # => Underscored file name
238
241
  def image_alt(src)
239
- File.basename(src, '.*').sub(/-[[:xdigit:]]{32,64}\z/, '').tr('-_', ' ').capitalize
242
+ File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32,64}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize
240
243
  end
241
244
 
242
245
  # Returns an HTML video tag for the +sources+. If +sources+ is a string,
@@ -318,12 +321,19 @@ module ActionView
318
321
  end
319
322
 
320
323
  def extract_dimensions(size)
324
+ size = size.to_s
321
325
  if size =~ %r{\A\d+x\d+\z}
322
326
  size.split('x')
323
327
  elsif size =~ %r{\A\d+\z}
324
328
  [size, size]
325
329
  end
326
330
  end
331
+
332
+ def check_for_image_tag_errors(options)
333
+ if options[:size] && (options[:height] || options[:width])
334
+ raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
335
+ end
336
+ end
327
337
  end
328
338
  end
329
339
  end