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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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