Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit b522cc9e11445290621d720242748f98bd5c06df 0 parents
@josevalim josevalim authored
0  CHANGELOG.rdoc
No changes.
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 [name of plugin creator]
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 README.rdoc
@@ -0,0 +1,31 @@
+== ShowFor
+
+Describe.
+
+== Installation
+
+Install the gem:
+
+ sudo gem install show_for
+
+Configure simple_form gem inside your app:
+
+ config.gem 'show_for'
+
+Run the generator:
+
+ ruby script/generate show_for
+
+And you're ready to go.
+
+== Contributors
+
+* José Valim (http://github.com/josevalim)
+
+== Bugs and Feedback
+
+If you discover any bugs or want to drop a line, feel free to create an issue on GitHub.
+
+http://github.com/plataformatec/show_for/issues
+
+MIT License. Copyright 2009 Plataforma Tecnologia. http://blog.plataformatec.com.br
23 Rakefile
@@ -0,0 +1,23 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the simple_form plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.libs << 'test'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the show_for plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'ShowFor'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
3  generators/show_for_install/USAGE
@@ -0,0 +1,3 @@
+To copy a ShowFor initializer to your Rails App, with some configuration values, just do:
+
+ script/generate show_for_install
19 generators/show_for_install/show_for_install_generator.rb
@@ -0,0 +1,19 @@
+class ShowForInstallGenerator < Rails::Generator::Base
+
+ def manifest
+ record do |m|
+ m.directory 'config/initializers'
+ m.template 'show_for.rb', 'config/initializers/show_for.rb'
+
+ m.directory 'config/locales'
+ m.template locale_file, 'config/locales/show_for.en.yml'
+ end
+ end
+
+ private
+
+ def locale_file
+ @locale_file ||= '../../../lib/show_for/locale/en.yml'
+ end
+
+end
4 generators/show_for_install/templates/show_for.rb
@@ -0,0 +1,4 @@
+# Use this setup block to configure all options available in ShowFor.
+ShowFor.setup do |config|
+
+end
1  init.rb
@@ -0,0 +1 @@
+require 'show_for'
268 lib/show_for.rb
@@ -0,0 +1,268 @@
+# ShowFor allows you to quickly show a model information with I18n features.
+#
+# <% show_for @admin do |a| %>
+# <%= a.attribute :name %>
+# <%= a.attribute :confirmed? %>
+# <%= a.attribute :created_at, :format => :short %>
+# <%= a.attribute :last_sign_in_at, :if_blank => "Administrator did not access yet"
+# :wrapper_html => { :id => "sign_in_timestamp" } %>
+#
+# <% a.attribute :photo do %>
+# <%= image_url(@admin.photo_url) %>
+# <% end %>
+# <% end %>
+#
+# Will generate something like:
+#
+# <div id="admin_1" class="show_for admin">
+# <p id="admin_name" class="wrapper">
+# <b class="label">Name</b><br />
+# José Valim
+# </p>
+# <p id="admin_confirmed" class="wrapper">
+# <b class="label">Confirmed?</b><br />
+# Yes
+# </p>
+# <p id="admin_created_at" class="wrapper">
+# <b class="label">Created at</b><br />
+# 13/12/2009 - 19h17
+# </p>
+# <p id="sign_in_timestamp" class="wrapper">
+# <b class="label">Last sign in at</b><br />
+# Administrator did not access yet
+# </p>
+# <p id="admin_photo" class="wrapper">
+# <b class="label">Photo</b><br />
+# <img src="path/to/photo" />
+# </p>
+# </div>
+#
+# == Value lookup
+#
+# To show the proper value, before retrieving the attribute value, show_for
+# first looks if a block without argument was given, otherwise checks if a
+# :"human_#{attribute}" method is defined and, if not, only then retrieve
+# the attribute.
+#
+# == Options
+#
+# show_for handles a series of options. Those are:
+#
+# * :escape * - When the attribute should be escaped. True by default.
+#
+# * :format * - Sent to I18n.localize when the attribute is a date/time object.
+#
+# * :if_blank * - An object to be used if the value is blank. Not escaped as well.
+#
+# Besides, all containers (:label, :content and :wrapper) can have their html
+# options configured through the :label_html, :content_html and :wrapper_html
+# options. Containers can have their tags configured on demand as well through
+# :label_tag, :content_tag and :wrapper_tag options.
+#
+# == Label
+#
+# show_for also exposes the label method. In case you want to use the default
+# human_attribute_name lookup and the default wrapping:
+#
+# a.label :name #=> <b class="label">Name</b>
+# a.label "Name", :id => "my_name" #=> <b class="label" id="my_name">Name</b>
+#
+# == Associations
+#
+# show_for also supports associations.
+#
+# <% show_for @artwork do |a| %>
+# <%= a.association :artist %>
+# <%= a.association :artist, :method => :name_with_title %>
+#
+# <%= a.association :tags %>
+# <%= a.association :tags do
+# @artwork.tags.map(&:name).to_sentence
+# end %>
+#
+# <% a.association :fans, :collection_tag => :ul do |fan| %>
+# <li><%= link_to fan.name, fan %></li>
+# <% end %>
+# <% end %>
+#
+# The first is a has_one or belongs_to association, which works like an attribute
+# to show_for, except it will retrieve the artist association and try to find a
+# proper attribute from ShowFor.association_methods to be used. You can pass
+# the option :attribute to tell (and not guess) which attribute from the association
+# to use.
+#
+# :tags is a has_and_belongs_to_many association which will return a collection.
+# show_for can handle collections by default by wrapping them in list (<ul> with
+# each item wrapped by an <li>). However, it also allows you to give a block
+# which receives the collection item, as in :fans. In both cases, you can use
+# both :collection_tag to set a new tag and :collection_html to customize it.
+#
+module ShowFor
+ mattr_accessor :show_for_tag
+ @@show_for_tag = :div
+
+ mattr_accessor :label_tag
+ @@label_tag = :b
+
+ mattr_accessor :separator
+ @@separator = "<br />"
+
+ mattr_accessor :content_tag
+ @@content_tag = nil
+
+ mattr_accessor :wrapper_tag
+ @@wrapper_tag = :p
+
+ mattr_accessor :collection_tag
+ @@collection_tag = :ul
+
+ mattr_accessor :default_collection_proc
+ @@default_collection_proc = lambda { |value| "<li>#{value}</li>" }
+
+ mattr_accessor :i18n_format
+ @@i18n_format = :default
+
+ mattr_accessor :association_methods
+ @@label_methods = [ :name, :title, :to_s ]
+
+ class Builder
+ attr_reader :object, :template
+
+ def initialize(object, template)
+ @object, @template = object, template
+ end
+
+ def label(text=nil, options=nil)
+ @options ||= {}
+
+ label = if text.is_a?(String)
+ text
+ elsif @options[:label]
+ @options.delete(:label)
+ else
+ human_attribute_name(text || @attribute_name)
+ end
+
+ @options[:label_html] = options if options
+ wrap_with :label, label
+ end
+
+ def attribute(attribute_name, options={}, &block)
+ @attribute_name = attribute_name
+ @options = options
+ @value ||= find_value
+ @block = block
+ set_default_options!
+ wrap_with_label_and_wrapper(content)
+ end
+
+ def association(association_name, options={}, &block)
+ reflection = find_association_reflection(association_name)
+ raise "Association #{association_name.inspect} not found" unless reflection
+
+ association = @object.send(association_name)
+
+ # If a block with an iterator was given, no need to calculate the labels
+ # since we want the collection to be yielded. Otherwise, calculate the values.
+ @value = if block_given?
+ association if block.arity == 1
+ else
+ sample = association.is_a?(Array) ? association.first : association
+ method = options[:method] || ShowFor.association_methods.find { |m| sample.respond_to?(m) }
+ association.is_a?(Array) ? association.map(&attribute) : association.send(attribute)
+ end
+
+ returning(attribute(association_name, options, &block)){ @value = nil }
+ end
+
+ protected
+
+ def content #:nodoc:
+ content = case @value
+ when Date, Time, DateTime
+ I18n.l @value, :format => @options.delete(:format) || ShowFor.i18n_format
+ when TrueClass
+ I18n.t :"show_for.yes", :default => "Yes"
+ when FalseClass
+ I18n.t :"show_for.no", :default => "No"
+ when Array
+ @options[:escape] = false
+ collection_handler
+ when Proc
+ @options[:escape] = false
+ @template.capture(&@value)
+ when nil, ""
+ @options[:escape] = false
+ @options.delete(:if_blank)
+ else
+ @value
+ end
+
+ content = @template.send(:h, content) unless @options.delete(:escape) == false
+ wrap_with :content, content
+ end
+
+ def collection_handler
+ iterator = @block && @block.arity == 1 ? @block : ShowFor.default_collection_proc
+ response = ""
+
+ @value.each do |item|
+ response << template.capture(item, &iterator)
+ end
+
+ wrap_with(:collection, response)
+ end
+
+ def object_name #:nodoc:
+ @object_name ||= @object.class.name.underscore
+ end
+
+ def wrap_with_label_and_wrapper(html) #:nodoc:
+ wrap_with(:wrapper, label + ShowFor.separator.to_s + html, true, !!@block)
+ end
+
+ def human_attribute_name(attribute) #:nodoc:
+ @object.class.human_attribute_name(attribute.to_s)
+ end
+
+ # Method which actually does the value lookup.
+ def find_value #:nodoc:
+ if @block && @block.arity < 1
+ @block
+ elsif @object.respond_to?(:"human_#{@attribute_name}")
+ @object.send :"human_#{@attribute_name}"
+ else
+ @object.send(@attribute_name)
+ end
+ end
+
+ # Set the "#{object_name}_#{attribute_name}" as id in the wrapper tag.
+ def set_default_options! #:nodoc:
+ @options[:wrapper_html] ||= {}
+ @options[:wrapper_html][:id] ||= "#{object_name}_#{@attribute_name}".gsub(/\W/, '')
+ end
+
+ # Gets the default tag set in ShowFor module and apply (if defined)
+ # around the given content. It also check for html_options in @options
+ # hash related to the current type.
+ def wrap_with(type, content, safe=false, concat=false) #:nodoc:
+ tag = @options.delete(:"#{type}_tag") || ShowFor.send(:"#{type}_tag")
+
+ html = if tag
+ html_options = @options.delete(:"#{type}_html") || {}
+ html_options[:class] = "#{type} #{html_options[:class]}".strip
+ @template.content_tag(tag, content, html_options)
+ else
+ content
+ end
+
+ html.html_safe! if safe && html.respond_to?(:html_safe!)
+ concat ? @template.concat(html) : html
+ end
+
+ # Find association related to attribute
+ def find_association_reflection(association) #:nodoc:
+ @object.class.reflect_on_association(association) if @object.class.respond_to?(:reflect_on_association)
+ end
+ end
+end
25 lib/show_for/form_helper.rb
@@ -0,0 +1,25 @@
+module ShowFor
+ module FormHelper
+ # Creates a div around the object and yields a builder.
+ #
+ # Example:
+ #
+ # show_for @user do |f|
+ # f.attribute :name
+ # f.attribute :email
+ # end
+ #
+ def show_for(object, html_options={}, &block)
+ tag = html_options.delete(:show_for_tag) || ShowFor.show_for_tag
+
+ html_options[:id] ||= dom_id(object)
+ html_options[:class] = "show_for #{dom_class(object)} #{html_options[:class]}".strip
+
+ concat(content_tag(tag, html_options) do
+ yield ShowFor::Builder.new(object, self)
+ end)
+ end
+ end
+end
+
+ActionView::Base.send :include, ShowFor::FormHelper
29 test/support/misc_helpers.rb
@@ -0,0 +1,29 @@
+module MiscHelpers
+ def store_translations(locale, translations, &block)
+ begin
+ I18n.backend.store_translations locale, translations
+ yield
+ ensure
+ I18n.reload!
+ end
+ end
+
+ def assert_no_select(*args)
+ assert_raise Test::Unit::AssertionFailedError do
+ assert_select(*args)
+ end
+ end
+
+ def swap(object, new_values)
+ old_values = {}
+ new_values.each do |key, value|
+ old_values[key] = object.send key
+ object.send :"#{key}=", value
+ end
+ yield
+ ensure
+ old_values.each do |key, value|
+ object.send :"#{key}=", value
+ end
+ end
+end
85 test/support/models.rb
@@ -0,0 +1,85 @@
+require 'ostruct'
+
+Column = Struct.new(:name, :type, :limit)
+Association = Struct.new(:klass, :name, :macro, :options)
+
+class Company < Struct.new(:id, :name)
+ def self.all(options={})
+ all = (1..3).map{|i| Company.new(i, "Company #{i}")}
+ return [all.first] if options[:conditions]
+ return [all.last] if options[:order]
+ all
+ end
+end
+
+class Tag < Struct.new(:id, :name)
+ def self.all(options={})
+ (1..3).map{|i| Tag.new(i, "Tag #{i}")}
+ end
+end
+
+class User < OpenStruct
+ # Get rid of deprecation warnings
+ undef_method :id
+
+ def new_record!
+ @new_record = true
+ end
+
+ def new_record?
+ @new_record || false
+ end
+
+ def column_for_attribute(attribute)
+ column_type, limit = case attribute.to_sym
+ when :name, :status, :password then [:string, 100]
+ when :description then :text
+ when :age then :integer
+ when :credit_limit then [:decimal, 15]
+ when :active then :boolean
+ when :born_at then :date
+ when :delivery_time then :time
+ when :created_at then :datetime
+ when :updated_at then :timestamp
+ end
+ Column.new(attribute, column_type, limit)
+ end
+
+ def self.human_attribute_name(attribute)
+ case attribute
+ when 'name'
+ 'Super User Name!'
+ when 'description'
+ 'User Description!'
+ when 'company'
+ 'Company Human Name!'
+ else
+ attribute.humanize
+ end
+ end
+
+ def self.human_name
+ "User"
+ end
+
+ def self.reflect_on_association(association)
+ case association
+ when :company
+ Association.new(Company, association, :belongs_to, {})
+ when :tags
+ Association.new(Tag, association, :has_many, {})
+ when :first_company
+ Association.new(Company, association, :has_one, {})
+ end
+ end
+
+ def errors
+ @errors ||= {
+ :name => "can't be blank",
+ :description => "must be longer than 15 characters",
+ :age => ["is not a number", "must be greater than 18"],
+ :company => "company must be present",
+ :company_id => "must be valid"
+ }
+ end
+end
33 test/test_helper.rb
@@ -0,0 +1,33 @@
+require 'rubygems'
+require 'test/unit'
+
+require 'action_controller'
+require 'action_view/test_case'
+
+begin
+ require 'ruby-debug'
+rescue LoadError
+end
+
+$:.unshift File.join(File.dirname(__FILE__), '..', 'lib', 'show_for')
+require 'show_for'
+
+Dir["#{File.dirname(__FILE__)}/support/*.rb"].each { |f| require f }
+I18n.default_locale = :en
+
+class ActionView::TestCase
+ include MiscHelpers
+
+ tests ShowFor::FormHelper
+
+ setup :setup_new_user
+
+ def setup_new_user(options={})
+ @user = User.new({
+ :id => 1,
+ :name => 'New in Simple Form!',
+ :description => 'Hello!',
+ :created_at => Time.now
+ }.merge(options))
+ end
+end

4 comments on commit b522cc9

@fnando

Please, replace that :b tag by :strong or I'll punch you in the face!

@fnando

This is the line I'm referring to: lib/show_for.rb#L105

@grimen

Nice builder, I got similar (but hackier) solution too. I would suggest using

label
value
for semantics - at very least, skip those yes. xD As a bonus with the dl/dt/dd way, you don't need
anymore and it's still nicely layout:ed with browser defaults.
@grimen

Dammit, I styled my comment (again). xP Here's what I said (with invented tags):

Nice builder, I got similar (but hackier) solution too. I would suggest using:

DL
DT label /DT
DD value /DD
/DL

for semantics - at very least, skip those B-tags yes. xD As a bonus with the DL/DT/DD way, you don't need
anymore and it's still nicely layout:ed with browser defaults.

Please sign in to comment.
Something went wrong with that request. Please try again.