= FieldHelpers
== Informal introduction
Let's play a little mind game here:
You are to build a Rails application which is very data heavy. The client wants many views with much data displayed on each.
Many of these data fields are optional so they can be nil sometimes. You want the app to be translatable with I18n.
Here is the icing on the cake: Many of the fields require special formatting.
Sounds tiresome? Indeed it would be -- if you didn't have FieldHelpers installed!
Tell me more
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
FieldHelpers. Many apps need to display values and many apps have optional values, so it makes sense to have helpers for doing this.
Okay, so I define a little helper. Big deal?
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
get to work.
You could solve the optional field problem by checking for nil:
def field(record, field)
(v = record.send(field)) ? v : ""
end
Okay, now add custom formatting, value conversion, field kind discovery, i18n capabilities and... a understanding of associations.
Yeah, not so tough anymore, are you?
Wait, what? Conversion? Discovery? What is this?
Didn't I tell you? FieldHelpers handles different kinds of fields differently. Look at this example:
field_value(@user, :name) # => "John Doe"
field_value(@user, :name, :format => "Mr. %s") # => "Mr. John Doe"
field_value(@user, :pay, :kind => :money) # => "$1,000.00"
field_value(@user, :active?) # => "Yes"
field_value(@user, :group) # => "<a href="/groups/14">Administrators"
field_value(@user, :created_at, :kind => :time) # => "13:45:02"
field_value(@user, :created_at, :kind => :date) # => "2007-02-14"
field_value(@user, :created_at) # => "2007-02-14 13:45:02 +0100"
Holy crap!
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.
You can customize the text, path, add classes, and so on. Use it to build your own helpers!
def user_field(record, field = :user)
field(record, field, :link_options => {:class => 'user'}, :link_field => :full_name)
end
user_field(@comment, :author)
=> "<div class="field">Author <a href="/users/13" class="user">Edward von Edenburgh"
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
like "name", "title" and so on. If everything fails, the record in question will just be converted to a string.
You can also do self-links. Say that you are doing a table of associated record: (This is a complex example!)
- @user.comments.each do |comment|
%tr
%td= field_value(comment, :title, :kind => :link, :link_path => post_comments_path(comment.post, comment))
%td= field_value(comment, :post)
%td= field_value(comment, :approved?)
%td= field_value(comment, :created_at)
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
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
the link_path option in a lambda and we would not get any errors at all.
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
see, using the link functionality is very easy!
Gimme! How do I install it?
Install it like any other gem from GitHub:
$> gem source -a http://gems.github.com
$> sudo gem install Mange-field_helpers
Then add it as a gem in your environment.rb:
config.gem "Mange-field_helpers", :lib => 'field_helpers', :source => 'http://gems.github.com'
== Features
You get two helpers: field and field_value. Use them for displaying values in your application.
- field_value
- Doesn't fail if the value is nil
- Discovers what kind of field it is by looking at the field name and/or value
- Formats the field from rules depending on the kind
- Returns sane default values when value was nil or an empty string
- Returns Model.human_attribute_name('no_name') when 'name' field is empty, for example
- Returns empty string if option :no_blanks is set
- Can use an additional custom format for even more fine-grained formatting in certain situations
- Can be extended with even more kinds
- Discovery is easy to extend
- Formatting is very easy to improve
- Can in theory be used outside views (but why would you want to? Sounds like a bad idea to me)
- field
- Uses field_value to get value
- Uses human_attribute_name (I18n support!) of the model to get field name
- Wraps it all in easy-to-style elements for you to use
The methods have a full test suite and most of the methods are very, very short.
== Example
Let's take the example from earlier and show how to do it without the helpers. That is a good example!
Note that we use Haml since ERB disgusts me. Haml is not required.
With FieldHelpers:
= field @user, :name
= field @user, :name, :format => "Mr. %s"
= field @user, :pay, :kind => :money
= field @user, :active?
= field @user, :group
= field @user, :created_at, :kind => :time
= field @user, :created_at, :kind => :date
= field @user, :created_at
Without FieldHelpers:
.field
%strong= User.human_attribute_name('name')
- if @user.name
= @user.name
- else
= User.human_attribute_name('no_name')
.field
%strong= User.human_attribute_name('name')
- if @user.name
== Mr. #{@user.name}
- else
= User.human_attribute_name('no_name')
.field
%strong= User.human_attribute_name('pay')
- if @user.pay
= number_to_currency(@user.pay)
- else
= User.human_attribute_name('no_pay')
.field
%strong= User.human_attribute_name('active?')
- unless @user.active?.nil?
= @user.active? ? I18n.translate(:yes) : I18n.translate(:no)
- else
= User.human_attribute_name('no_active?')
.field
%strong= User.human_attribute_name('group')
- if @user.group
= link_to @user.group.name, @user.group
- else
= User.human_attribute_name('no_group')
.field
%strong= User.human_attribute_name('created_at')
- if @user.created_at
= @user.created_at.to_time.to_s(:time)
- else
= User.human_attribute_name('no_created_at')
.field
%strong= User.human_attribute_name('created_at')
- if @user.created_at
= @user.created_at.to_date.to_s
- else
= User.human_attribute_name('no_created_at')
.field
%strong= User.human_attribute_name('created_at')
- if @user.created_at
= @user.created_at
- else
= User.human_attribute_name('no_created_at')
Now, tell me which one you want to maintain.
== Registering your own kinds
As I have mentioned earlier, you can register your own kinds. Just call FieldHelpers::Base.register_kind
and you'll be on your way to world domination. Here are some silly examples of this:
=== Adding the kinds themselves
Method 1: Using procs
FieldHelpers::Base.register_kind(:title,
lambda { |base|
base.original_value.to_s.titleize
}
)
Method 2: Using other methods
In lib/field_extensions.rb
class FieldExtensions
def convert_password(base)
if base.options[:password_full]
base.original_value.gsub(/./, '')
else
"***********"
end
end
end
Then, wherever you want to. In an initializer, perhaps?
FieldHelpers::Base.register_kind(:password, FieldExtensions.method(:convert_password))
After doing this, you can display them by specifying kind as either :password or :title. You can also
overwrite defaults kinds this way if you want.
=== Adding auto-discovery of kinds
In a much similar way as adding the helper methods, you can add methods to discover kinds.
Method 1: Using procs
FieldHelpers::Base.register_kind_discovery(:title,
lambda { |field, value|
true if field =~ /title$/
}
)
Method 2: Using blocks
FieldHelpers::Base.register_kind_discovery(:title) do |field, value|
true if field =~ /title$/
end
Method 3: Using other methods
In lib/field_extensions.rb
class FieldExtensions
def discover_password(field, value)
true if field == :password
end
end
Then, wherever you want to. In an initializer, perhaps?
FieldHelpers::Base.register_kind_discovery(:password, FieldExtensions.method(:discover_password))
You can turn of auto-discovery for certain kinds this way!
FieldHelpers::Base.register_kind_discovery(:email, lambda { false })