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.
@@ -0,0 +1,203 @@
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
+ # The methods defined here will be included in all views as helpers. Read the documentation
25
+ # for each of them to get a good understanding of how everything works.
26
+ module FieldHelpers::Helper
27
+
28
+ #
29
+ # Returns a rendering of a specific field from a given record.
30
+ #
31
+ # == Features
32
+ # * Display translated field name
33
+ # * Display view-friendly variants of values
34
+ # * Email adresses as <tt>mail_to</tt> links
35
+ # * Money as <tt>number_to_currency</tt> dictates
36
+ # * nil and blank values behave differently, but safely
37
+ # * And more!
38
+ # * Consistent markup
39
+ #
40
+ # == Value
41
+ # The logic behind the value being displayed is passed off to <tt>field_value</tt>. See documentation for
42
+ # <tt>field_value</tt> for more information about that.
43
+ #
44
+ # Before being used, the resulting value will also be passed through <tt>simple_format</tt>, which will
45
+ # convert newlines and paragraph breaks into their proper html elements.
46
+ #
47
+ # == Title / Label / Field name
48
+ # By default, the field name will be whatever the record says it should be through <tt>human_attribute_name</tt>,
49
+ # but you can customize this by giving a translation key to the <tt>:title</tt> option.
50
+ #
51
+ # == Visibility
52
+ # When the value is blank and <tt>:no_blanks</tt> option is set to true, nothing will be returned from this helper.
53
+ # This is useful for displaying a huge bunch of optional fields where most of them will not be entered,
54
+ # since only those with actual values will take space on the view.
55
+ #
56
+ # The <tt>:no_blanks</tt> option will be passed on to the <tt>field_value</tt> helper, so the fallback translation values
57
+ # will not be used.
58
+ #
59
+ def field(record, field, options = {})
60
+ no_blanks = options[:no_blanks]
61
+ title = options.delete(:title)
62
+
63
+ label = I18n.translate(title) if title
64
+ label ||= record.class.human_attribute_name(field.to_s)
65
+
66
+ value = field_value(record, field, options)
67
+
68
+ # If no_blanks is set, we should not output anything if the value is empty
69
+ return "" if no_blanks and value.blank?
70
+
71
+ formatted_value = simple_format(value)
72
+ content_tag(:div, "#{content_tag(:strong, label)}#{formatted_value}", :class => 'field')
73
+ end
74
+
75
+ #
76
+ # Returns a view-friendly representation of a record's field value.
77
+ #
78
+ # == Friendly representation
79
+ # Sometimes you want to display an optional value on a page, so you need to think about the event of
80
+ # the value being nil. Now, if you want to display a placholder for the nil value, you'll have to place
81
+ # a conditional in your view, which clutters and adds complexity. By using this helper, you can avoid
82
+ # situations like these.
83
+ #
84
+ # If the field you specify is nil or empty the first working variant of a placeholder will be displayed
85
+ # instead:
86
+ # * Model.human_attribute_name("no_field_name")
87
+ # * I18n.translate(:"activerecord.attributes.blank_field_value")
88
+ # * "" (Empty string)
89
+ #
90
+ # Now, this is a lot easier to handle on a per-case basis. If you want to always display an empty value
91
+ # instead of looking for the translations, you can do so by passing the <tt>:no_blanks</tt> option.
92
+ #
93
+ # == Special value kinds
94
+ # Some fields need special treatment. It could be a field containing money values which you want to be
95
+ # formatted correctly. It could be that you want the date component from a DateTime. You could want a
96
+ # simple "Yes" or "No" for a boolean.
97
+ #
98
+ # You can do this be specifying the <tt>:kind</tt> parameter (or relying on the auto-detection). The following
99
+ # kinds are supported:
100
+ # [<tt>:money</tt>] Value is passed through number_to_currency. Auto-discovered when field name contains
101
+ # "price" or "amount".
102
+ #
103
+ # [<tt>:email</tt>] Value is passed through mail_to. Auto-discovered when field name is "email".
104
+ #
105
+ # [<tt>:date</tt>] Value is casted to date and then to string. Effectivly displaying only the date component
106
+ # of any date-like value. Autodiscovered when field name ends with "_on" or value is a
107
+ # Date object.
108
+ #
109
+ # [<tt>:time</tt>] Value is casted to time and then to a formatted string following the template <tt>:time</tt>.
110
+ # This removes anything but the hour, minute and second from the value. Never auto-
111
+ # discovered.
112
+ #
113
+ # [<tt>:datetime</tt>] Value is being casted to a time and then directly to a string. Auto-discovered when field
114
+ # name ends with "_at" or value is a Time or TimeWithZone object.
115
+ #
116
+ # [<tt>:bool</tt>] Value will be a translation of the key <tt>:yes</tt> or <tt>:no</tt> depending on whether or not the
117
+ # value evaluates to true. Auto-discovered when the field name ends with a question mark or
118
+ # if the value is an explicit <tt>true</tt> or <tt>false</tt>.
119
+ #
120
+ # [<tt>:link</tt>] This is pretty special. See section about it below. Auto-discovered when value is a kind
121
+ # of ActiveRecord::Base.
122
+ #
123
+ # == Links
124
+ # You can get actual links from using this helper. This is very useful for displaying associations that can
125
+ # be nil. If you set the kind to <tt>:link</tt> this specific logic will kick in. If there is an active association
126
+ # in the specified field (e.g. <tt>record.field</tt> is not <tt>nil</tt>) a link to the value will be generated.
127
+ #
128
+ # field_value(@order_item, :order, :kind => :link) # => "<a href=\"/orders/1\">Order One</a>"
129
+ #
130
+ # === Self-links
131
+ # You can also use this to get a link to the object itself. If the field does not contain an ActiveRecord it
132
+ # is assumed that you want a self-link. The text of the link will then be assigned to the field specified and
133
+ # the target to the object itself.
134
+ #
135
+ # field_value(@user, :full_name, :kind => :link) # => "<a href=\"/users/532\">Kevin Bacon</a>"
136
+ #
137
+ # In the case above, the following could be seen as equivalent if @order has a user attribute:
138
+ #
139
+ # field_value(@order, :user, :link_field => :full_name)
140
+ # # => "<a href=\"/users/532\">Kevin Bacon</a>"
141
+ #
142
+ # === Link text
143
+ # The link text will be genrated from the first option that is not evaluated to false:
144
+ # * Text of the <tt>:link_text</tt> option
145
+ # * The field name specified in <tt>:link_field</tt> of the linked to object (or the specified field name if it's a
146
+ # self-link)
147
+ # * The value of the first field that the target responds to:
148
+ # * <tt>name</tt>
149
+ # * <tt>title</tt>
150
+ # * <tt>label</tt>
151
+ # * The linked to object turned to a string (e.g. <tt>to_s</tt>)
152
+ #
153
+ # As you can see, in most cases you might not need to specify the text at all, but everything will be handled
154
+ # for you.
155
+ #
156
+ # === Custom path
157
+ # You might want to be able to link to a custom path, for example in the case of nested resources. You can specify
158
+ # a custom path via the <tt>:link_path</tt>.
159
+ #
160
+ # field_value(@customer, :user, :link_path => customer_user_path(@customer, @customer.user))
161
+ # # => "<a href=\"customers/10/users/23\">John Doe</a>"
162
+ #
163
+ # The method above have a large disadvantage, though. If +@customer.user+ is <tt>nil</tt>, you will get an excaption while
164
+ # generating the URL with <tt>customer_user_path</tt>. You can avoid this by specifying a lambda as the path:
165
+ #
166
+ # field_value(@customer, :user, :link_path => lambda { customer_user_path(@customer, @customer.user) })
167
+ # # => "<a href=\"customers/10/users/23\">John Doe</a>"
168
+ #
169
+ # As an added bonus, you could also get the target into the block via the first argument. Here is a full example:
170
+ #
171
+ # field_value(@customer, :user, :link_path => lambda { |c| customer_user_path(c, c.user) })
172
+ # # => "<a href=\"customers/10/users/23\">John Doe</a>"
173
+ #
174
+ # field_value(@customer, :user,
175
+ # :link_text => "Search Google for this user's name",
176
+ # :link_path => lambda { |cust| "http://www.google.com/search?q=#{cust.full_name}" })
177
+ # # => "<a href=\"http://www.google.com/search?q=Kevin Bacon\">Search Google for this user's name</a>"
178
+ #
179
+ # === Additional options
180
+ # Any extra options to the link_to method (like class attributes) can be passed to <tt>:link_options</tt>. They will be
181
+ # forwarded correctly.
182
+ #
183
+ # == Custom format
184
+ # You can do your own small custom formatting on top of all this by passing the <tt>:format</tt> option. The
185
+ # first instance of "%s" will be replaced by the value. Say that you want to display a normal number
186
+ # as a percent by appending a % to the end of it, just pass
187
+ # :format => "%s%%"
188
+ # The double percent sign is to quote it from substitution.
189
+ #
190
+ #
191
+ # == Example
192
+ # field_value(@user, :name) # => "John"
193
+ # field_value(@user, :name, :format => "Mr. %s") # => "Mr. John"
194
+ # field_value(@user, :pay, :kind => :money) # => "$1,000.00"
195
+ # field_value(@user, :active?) # => "Yes"
196
+ # field_value(@user, :group) # => "<a href=\"/groups/14\">Administrators</a>"
197
+ # field_value(@user, :created_at, :kind => :time) # => "13:45:02"
198
+ #
199
+ def field_value(record, field, options = {})
200
+ FieldHelpers::Base.new(self, record, field, options).value_for_view
201
+ end
202
+
203
+ end
@@ -0,0 +1,255 @@
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
+ # This class handles the auto-discovery and formatting of the built-in kinds.
25
+ # It will be loaded and registered by FieldHelpers module.
26
+ #
27
+ # == Kind discovery
28
+ # Kind discovery works around two things: Field name and field value.
29
+ #
30
+ # The field name is pretty straightforward; it's stuff like "name", "created_at"
31
+ # and similar names. Value is also pretty easy since it's just the raw value for
32
+ # the specified field name, which could be instances of other classes, <tt>nil</tt>, etc.
33
+ #
34
+ # A method that takes care of discovering types is called "discoverer". If a method
35
+ # returns <tt>true</tt> (explicit!) the discovery will be concluded positive and the kind
36
+ # be assumed from there. No more discoverers will be called.
37
+ #
38
+ # Here is an example discovery method:
39
+ # def discover_dog(field, value)
40
+ # true if value =~ /dog/i or field == :pet
41
+ # end
42
+ #
43
+ # == Kind conversion
44
+ # Before being returned, the *value* (not default value) will always be converted to
45
+ # the proper kind. This involves calling a "helper method" as they are called. A helper
46
+ # method should take one argument: An instance of the "base" (FieldHelpers::Base).
47
+ #
48
+ # You most probably want to get a hold of the actual value in question, which you can
49
+ # do by calling <tt>original_value</tt> on the base. Avoid calling <tt>value</tt> since you might
50
+ # end up in an infinite loop in which <tt>value</tt> will call your helper and your helper will
51
+ # call <tt>value</tt> again.
52
+ #
53
+ # If you want to access any options given to the field helpers, you can do so through
54
+ # the base object. You can actually access about anything, even call discoverers and
55
+ # other helpers if you so wish.
56
+ #
57
+ # A conversion helper should always return a string, else the behavior is undefined.
58
+ #
59
+ # Here is a couple of example helper methods:
60
+ #
61
+ # def convert_title(base)
62
+ # base.original_value.to_s.titleize
63
+ # end
64
+ #
65
+ # def convert_password(base)
66
+ # if base.options[:password_full]
67
+ # base.original_value.gsub(/./, '*')
68
+ # else
69
+ # "************"
70
+ # end
71
+ # end
72
+ #
73
+ class FieldHelpers::Kinds
74
+ class << self
75
+
76
+ # Returns a hash of <tt>kind</tt> => <tt>method proc</tt> for every kind with a helper
77
+ # method. This is done through metaprogamming so you only need to add methods
78
+ # here if you want to add new methods to the FieldHelpers core.
79
+ def available_kinds_with_helpers
80
+ flat = self.methods.collect do |m|
81
+ if m.to_s =~ /convert_to_(.*)/
82
+ [$1.to_sym, self.method(m.to_sym)]
83
+ else
84
+ nil
85
+ end
86
+ end
87
+
88
+ Hash[*flat.compact.flatten]
89
+ end
90
+
91
+ # Returns a hash of <tt>kind</tt> => <tt>method proc</tt> for every kind with a
92
+ # discoverer. This is done through metaprogamming so you only need to add methods
93
+ # here if you want to add new methods to the FieldHelpers core.
94
+ def available_kinds_with_discoverers
95
+ flat = self.methods.collect do |m|
96
+ if m.to_s =~ /discover_(.*)/
97
+ [$1.to_sym, self.method(m.to_sym)]
98
+ else
99
+ nil
100
+ end
101
+ end
102
+
103
+ Hash[*flat.compact.flatten]
104
+ end
105
+
106
+ #### Discovery methods
107
+
108
+ # Discovers dates by looking at the following:
109
+ # * Field name ends with "_on"
110
+ # * Value is a <tt>Date</tt> instance
111
+ # If any of these are true, the discovery will be positive.
112
+ def discover_date(name, value)
113
+ true if name =~ /_on$/ or value.is_a?(Date)
114
+ end
115
+
116
+ # Discovers datetimes by looking at the following:
117
+ # * Field name ends with "_at"
118
+ # * Value is a <tt>DateTime</tt> instance
119
+ # * Value is a <tt>Time</tt> instance
120
+ # * Value is a <tt>TimeWithZone</tt> instance
121
+ # If any of these are true, the discovery will be positive.
122
+ def discover_datetime(name, value)
123
+ true if name =~ /_at$/ or value.is_a?(DateTime) or value.is_a?(Time) or value.is_a?(::ActiveSupport::TimeWithZone)
124
+ end
125
+
126
+ # Discovers booleans by looking at the following:
127
+ # * Field name ends with "?"
128
+ # * Value is <tt>true</tt>
129
+ # * Value is <tt>false</tt>
130
+ # If any of these are true, the discovery will be positive.
131
+ def discover_bool(name, value)
132
+ true if name =~ /\?$/ or value === true or value === false
133
+ end
134
+
135
+ # Discovers money by looking at the following:
136
+ # * Field name contains "cost"
137
+ # * Field name contains "price"
138
+ # If any of these are true, the discovery will be positive.
139
+ def discover_money(name, value)
140
+ true if name =~ /cost|price/
141
+ end
142
+
143
+ # Discovers email by looking at the following:
144
+ # * Field name contains "email"
145
+ # * Field name contains "e-mail"
146
+ # * Field name contains "e_mail"
147
+ # If any of these are true, the discovery will be positive.
148
+ def discover_email(name, value)
149
+ true if name =~ /e[-_]?mail/
150
+ end
151
+
152
+ # Discovers links by looking at value and seeing if it is an <tt>ActiveRecord::Base</tt>
153
+ # instance
154
+ def discover_link(name, value)
155
+ true if value.kind_of?(::ActiveRecord::Base)
156
+ end
157
+
158
+ ### Conversion methods
159
+
160
+ # Just calls to_s on the value
161
+ def convert_to_string(base)
162
+ base.original_value.to_s
163
+ end
164
+
165
+ # Converts to a <tt>Date</tt> instace by calling <tt>to_date</tt> and then calls <tt>to_s</tt> on it
166
+ def convert_to_date(base)
167
+ base.original_value.to_date.to_s
168
+ end
169
+
170
+ # Converts to a <tt>Time</tt> instance through <tt>to_time</tt> and then calls <tt>to_s</tt> with the
171
+ # <tt>:time</tt> formatting.
172
+ # This converts full <tt>Time</tt> objects like "2009-02-06 12:34:55 +0100" into just "12:34:55"
173
+ def convert_to_time(base)
174
+ base.original_value.to_time.to_s(:time)
175
+ end
176
+
177
+ # Converts to <tt>Time</tt> with <tt>to_time</tt> and then calls <tt>to_s</tt>. This will display dates
178
+ # in their full form.
179
+ def convert_to_datetime(base)
180
+ base.original_value.to_time.to_s
181
+ end
182
+
183
+ # If value evaluates to true, returns a translation of "yes" from <tt>I18n</tt>. Else, will return
184
+ # a translation of "no".
185
+ def convert_to_bool(base)
186
+ return I18n.translate(:yes) if base.original_value
187
+ I18n.translate(:no)
188
+ end
189
+
190
+ # Passes the value through <tt>number_to_currency</tt>.
191
+ def convert_to_money(base)
192
+ base.template.number_to_currency(base.original_value)
193
+ end
194
+
195
+ # Passes the value through <tt>mail_to</tt>.
196
+ def convert_to_email(base)
197
+ base.template.mail_to(base.original_value)
198
+ end
199
+
200
+ # This is a complex one. Please see FieldHelpers::Base for information about how this works. It is written
201
+ # in the class documentation.
202
+ #
203
+ # In summary: Passes the value through <tt>link_to</tt> if the value is a model instance, else passes the
204
+ # <tt>record</tt> through <tt>link_to</tt> with the value of the field as the text for the link. Accepts
205
+ # a few options, too.
206
+ def convert_to_link(base)
207
+ if base.original_value.kind_of? ActiveRecord::Base
208
+ target = base.original_value # Not a self-link
209
+ else
210
+ target = base.record # Self-link
211
+ base.options[:link_field] = base.field
212
+ end
213
+
214
+ text = get_link_text(target, base)
215
+ path = get_link_path(target, base)
216
+
217
+ base.template.link_to(text, path, base.options[:link_options])
218
+ end
219
+
220
+ # Handles the link text logic for <tt>convert_to_link</tt>. This is an internal method but public
221
+ # in case you want to use it for your own helpers.
222
+ #
223
+ # It takes the target record and then the base in which the options are stored.
224
+ def get_link_text(target, base)
225
+ options = base.options
226
+ if options[:link_text]
227
+ return options[:link_text]
228
+ elsif options[:link_field]
229
+ return target.send(options[:link_field])
230
+ end
231
+
232
+ %w{name title label}.each do |field|
233
+ return target.send(field.to_sym) if target.respond_to?(field.to_sym)
234
+ end
235
+
236
+ target.to_s
237
+ end
238
+
239
+ # Handles the link path logic for <tt>convert_to_link</tt>. This is an internal method but public
240
+ # in case you want to use it for yout own helpers.
241
+ #
242
+ # It takes the target record and then the base in which the options are stored.
243
+ def get_link_path(target, base)
244
+ path = base.options[:link_path]
245
+ if path.kind_of? Proc
246
+ path = path.call(target)
247
+ elsif path.nil?
248
+ path = target
249
+ end
250
+ path
251
+ end
252
+
253
+ end
254
+
255
+ end