view 1.0.0.alpha.3 → 1.0.0.alpha.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/README.rdoc +269 -51
  2. data/lib/view/configuration.rb +55 -0
  3. data/lib/view/formatter.rb +8 -12
  4. data/lib/view/formatters/array.rb +1 -1
  5. data/lib/view/formatters/auto.rb +6 -2
  6. data/lib/view/formatters/definition_list.rb +110 -0
  7. data/lib/view/formatters/file_link.rb +1 -1
  8. data/lib/view/formatters/guess.rb +2 -2
  9. data/lib/view/formatters/human.rb +1 -0
  10. data/lib/view/formatters/image.rb +49 -8
  11. data/lib/view/formatters/link.rb +29 -4
  12. data/lib/view/formatters/percentage.rb +3 -1
  13. data/lib/view/formatters/phone.rb +1 -0
  14. data/lib/view/formatters/precision.rb +3 -1
  15. data/lib/view/formatters/size.rb +7 -0
  16. data/lib/view/formatters/table.rb +208 -0
  17. data/lib/view/helper.rb +27 -0
  18. data/lib/view/railtie.rb +1 -1
  19. data/lib/view/version.rb +1 -1
  20. data/lib/view.rb +28 -56
  21. data/spec/spec_helper.rb +52 -4
  22. data/spec/view/configuration_spec.rb +11 -0
  23. data/spec/view/formatters/currency_spec.rb +2 -6
  24. data/spec/view/formatters/definition_list_spec.rb +59 -0
  25. data/spec/view/formatters/delimited_spec.rb +2 -6
  26. data/spec/view/formatters/html_safe_spec.rb +13 -0
  27. data/spec/view/formatters/human_spec.rb +9 -0
  28. data/spec/view/formatters/image_spec.rb +36 -0
  29. data/spec/view/formatters/link_spec.rb +20 -0
  30. data/spec/view/formatters/percentage_spec.rb +15 -0
  31. data/spec/view/formatters/phone_spec.rb +15 -0
  32. data/spec/view/formatters/precision_spec.rb +15 -0
  33. data/spec/view/formatters/size_spec.rb +15 -0
  34. data/spec/view/formatters/table_spec.rb +131 -0
  35. data/spec/view/helper_spec.rb +26 -4
  36. metadata +29 -4
@@ -1,35 +1,76 @@
1
1
  module View
2
2
 
3
+ # Makes an image tag. It also works with paperclip objects.
4
+ #
5
+ # With paperclip, it also checks if the file was really uploaded.
6
+ #
7
+ # This is actually longer than the regular Rails way. But the added benefit
8
+ # of this formatter comes from using it inside the table and definition list
9
+ # formatter.
10
+ #
11
+ # @example Without paperclip
12
+ #
13
+ # = view "foo.jpg", :as => :image
14
+ #
15
+ # @example With paperclip
16
+ #
17
+ # = view @user.avatar, :as => :image
18
+ #
19
+ # @example With options
20
+ #
21
+ # = view @user.avatar, :as => :image, :html => { :class => "meh" }
22
+ #
23
+ # @example Inside the definition_list formatter:
24
+ #
25
+ # = definition_list_for @user do |dl|
26
+ # = dl.view :name
27
+ # = dl.view :avatar, :as => :image
28
+ #
29
+ #
3
30
  class Image < Formatter
4
31
 
5
- self.reserved_options = [ :with ]
32
+ self.default_options = { :html => {} }
6
33
 
7
34
  def format
8
35
  template.image_tag(path, options) if file?
9
36
  end
10
37
 
38
+ # Returns the path of the image, based on the path_methods and
39
+ # path_arguments configuration.
11
40
  def path
12
- value.send(path_method, *path_arguments)
41
+ if value.is_a?(String)
42
+ value
43
+ else
44
+ value.send(path_method, *path_arguments)
45
+ end
46
+ end
47
+
48
+ # The options for the image tag are in the :html option.
49
+ # You can use the default options to change them.
50
+ #
51
+ # @example
52
+ #
53
+ # View::Image.default_options = { :html => { :class => "avatar" } }
54
+ def options
55
+ super[:html]
13
56
  end
