Mange-field_helpers 1.0.1

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.
data/History.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = History
2
+
3
+ == Release 1.0.1
4
+ * Added default options for FieldHelpers::Base. You can now do some cool stuff with this.
5
+
6
+ == Release 1.0.0
7
+ * Initial release
data/README.rdoc ADDED
@@ -0,0 +1,240 @@
1
+ = FieldHelpers
2
+
3
+ == Informal introduction
4
+ Let's play a little mind game here:
5
+
6
+ You are to build a Rails application which is very data heavy. The client wants many views with much data displayed on each.
7
+ Many of these data fields are optional so they can be nil sometimes. You want the app to be translatable with <tt>I18n</tt>.
8
+
9
+ Here is the icing on the cake: Many of the fields require special formatting.
10
+
11
+ Sounds tiresome? Indeed it would be -- if you didn't have FieldHelpers installed!
12
+
13
+ <b>Tell me more</b>
14
+
15
+ Even if you don't have it as bad as in the imagined (I wish) scenario above, you could get a productivity boost from using
16
+ FieldHelpers. Many apps need to display values and many apps have optional values, so it makes sense to have helpers for doing this.
17
+
18
+ <b>Okay, so I define a little helper. Big deal?</b>
19
+
20
+ Well, it's not as simple as that all the time. Some features are hard to build right and even more features require a lot of code to
21
+ get to work.
22
+
23
+ You could solve the optional field problem by checking for nil:
24
+ def field(record, field)
25
+ (v = record.send(field)) ? v : ""
26
+ end
27
+
28
+ Okay, now add custom formatting, value conversion, field kind discovery, i18n capabilities and... a understanding of associations.
29
+ Yeah, not so tough anymore, are you?
30
+
31
+ <b>Wait, what? Conversion? Discovery? What is this?</b>
32
+
33
+ Didn't I tell you? FieldHelpers handles different kinds of fields differently. Look at this example:
34
+ field_value(@user, :name) # => "John Doe"
35
+ field_value(@user, :name, :format => "Mr. %s") # => "Mr. John Doe"
36
+ field_value(@user, :pay, :kind => :money) # => "$1,000.00"
37
+ field_value(@user, :active?) # => "Yes"
38
+ field_value(@user, :group) # => "<a href=\"/groups/14\">Administrators</a>"
39
+ field_value(@user, :created_at, :kind => :time) # => "13:45:02"
40
+ field_value(@user, :created_at, :kind => :date) # => "2007-02-14"
41
+ field_value(@user, :created_at) # => "2007-02-14 13:45:02 +0100"
42
+
43
+ <b>Holy crap!</b>
44
+
45
+ No, this is not crap. It can do a lot more! See the association example above? The group link? It contains a lot of logic.
46
+ You can customize the text, path, add classes, and so on. Use it to build your own helpers!
47
+
48
+ def user_field(record, field = :user)
49
+ field(record, field, :link_options => {:class => 'user'}, :link_field => :full_name)
50
+ end
51
+
52
+ user_field(@comment, :author)
53
+ # => "<div class=\"field\"><strong>Author</strong> <a href=\"/users/13\" class=\"user\">Edward von Edenburgh</a></div>"
54
+
55
+ It will try to find the text by looking at a few options you specify, and then by looking to see if the model responds to a few methods
56
+ like "name", "title" and so on. If everything fails, the record in question will just be converted to a string.
57
+
58
+ You can also do self-links. Say that you are doing a table of associated record: (This is a complex example!)
59
+
60
+ - @user.comments.each do |comment|
61
+ %tr
62
+ %td= field_value(comment, :title, :kind => :link, :link_path => post_comments_path(comment.post, comment))
63
+ %td= field_value(comment, :post)
64
+ %td= field_value(comment, :approved?)
65
+ %td= field_value(comment, :created_at)
66
+
67
+ The first cell will now contain a link to the comment in question with the text in the "title" attribute of the comment. The path will be
68
+ a nested resource path that had nothing to do with the current view. If there was a chance of comment being nil, we could even have wrapped
69
+ the link_path option in a lambda and we would not get any errors at all.
70
+
71
+ The second cell would contain a link to the post. field_value can figure out how to handle this association all by itself. Sexy! As you can
72
+ see, using the link functionality is very easy!
73
+
74
+ <b>Gimme! How do I install it?</b>
75
+
76
+ Install it like any other gem from GitHub:
77
+ $> gem source -a http://gems.github.com
78
+ $> sudo gem install Mange-field_helpers
79
+
80
+ Then add it as a gem in your <tt>environment.rb</tt>:
81
+ config.gem "Mange-field_helpers", :lib => 'field_helpers', :source => 'http://gems.github.com'
82
+
83
+ == Features
84
+ You get two helpers: <tt>field</tt> and <tt>field_value</tt>. Use them for displaying values in your application.
85
+ * <tt>field_value</tt>
86
+ * Doesn't fail if the value is nil
87
+ * Discovers what kind of field it is by looking at the field name and/or value
88
+ * Formats the field from rules depending on the kind
89
+ * Returns sane default values when value was nil or an empty string
90
+ * Returns <tt>Model.human_attribute_name('no_name')</tt> when 'name' field is empty, for example
91
+ * Returns empty string if option <tt>:no_blanks</tt> is set
92
+ * Can use an additional custom format for even more fine-grained formatting in certain situations
93
+ * Can be extended with even more kinds
94
+ * Discovery is easy to extend
95
+ * Formatting is very easy to improve
96
+ * Can in theory be used outside views (but why would you want to? Sounds like a bad idea to me)
97
+ * <tt>field</tt>
98
+ * Uses <tt>field_value</tt> to get value
99
+ * Uses <tt>human_attribute_name</tt> (I18n support!) of the model to get field name
100
+ * Wraps it all in easy-to-style elements for you to use
101
+
102
+ The methods have a full test suite and most of the methods are very, very short.
103
+
104
+ == Example
105
+ Let's take the example from earlier and show how to do it without the helpers. That is a good example!
106
+ Note that we use Haml since ERB disgusts me. Haml is not required.
107
+
108
+ <b>With FieldHelpers:</b>
109
+
110
+ = field @user, :name
111
+ = field @user, :name, :format => "Mr. %s"
112
+ = field @user, :pay, :kind => :money
113
+ = field @user, :active?
114
+ = field @user, :group
115
+ = field @user, :created_at, :kind => :time
116
+ = field @user, :created_at, :kind => :date
117
+ = field @user, :created_at
118
+
119
+ <b>Without FieldHelpers:</b>
120
+
121
+ .field
122
+ %strong= User.human_attribute_name('name')
123
+ - if @user.name
124
+ = @user.name
125
+ - else
126
+ = User.human_attribute_name('no_name')
127
+
128
+ .field
129
+ %strong= User.human_attribute_name('name')
130
+ - if @user.name
131
+ == Mr. #{@user.name}
132
+ - else
133
+ = User.human_attribute_name('no_name')
134
+
135
+ .field
136
+ %strong= User.human_attribute_name('pay')
137
+ - if @user.pay
138
+ = number_to_currency(@user.pay)
139
+ - else
140
+ = User.human_attribute_name('no_pay')
141
+
142
+ .field
143
+ %strong= User.human_attribute_name('active?')
144
+ - unless @user.active?.nil?
145
+ = @user.active? ? I18n.translate(:yes) : I18n.translate(:no)
146
+ - else
147
+ = User.human_attribute_name('no_active?')
148
+
149
+ .field
150
+ %strong= User.human_attribute_name('group')
151
+ - if @user.group
152
+ = link_to @user.group.name, @user.group
153
+ - else
154
+ = User.human_attribute_name('no_group')
155
+
156
+ .field
157
+ %strong= User.human_attribute_name('created_at')
158
+ - if @user.created_at
159
+ = @user.created_at.to_time.to_s(:time)
160
+ - else
161
+ = User.human_attribute_name('no_created_at')
162
+
163
+ .field
164
+ %strong= User.human_attribute_name('created_at')
165
+ - if @user.created_at
166
+ = @user.created_at.to_date.to_s
167
+ - else
168
+ = User.human_attribute_name('no_created_at')
169
+
170
+ .field
171
+ %strong= User.human_attribute_name('created_at')
172
+ - if @user.created_at
173
+ = @user.created_at
174
+ - else
175
+ = User.human_attribute_name('no_created_at')
176
+
177
+
178
+ Now, tell me which one you want to maintain.
179
+
180
+ == Registering your own kinds
181
+ As I have mentioned earlier, you can register your own kinds. Just call <tt>FieldHelpers::Base.register_kind</tt>
182
+ and you'll be on your way to world domination. Here are some silly examples of this:
183
+
184
+ === Adding the kinds themselves
185
+
186
+ # Method 1: Using procs
187
+ FieldHelpers::Base.register_kind(:title,
188
+ lambda { |base|
189
+ base.original_value.to_s.titleize
190
+ }
191
+ )
192
+
193
+ # Method 2: Using other methods
194
+
195
+ # In lib/field_extensions.rb
196
+ class FieldExtensions
197
+ def convert_password(base)
198
+ if base.options[:password_full]
199
+ base.original_value.gsub(/./, '*')
200
+ else
201
+ "************"
202
+ end
203
+ end
204
+ end
205
+
206
+ # Then, wherever you want to. In an initializer, perhaps?
207
+ FieldHelpers::Base.register_kind(:password, FieldExtensions.method(:convert_password))
208
+
209
+ After doing this, you can display them by specifying kind as either <tt>:password</tt> or <tt>:title</tt>. You can also
210
+ overwrite defaults kinds this way if you want.
211
+
212
+ === Adding auto-discovery of kinds
213
+ In a much similar way as adding the helper methods, you can add methods to discover kinds.
214
+
215
+ # Method 1: Using procs
216
+ FieldHelpers::Base.register_kind_discovery(:title,
217
+ lambda { |field, value|
218
+ true if field =~ /title$/
219
+ }
220
+ )
221
+
222
+ # Method 2: Using blocks
223
+ FieldHelpers::Base.register_kind_discovery(:title) do |field, value|
224
+ true if field =~ /title$/
225
+ end
226
+
227
+ # Method 3: Using other methods
228
+
229
+ # In lib/field_extensions.rb
230
+ class FieldExtensions
231
+ def discover_password(field, value)
232
+ true if field == :password
233
+ end
234
+ end
235
+
236
+ # Then, wherever you want to. In an initializer, perhaps?
237
+ FieldHelpers::Base.register_kind_discovery(:password, FieldExtensions.method(:discover_password))
238
+
239
+ You can turn of auto-discovery for certain kinds this way!
240
+ FieldHelpers::Base.register_kind_discovery(:email, lambda { false })
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 0
3
+ :patch: 1
4
+ :major: 1
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2008
2
+ # * Magnus Bergmark
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ # Main module for everything related to the field helpers. By requiring this file,
25
+ # +ActionView+ will be extended with the methods in <tt>FieldHelpers::Helper</tt>.
26
+ #
27
+ # Most probably, the methods in <tt>FieldHelpers::Helper</tt> are the methods you are
28
+ # interested in reading. Here's a small shortcut:
29
+ # * <tt>FieldHelpers::Helper.field</tt>
30
+ # * <tt>FieldHelpers::Helper.field_value</tt>
31
+ module FieldHelpers
32
+ end
33
+
34
+ require 'field_helpers/base'
35
+ require 'field_helpers/kinds'
36
+ require 'field_helpers/helper'
37
+
38
+ # Register the default kinds
39
+ FieldHelpers::Kinds.available_kinds_with_helpers.each_pair do |kind, method|
40
+ FieldHelpers::Base.register_kind(kind, method)
41
+ end
42
+
43
+ FieldHelpers::Kinds.available_kinds_with_discoverers.each_pair do |kind, method|
44
+ FieldHelpers::Base.register_kind_discovery(kind, method)
45
+ end
46
+
47
+ # Add the helpers to all views
48
+ ActionView::Base.class_eval do
49
+ include FieldHelpers::Helper
50
+ end
@@ -0,0 +1,179 @@
1
+ # Copyright (c) 2008
2
+ # * Magnus Bergmark
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ # Instances of this class will be passed around between all the helper methods to ease the
25
+ # options handling. This class takes values when being initialized and then parses and stores
26
+ # them in attributes. It has some virtual attributes that you should check out, too.
27
+ #
28
+ # Instances of this class is often referred to as <tt>base</tt>s.
29
+ class FieldHelpers::Base
30
+
31
+ # The registered helper methods will be saved in this hash. The key will be a kind symbol
32
+ # and the actual value will be a proc.
33
+ cattr_accessor :kind_helpers
34
+
35
+ # The registered kind discoverers will be saved in this hash. The key will be a kind symbol
36
+ # and the actual value will be a proc.
37
+ cattr_accessor :kind_discoverers
38
+
39
+ # Contains the default options that the explicit options will be merged with.
40
+ #
41
+ # Tip: Disable kind auto-discovery by setting <tt>:kind</tt> to <tt>:string</tt> here.
42
+ cattr_accessor :default_options
43
+ self.default_options = {}
44
+
45
+ class << self
46
+
47
+ # Register a kind for the helper methods. Specify the kind name as a symbol and then give
48
+ # a proc for the helper as the second parameter. It is recommended that you use the <tt>method</tt>
49
+ # method to get a method from a class as a proc.
50
+ #
51
+ # Example:
52
+ # FieldHelpers::Base.register_kind(:funky, FunkyHelpers.method(:render_funky))
53
+ # FieldHelpers::Base.register_kind(:small, lambda { |b| b.original_value.downcase })
54
+ #
55
+ # Please see docs for FieldHelpers::Kinds for information about how these methods are to be
56
+ # built and how to use them.
57
+ #
58
+ def register_kind(kind_name, helper_proc)
59
+ self.kind_helpers ||= {}
60
+ self.kind_helpers[kind_name.to_sym] = helper_proc
61
+ end
62
+
63
+ # Register a kind for the discover methods. Specify the kind name as a symbol and then give
64
+ # a proc for the discoverer as the second parameter (or provide a block). It is recommended
65
+ # that you use the <tt>method</tt> method to get a method from a class as a proc.
66
+ #
67
+ # Example:
68
+ # FieldHelpers::Base.register_kind_discovery(:funky, FunkyHelpers.method(:field_is_funky?))
69
+ # FieldHelpers::Base.register_kind_discovery(:title, lambda { |f,v| true if f =~ /title/ })
70
+ # FieldHelpers::Base.register_kind_discovery(:complex) do |field, value|
71
+ # # ...
72
+ # end
73
+ #
74
+ # Please see docs for FieldHelpers::Kinds for information about how these methods are to be
75
+ # built and how to use them.
76
+ #
77
+ def register_kind_discovery(kind_name, discovery_proc = nil, &block)
78
+ self.kind_discoverers ||= {}
79
+ raise ArgumentError, "You must specify a proc or block" if discovery_proc.nil? and not block_given?
80
+
81
+ self.kind_discoverers[kind_name.to_sym] = (block_given? ? block : discovery_proc)
82
+ end
83
+
84
+ # Calls the registered discoverers to find out the kind of a given field name and value. If no
85
+ # discoverer returns <tt>true</tt>, a kind of <tt>:string</tt> is assumed.
86
+ #
87
+ # Note: The order of the discoverers calls are not guranteed or known.
88
+ def discover_kind(field, value)
89
+ self.kind_discoverers.each_pair do |kind, method|
90
+ return kind if method.call(field.to_s, value) == true # Require an explicit TrueClass
91
+ end
92
+ return :string # Default to string
93
+ end
94
+ end
95
+
96
+ # This holds a reference to the template context in which the text is about to be rendered. If you
97
+ # want to use any methods present in views, just call them through this variable.
98
+ #
99
+ # Example:
100
+ # template.content_tag(:div, "Hello World")
101
+ attr_accessor :template
102
+
103
+ # This is a reference to the record (aka. model) being used.
104
+ attr_accessor :record
105
+
106
+ # This is a symbol representing the field given to the helpers.
107
+ attr_accessor :field
108
+
109
+ # This is a copy of the options hash that was passed to the helpers.
110
+ attr_accessor :options
111
+
112
+ # This is the original value before any convertions and similar. It could be nil.
113
+ # The value is worked out by asking the record for the value of the method specified in field.
114
+ def original_value
115
+ return @original_value if @original_value
116
+ @original_value = record.send(field) if record.respond_to? field
117
+ end
118
+
119
+ # A virtual attribute that wraps some logic around the handling of empty original values. If the
120
+ # original_value is nil or an empty string, default_value will be returned instead.
121
+ #
122
+ # If the original_value was not empty, the method will try to guess the kind and then convert it
123
+ # to that kind before returning it.
124
+ #
125
+ # This method will raise an ArgumentError if there is no helper for the specified/discovered kind.
126
+ def value
127
+ return @value if @value
128
+ @value = original_value
129
+ # false will be considered #blank? == true, which is not intended
130
+ if @value.nil? or @value == ""
131
+ @value = default_value
132
+ else
133
+ method = kind_helpers[kind]
134
+ raise ArgumentError, "Unknown kind: #{kind}" if method.nil?
135
+ @value = method.call(self)
136
+ end
137
+ @value
138
+ end
139
+
140
+ # Returns the default value, aka. what to display if the field is nil or empty. If no <tt>:default</tt>
141
+ # option has been specified, and blank values are to be displayed, this will be the human attribute
142
+ # name <tt>no_<field></tt> where <tt><field></tt> is the field name.
143
+ #
144
+ # If <tt>:no_blanks</tt> is specified, this method will always return an empty string.
145
+ def default_value
146
+ if options[:default]
147
+ options[:default]
148
+ elsif options[:no_blanks]
149
+ ""
150
+ else
151
+ record.class.human_attribute_name("no_#{field}", :default => [:blank_field_value, ""])
152
+ end
153
+ end
154
+
155
+ # Returns the specified kind, or tries to discover the kind from the field name and value. This method
156
+ # is memoized so the discovery will not be done multiple times.
157
+ def kind
158
+ @kind ||= options[:kind] || FieldHelpers::Base.discover_kind(field, original_value)
159
+ end
160
+
161
+ # Creates a new "base field". This contains the given options and attributes and provides convenience
162
+ # methods to make everything easier.
163
+ def initialize(template, record, field, options={})
164
+ self.template = template
165
+ self.record = record
166
+ self.field = field
167
+ self.options = default_options.merge(options)
168
+ end
169
+
170
+ # A small wrapper around <tt>value</tt>. This method takes care of custom formatting and might in the future
171
+ # also take care of some form of filtering and escaping.
172
+ def value_for_view
173
+ unless record.nil?
174
+ format = options[:format] || "%s"
175
+ format % value
176
+ end
177
+ end
178
+
179
+ end