14
57
 
15
58
  # TODO I'm only guessing here, I don't actually know how other upload gems
16
59
  # work, besides paperclip.
17
60
  def path_method
18
- View.path_methods.find { |method| value.respond_to?(method) }
61
+ View.configuration.path_methods.find { |method| value.respond_to?(method) }
19
62
  end
20
63
 
21
- # TODO with seems like a stupid name, but style is probably used for
22
- # image_tag
23
64
  def path_arguments
24
- all_options[:with] || View.path_arguments
65
+ all_options[:style] || View.configuration.path_arguments
25
66
  end
26
67
 
27
68
  def file?
28
- value.send(file_method)
69
+ value.is_a?(String) || value.send(file_method)
29
70
  end
30
71
 
31
72
  def file_method
32
- View.file_methods.find { |method| value.respond_to?(method) }
73
+ View.configuration.file_methods.find { |method| value.respond_to?(method) }
33
74
  end
34
75
 
35
76
  end
@@ -1,11 +1,36 @@
1
1
  module View
2
2
 
3
+ # Makes a link of it.
4
+ #
5
+ # The text will be determined by a new formatter.
6
+ #
7
+ # You can specify a url with the `:to` option.
8
+ # You can specify part of the polymorphic_path with `:path`.
9
+ #
10
+ # @example
11
+ #
12
+ # = view @post, :as => :link
13
+ # -# equal to
14
+ # = link_to @post.title, @post
15
+ #
16
+ # @example
17
+ #
18
+ # = view @post, :as => :link, :path => :edit
19
+ #
20
+ # @example
21
+ #
22
+ # = view @post, :as => :link, :to => "http://example.com"
23
+ #
24
+ # @example
25
+ #
26
+ # = view @post, :as => :link, :text => "go to the post"
27
+ #
3
28
  class Link < Formatter
4
29
 
5
30
  self.reserved_options = [ :to, :path, :text ]
6
31
 
7
32
  def format
8
- template.link_to(format, to, options)
33
+ template.link_to(text, to, options)
9
34
  end
10
35
 
11
36
  def to
@@ -13,11 +38,11 @@ module View
13
38
  end
14
39
 
15
40
  def automatic_link
16
- (all_options[:path] || []) + [ value ]
41
+ ::Array.wrap(all_options[:path]) + [ value ]
17
42
  end
18
43
 
19
- def as
20
- all_options[:text] || :auto
44
+ def text
45
+ all_options[:text] || View.format(value, {:as => :auto}, template)
21
46
  end
22
47
 
23
48
  end
@@ -1,8 +1,10 @@
1
1
  module View
2
2
 
3
+ # Uses the number_to_percentage helper
3
4
  class Percentage < Formatter
4
5
 
5
- self.allowed_options = [ :precision, :separator, :delimiter ]
6
+ self.allowed_options = [ :precision, :separator, :delimiter, :locale,
7
+ :signigicant, :strip_insignificant_zeros ]
6
8
 
7
9
  def format
8
10
  template.number_to_percentage(value, options)
@@ -1,5 +1,6 @@
1
1
  module View
2
2
 
3
+ # Formats with the number_to_phone helper
3
4
  class Phone < Formatter
4
5
 
5
6
  self.allowed_options = [ :area_code, :delimiter, :extension, :country_code ]
@@ -1,8 +1,10 @@
1
1
  module View
2
2
 
3
+ # Format it with the number_with_precision helper
3
4
  class Precision < Formatter
4
5
 
5
- self.allowed_options = [ :precision, :separator, :delimiter ]
6
+ self.allowed_options = [ :precision, :separator, :delimiter,
7
+ :locale, :significant, :strip_insignificant_zeros ]
6
8
 
7
9
  def format
8
10
  template.number_with_precision(value, options)
@@ -1,5 +1,12 @@
1
1
  module View
2
2
 
3
+ # Formats with number_to_human_size
4
+ #
5
+ # @example
6
+ #
7
+ # = view 1999, :as => :size # => "1.95 KB"
8
+ #
9
+ # @see http://rubydoc.info/docs/rails/3.0.0/ActionView/Helpers/NumberHelper:number_to_human_size
3
10
  class Size < Formatter
4
11
 
5
12
  self.allowed_options = [ :precision, :separator, :delimiter ]
@@ -0,0 +1,208 @@
1
+ module View
2
+
3
+ # Renders a table.
4
+ #
5
+ # It uses a partial, which you can find in the gem under
6
+ # app/views/shared/_table.html.erb (and haml).
7
+ #
8
+ # To change the table layout, you can copy the partial to your own
9
+ # application.
10
+ #
11
+ # You can use a block to format attributes, or you can specify them with the
12
+ # fields option and trust them to be properly formatted automatically.
13
+ #
14
+ # If you don't provide a block or fields, it will try to detect your fields
15
+ # automatically.
16
+ #
17
+ # Use the partial option to render a different table partial.
18
+ #
19
+ # When you use :as => :link, it will link to the record, or you specify the
20
+ # path option. Please see the documentation of View::Link for more information.
21
+ #
22
+ # You will need to specify the class option to get a proper table headers
23
+ # when the collection is empty.
24
+ #
25
+ # @example Without a block:
26
+ #
27
+ # = view @posts, :as => :table, :fields => [ :title, :author ]
28
+ #
29
+ # @example With a block:
30
+ #
31
+ # = view @posts, :as => :table do |tb|
32
+ #
33
+ # = tb.view :title, :path => :edit
34
+ #
35
+ # = tb.view :author do |formatter|
36
+ # = link_to formatter.to_s, user_path(formatter.value)
37
+ #
38
+ # = tb.view :published_at
39
+ #
40
+ # @example With a different partial:
41
+ #
42
+ # = view @posts, :as => :table, :partial => "shared/fancy_table"
43
+ #
44
+ # @example Change the partial globally:
45
+ #
46
+ # View::Table.partial = "shared/fancy_table"
47
+ #
48
+ # @example Linking a column, without passing a block:
49
+ #
50
+ # = view @posts, :as => :table,
51
+ # :link => :title, :link_options => { :path => :edit }
52
+ #
53
+ class Table < Formatter
54
+
55
+ class_inheritable_accessor :partial
56
+ self.partial = 'shared/table'
57
+
58
+ # This will add the th and td tags.
59
+ #
60
+ # @param [Symbol] attribute The name of the attribute to be called on +value+
61
+ # @param [Hash] options These will be delegated to the formatting of the
62
+ # attribute's value.
63
+ # @yield [formatter] The block will be used for formatting the attribute's
64
+ # value
65
+ def view(attribute, options = {}, &block)
66
+ columns << Column.new(attribute, options, block, self)
67
+ nil
68
+ end
69
+
70
+ # The columns defined
71
+ # @return [Array]
72
+ def columns
73
+ @columns ||= []
74
+ end
75
+
76
+ # Iterates over the collection and yields rows.
77
+ # @yield [Row]
78
+ def each
79
+ value.each_with_index do |resource, index|
80
+ yield Row.new(resource, self, index)
81
+ end
82
+ end
83
+
84
+ def resource_class
85
+ all_options[:class] || value.first.class
86
+ end
87
+
88
+ protected
89
+
90
+ # FIXME duplication with definition_list formatter
91
+ def format
92
+ unless block
93
+ fields.inject("".html_safe) do |html, field|
94
+ html << view(field)
95
+ end
96
+ end
97
+ end
98
+
99
+ def fields
100
+ all_options[:fields] || all_fields
101
+ end
102
+
103
+ def partial
104
+ all_options[:partial] || self.class.partial
105
+ end
106
+
107
+ private
108
+
109
+ def all_fields
110
+ resource_class.respond_to?(:column_names) ? resource_class.column_names : []
111
+ end
112
+
113
+ def format!
114
+ super
115
+ template.render(partial, :table => self)
116
+ end
117
+
118
+ class Column < Struct.new(:attribute, :options, :block, :table)
119
+
120
+ delegate :resource_class, :to => :table
121
+
122
+ # Returns the name of the column.
123
+ # If the record extends ActiveModel::Translations, it will use
124
+ # human_attribute_name, which gets the translations from I18n. Otherwise
125
+ # it will just titleize the name of the attribute.
126
+ def name
127
+ if resource_class.respond_to?(:human_attribute_name)
128
+ resource_class.human_attribute_name(attribute)
129
+ else
130
+ attribute.to_s.titleize
131
+ end
132
+ end
133
+
134
+ # Html options to add to your td and th tags.
135
+ # @return [Hash]
136
+ def html_options
137
+ { :class => attribute }
138
+ end
139
+
140
+ end
141
+
142
+ class Row < Struct.new(:resource, :table, :index)
143
+
144
+ # Loops through all columns and yields cells.
145
+ #
146
+ # @yield [Cell]
147
+ def each
148
+ table.columns.each do |column|
149
+ yield Cell.new(self, column)
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ class Cell < Struct.new(:row, :column)
156
+
157
+ delegate :resource, :table, :to => :row
158
+ delegate :template, :to => :table
159
+ delegate :attribute, :block, :options, :html_options, :to => :column
160
+
161
+ # Returns the formatted value for the cell
162
+ def value
163
+ View.format(value_or_resource, options, template, &block)
164
+ end
165
+
166
+ private
167
+
168
+ def value_or_resource
169
+ link? ? resource : original_value
170
+ end
171
+
172
+ def original_value
173
+ resource.send(attribute)
174
+ end
175
+
176
+ def all_options
177
+ column.options
178
+ end
179
+
180
+ def options
181
+ link_options.merge(all_options)
182
+ end
183
+
184
+ def link?
185
+ has_link_options? || global_link_column?
186
+ end
187
+
188
+ def global_link_column?
189
+ table.all_options[:link].to_s == attribute.to_s
190
+ end
191
+
192
+ def has_link_options?
193
+ all_options[:as] == :link || (!all_options[:as] && all_options[:path])
194
+ end
195
+
196
+ def link_options
197
+ link? ? table_link_options.merge(:text => original_value, :as => :link) : {}
198
+ end
199
+
200
+ def table_link_options
201
+ table.all_options[:link_options] || {}
202
+ end
203
+
204
+ end
205
+
206
+ end
207
+
208
+ end
data/lib/view/helper.rb CHANGED
@@ -2,10 +2,37 @@ module View
2
2
 
3
3
  module Helper
4
4
 
5
+ # Use the formatter straight from the view
5
6
  def view(value, options = {}, &block)
6
7
  ::View.format(value, options, self, &block)
7
8
  end
8
9
 
10
+ # Shortcut for rendering tables
11
+ def table_for(value, options = {}, &block)
12
+ view(value, options.merge(:as => :table), &block)
13
+ end
14
+
15
+ # If you're using something like InheritedResource, this will automatically
16
+ # set the table for the current collection, so you'll only need to do:
17
+ #
18
+ # = table
19
+ def table(options = {}, &block)
20
+ table_for(collection, options.merge(:class => resource_class), &block)
21
+ end
22
+
23
+ # Shortcut for definition_lists
24
+ def definition_list_for(value, options = {}, &block)
25
+ view(value, options.merge(:as => :definition_list), &block)
26
+ end
27
+
28
+ # If you're using something like InheritedResource, this will automatically
29
+ # set the definition_list for the current resource, so you'll only need to do:
30
+ #
31
+ # = definition_list
32
+ def definition_list(options = {}, &block)
33
+ definition_list_for(resource, options, &block)
34
+ end
35
+
9
36
  end
10
37
 
11
38
  end
data/lib/view/railtie.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module View
2
2
 
3
- class Railtie < ::Rails::Railtie
3
+ class Railtie < ::Rails::Engine
4
4
 
5
5
  ActiveSupport.on_load(:after_initialize) do
6
6
  ActiveSupport.on_load(:action_view) do
data/lib/view/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module View
2
- VERSION = '1.0.0.alpha.3'
2
+ VERSION = '1.0.0.alpha.4'
3
3
  end
data/lib/view.rb CHANGED
@@ -1,61 +1,9 @@
1
1
  module View
2
2
 
3
- autoload :Formatter, 'view/formatter'
4
- autoload :Helper, 'view/helper'
5
- autoload :VERSION, 'view/version'
6
-
7
- class << self
8
- # Used by the guess filter, these are the methods used to format an object.
9
- # It will use the first method that the object responds to.
10
- attr_accessor :guessing_methods
11
-
12
- # To determine if something is an uploaded image, it checks
13
- # to see whether it responds to one of these methods.
14
- attr_accessor :file_methods
15
-
16
- # To show an image, it will use one of these methods to get the src
17
- # attribute.
18
- attr_accessor :path_methods
19
-
20
- # You can set a default argument for rendering images, like the style or size
21
- # of the image. It will be passed to one of the path_methods.
22
- attr_accessor :path_arguments
23
-
24
- # Which formatter will be used by default. The default is auto, which does
25
- # all sorts of interesting stuff to see what to do.
26
- attr_accessor :default_formatter
27
-
28
- # The auto formatter will choose this formatter by default when your value is
29
- # an array (or really, something that responds to +each+).
30
- attr_accessor :default_list_formatter
31
-
32
- # Holds a list of formatters. It will be filled automatically by inheriting
33
- # from View::Formatter.
34
- attr_accessor :formatters
35
-
36
- # Shorthand for configuring this gem.
37
- #
38
- # @example In +config/initializers/view.rb+
39
- #
40
- # View.configure do |config|
41
- # config.default_formatter = :self
42
- # end
43
- #
44
- # @yield [config] The View module
45
- def configure
46
- yield self
47
- end
48
-
49
- end
50
-
51
- self.guessing_methods = %w|to_label display_name full_name name title
52
- username login value to_s|
53
- self.file_methods = %w|mounted_as file? public_filename|
54
- self.path_methods = %w|mounted_as url public_filename|
55
- self.path_arguments = []
56
- self.default_formatter = :auto
57
- self.default_list_formatter = :sentence
58
-
3
+ autoload :Formatter, 'view/formatter'
4
+ autoload :Helper, 'view/helper'
5
+ autoload :Configuration, 'view/configuration'
6
+ autoload :VERSION, 'view/version'
59
7
 
60
8
  # This is the main method to use this gem. Any formatting from outside this
61
9
  # gem should be done through this method (notwithstanding custom formatters).
@@ -91,6 +39,30 @@ module View
91
39
  Formatter.format(value, options, template, &block)
92
40
  end
93
41
 
42
+ # Access to the configuration object
43
+ # @return [Configuration]
44
+ def self.configuration
45
+ @configuration ||= Configuration.new
46
+ end
47
+
48
+ # Configures this gem
49
+ #
50
+ # @example config/initializers/view.rb
51
+ #
52
+ # View.configure do |config|
53
+ # config.default_formatter = :self
54
+ # end
55
+ #
56
+ # @yield [config] The configuration instance
57
+ def self.configure
58
+ yield configuration
59
+ end
60
+
61
+ # Contains a list of registered formatters
62
+ def self.formatters
63
+ @formatters ||= []
64
+ end
65
+
94
66
  end
95
67
 
96
68
  require 'view/railtie'
data/spec/spec_helper.rb CHANGED
@@ -1,22 +1,63 @@
1
1
  require "rubygems"
2
2
  require "bundler/setup"
3
3
  Bundler.setup :default
4
+
4
5
  require 'rails'
5
6
  require 'action_view'
7
+
8
+ # Needed to make the capture helper work in tests
9
+ require 'action_view/template/handlers/erb'
10
+
11
+ # We need them all
12
+ require 'active_support/core_ext'
13
+
6
14
  require 'rspec'
7
15
  require 'view'
8
16
 
9
17
  module WithTranslation
18
+
19
+ # Temporarily use a clean 'test'-locale
20
+ #
21
+ # with_translation :foo => 'bar' do
22
+ # I18n.t(:foo).should == 'bar'
23
+ # end
24
+ # I18n.t(:foo).should_not == 'bar'
10
25
  def with_translation(keys)
11
26
  locale = I18n.locale
12
- I18n.backend.store_translations(:test, keys)
13
27
  I18n.locale = :test
28
+ I18n.backend.reload!
29
+ I18n.backend.store_translations(:test, keys)
14
30
  yield
15
31
  ensure
32
+ I18n.backend.reload!
16
33
  I18n.locale = locale
17
34
  end
35
+
18
36
  end
19
37
 
38
+ # Make a fake Rails application, to test rendering partials
39
+ class RailsApplication
40
+
41
+ def root
42
+ File.expand_path('../../', __FILE__)
43
+ end
44
+
45
+ def config
46
+ Struct.new(:root, :cache_classes, :assets_dir).new(root, false, root)
47
+ end
48
+
49
+ # This runs before every spec
50
+ def before(context)
51
+ Rails.stub(:application).and_return(self)
52
+ ActionView::Template.register_default_template_handler :erb, ActionView::Template::Handlers::ERB
53
+ context.helper.view_paths = view_paths
54
+ end
55
+
56
+ def view_paths
57
+ [ File.join(root, 'app', 'views') ]
58
+ end
59
+
60
+ end
20
61
 
21
62
  module TemplateHelper
22
63
 
@@ -25,11 +66,13 @@ module TemplateHelper
25
66
  @helper ||= Template.new
26
67
  end
27
68
 
28
- class Template
29
-
30
- include ActionView::Helpers
69
+ # emulate a Rails view
70
+ class Template < ActionView::Base
31
71
  include View::Helper
32
72
 
73
+ def config
74
+ Struct.new(:asset_path, :asset_host).new('/', '/')
75
+ end
33
76
  end
34
77
 
35
78
  end
@@ -37,4 +80,9 @@ end
37
80
  RSpec.configure do |config|
38
81
  config.include(WithTranslation)
39
82
  config.include(TemplateHelper)
83
+ config.before do
84
+ RailsApplication.new.before(self)
85
+ end
86
+ # Uncomment to see the big backtrace
87
+ # config.backtrace_clean_patterns = []
40
88
  end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe View::Configuration do
4
+
5
+ it { should respond_to(:guessing_methods) }
6
+ it { should respond_to(:file_methods) }
7
+ it { should respond_to(:path_methods) }
8
+ it { should respond_to(:default_formatter) }
9
+ it { should respond_to(:default_list_formatter) }
10
+
11
+ end
@@ -3,15 +3,11 @@ require 'spec_helper'
3
3
 
4
4
  describe "Currency formatter" do
5
5
 
6
- before do
7
- helper.stub(:number_to_currency).and_return("called")
8
- end
9
-
10
6
  it "calls number_to_currency" do
11
- helper.view(19.99, :as => :currency).should == "called"
7
+ helper.view(19.99, :as => :currency).should == "$19.99"
12
8
  end
13
9
 
14
- it "allowes no other options" do
10
+ it "allows no other options" do
15
11
  helper.should_receive(:number_to_currency).with(19.99, :unit => "£")
16
12
  helper.view(19.99, :as => :currency, :unit => "£", :foo => "bar")
17
13
  end