Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

I18n inheritance #873

Closed
wants to merge 2 commits into from

4 participants

@Arjeno

This pull requests adds support for I18n model inheritance. Example setup;

en:
  formtastic:
    labels:
      group:
        title: "Your Title"
class Group < ActiveRecord::Base
end
class Company < Group
end
<%= semantic_form_for Company.new do |f| %>
  <%= f.inputs do %>
    <%= f.input :title %>      # => :label => "Your Title"
  <% end %>
<% end %>
@Arjeno

It's been 3 months now, any update? :)

@justinfrench
Owner

This seems okay, anyone care to +1?

@sobrinho
Collaborator

Seems okay :)

@the8472

Instead of using .base_class and generating a range of ancestors you might want to check if the class supports .lookup_ancestors, see http://apidock.com/rails/ActiveModel/Translation/lookup_ancestors

For the general proposal of supporting inheritance: +1

@justinfrench
Owner

@Arjeno or someone else: any chance you can get this across the line with @the8472's suggestion

@justinfrench

Closing due to lack of inactivity, would love it if @the8472 or @Arjeno could still make this happen :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 24, 2012
  1. @Arjeno
  2. @Arjeno
This page is out of date. Refresh to see the latest.
View
3  lib/formtastic/i18n.rb
@@ -12,8 +12,7 @@ module I18n
'%{nested_model}.%{action}.%{attribute}',
'%{nested_model}.%{attribute}',
'%{model}.%{action}.%{attribute}',
- '%{model}.%{attribute}',
- '%{attribute}'
+ '%{model}.%{attribute}'
]
class << self
View
13 lib/formtastic/localized_string.rb
@@ -5,6 +5,19 @@ def model_name
@object.present? ? @object.class.name : @object_name.to_s.classify
end
+ def model_names
+ unless @model_names
+ model_class = @object.class
+ classes = (model_class.ancestors - model_class.included_modules)
+ classes = model_class.respond_to?(:base_class) ?
+ classes[0..classes.index(model_class.base_class)] :
+ classes[0...classes.index(Object)]
+ @model_names = classes.map { |c| c.name.underscore }
+ end
+
+ @model_names
+ end
+
protected
def localized_string(key, value, type, options = {}) #:nodoc:
View
26 lib/formtastic/localizer.rb
@@ -68,6 +68,9 @@ def localize(key, value, type, options = {}) #:nodoc:
if use_i18n
model_name, nested_model_name = normalize_model_name(builder.model_name.underscore)
+ model_names = [model_name] + builder.model_names
+
+ model_names.uniq!
action_name = builder.template.params[:action].to_s rescue ''
attribute_name = key.to_s
@@ -78,17 +81,22 @@ def localize(key, value, type, options = {}) #:nodoc:
return cache.get(cache_key) if cache.has_key?(cache_key)
end
- defaults = Formtastic::I18n::SCOPES.reject do |i18n_scope|
+ scopes = Formtastic::I18n::SCOPES.reject do |i18n_scope|
nested_model_name.nil? && i18n_scope.match(/nested_model/)
- end.collect do |i18n_scope|
- i18n_path = i18n_scope.dup
- i18n_path.gsub!('%{action}', action_name)
- i18n_path.gsub!('%{model}', model_name)
- i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
- i18n_path.gsub!('%{attribute}', attribute_name)
- i18n_path.gsub!('..', '.')
- i18n_path.to_sym
end
+
+ defaults = model_names.collect do |_model_name|
+ scopes.map do |i18n_scope|
+ i18n_path = i18n_scope.dup
+ i18n_path.gsub!('%{action}', action_name)
+ i18n_path.gsub!('%{model}', _model_name)
+ i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
+ i18n_path.gsub!('%{attribute}', attribute_name)
+ i18n_path.gsub!('..', '.')
+ i18n_path.to_sym
+ end
+ end.flatten
+ defaults << attribute_name.to_sym
defaults << ''
defaults.uniq!
View
47 spec/helpers/input_helper_spec.rb
@@ -599,6 +599,53 @@ def length_should_be_required(options)
end
end
+ describe 'when localized label is provided for the parent model' do
+ before do
+ @localized_label_text = 'Localized title'
+ @default_localized_label_text = 'Default localized title'
+ ::I18n.backend.store_translations :en,
+ :formtastic => {
+ :labels => {
+ :title => @default_localized_label_text,
+ :published => @default_localized_label_text,
+ :post => {
+ :title => @localized_label_text,
+ :published => @default_localized_label_text
+ }
+ }
+ }
+ end
+
+ it 'should render a label with localized label (I18n)' do
+ with_config :i18n_lookups_by_default, false do
+ concat(semantic_form_for(@inherited_post) do |builder|
+ concat(builder.input(:title, :label => true))
+ concat(builder.input(:published, :as => :boolean, :label => true))
+ end)
+ output_buffer.should have_tag('form li label', Regexp.new('^' + @localized_label_text))
+ end
+ end
+
+ it 'should render a hint paragraph containing an optional localized label (I18n) if first is not set' do
+ with_config :i18n_lookups_by_default, false do
+ ::I18n.backend.store_translations :en,
+ :formtastic => {
+ :labels => {
+ :post => {
+ :title => nil,
+ :published => nil
+ }
+ }
+ }
+ concat(semantic_form_for(@inherited_post) do |builder|
+ concat(builder.input(:title, :label => true))
+ concat(builder.input(:published, :as => :boolean, :label => true))
+ end)
+ output_buffer.should have_tag('form li label', Regexp.new('^' + @default_localized_label_text))
+ end
+ end
+ end
+
describe 'when localized label is provided' do
before do
@localized_label_text = 'Localized title'
View
201 spec/spec_helper.rb
@@ -94,6 +94,17 @@ def persisted?
end
end
+ class ::InheritedPost < ::Post
+ extend ActiveModel::Naming if defined?(ActiveModel::Naming)
+ include ActiveModel::Conversion if defined?(ActiveModel::Conversion)
+
+ def id
+ end
+
+ def persisted?
+ end
+ end
+
module ::Namespaced
class Post
extend ActiveModel::Naming if defined?(ActiveModel::Naming)
@@ -182,6 +193,10 @@ def post_path(*args); "/posts/1"; end
def posts_path(*args); "/posts"; end
def new_post_path(*args); "/posts/new"; end
+ def inherited_post_path(*args); "/inherited_posts/1"; end
+ def inherited_posts_path(*args); "/inherited_posts"; end
+ def new_inherited_post_path(*args); "/inherited_posts/new"; end
+
def author_path(*args); "/authors/1"; end
def authors_path(*args); "/authors"; end
def new_author_path(*args); "/authors/new"; end
@@ -256,6 +271,22 @@ def new_author_path(*args); "/authors/new"; end
@new_post.stub!(:to_model).and_return(@new_post)
@new_post.stub!(:persisted?).and_return(nil)
+ @inherited_post = mock('inherited_post')
+ @inherited_post.stub!(:class).and_return(::InheritedPost)
+ @inherited_post.stub!(:id).and_return(nil)
+ @inherited_post.stub!(:new_record?).and_return(true)
+ @inherited_post.stub!(:errors).and_return(mock('errors', :[] => nil))
+ @inherited_post.stub!(:author).and_return(nil)
+ @inherited_post.stub!(:author_attributes=).and_return(nil)
+ @inherited_post.stub!(:authors).and_return([@fred])
+ @inherited_post.stub!(:authors_attributes=)
+ @inherited_post.stub!(:reviewer).and_return(nil)
+ @inherited_post.stub!(:main_post).and_return(nil)
+ @inherited_post.stub!(:sub_posts).and_return([]) #TODO should be a mock with methods for adding sub posts
+ @inherited_post.stub!(:to_key).and_return(nil)
+ @inherited_post.stub!(:to_model).and_return(@inherited_post)
+ @inherited_post.stub!(:persisted?).and_return(nil)
+
@freds_post = mock('post')
@freds_post.stub!(:to_ary)
@freds_post.stub!(:class).and_return(::Post)
@@ -273,41 +304,43 @@ def new_author_path(*args); "/authors/new"; end
@fred.stub!(:posts).and_return([@freds_post])
@fred.stub!(:post_ids).and_return([@freds_post.id])
- ::Post.stub!(:scoped).and_return(::Post)
- ::Post.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
- ::Post.stub!(:human_name).and_return('Post')
- ::Post.stub!(:reflect_on_all_validations).and_return([])
- ::Post.stub!(:reflect_on_validations_for).and_return([])
- ::Post.stub!(:reflections).and_return({})
- ::Post.stub!(:reflect_on_association).and_return do |column_name|
- case column_name
- when :author, :author_status
- mock = mock('reflection', :options => {}, :klass => ::Author, :macro => :belongs_to)
- mock.stub!(:[]).with(:class_name).and_return("Author")
- mock
- when :reviewer
- mock = mock('reflection', :options => {:class_name => 'Author'}, :klass => ::Author, :macro => :belongs_to)
- mock.stub!(:[]).with(:class_name).and_return("Author")
- mock
- when :authors
- mock('reflection', :options => {}, :klass => ::Author, :macro => :has_and_belongs_to_many)
- when :sub_posts
- mock('reflection', :options => {}, :klass => ::Post, :macro => :has_many)
- when :main_post
- mock('reflection', :options => {}, :klass => ::Post, :macro => :belongs_to)
- when :mongoid_reviewer
- ::MongoidReflectionMock.new('reflection',
- :options => Proc.new { raise NoMethodError, "Mongoid has no reflection.options" },
- :klass => ::Author, :macro => :referenced_in, :foreign_key => "reviewer_id") # custom id
+ [::Post, ::InheritedPost].each do |model|
+ model.stub!(:scoped).and_return(model)
+ model.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
+ model.stub!(:human_name).and_return('Post')
+ model.stub!(:reflect_on_all_validations).and_return([])
+ model.stub!(:reflect_on_validations_for).and_return([])
+ model.stub!(:reflections).and_return({})
+ model.stub!(:reflect_on_association).and_return do |column_name|
+ case column_name
+ when :author, :author_status
+ mock = mock('reflection', :options => {}, :klass => ::Author, :macro => :belongs_to)
+ mock.stub!(:[]).with(:class_name).and_return("Author")
+ mock
+ when :reviewer
+ mock = mock('reflection', :options => {:class_name => 'Author'}, :klass => ::Author, :macro => :belongs_to)
+ mock.stub!(:[]).with(:class_name).and_return("Author")
+ mock
+ when :authors
+ mock('reflection', :options => {}, :klass => ::Author, :macro => :has_and_belongs_to_many)
+ when :sub_posts
+ mock('reflection', :options => {}, :klass => ::Post, :macro => :has_many)
+ when :main_post
+ mock('reflection', :options => {}, :klass => ::Post, :macro => :belongs_to)
+ when :mongoid_reviewer
+ ::MongoidReflectionMock.new('reflection',
+ :options => Proc.new { raise NoMethodError, "Mongoid has no reflection.options" },
+ :klass => ::Author, :macro => :referenced_in, :foreign_key => "reviewer_id") # custom id
+ end
end
+ model.stub!(:find).and_return([@freds_post])
+ model.stub!(:all).and_return([@freds_post])
+ model.stub!(:where).and_return([@freds_post])
+ model.stub!(:content_columns).and_return([mock('column', :name => 'title'), mock('column', :name => 'body'), mock('column', :name => 'created_at')])
+ model.stub!(:to_key).and_return(nil)
+ model.stub!(:persisted?).and_return(nil)
+ model.stub!(:to_ary)
end
- ::Post.stub!(:find).and_return([@freds_post])
- ::Post.stub!(:all).and_return([@freds_post])
- ::Post.stub!(:where).and_return([@freds_post])
- ::Post.stub!(:content_columns).and_return([mock('column', :name => 'title'), mock('column', :name => 'body'), mock('column', :name => 'created_at')])
- ::Post.stub!(:to_key).and_return(nil)
- ::Post.stub!(:persisted?).and_return(nil)
- ::Post.stub!(:to_ary)
::MongoPost.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
::MongoPost.stub!(:human_name).and_return('MongoPost')
@@ -339,56 +372,58 @@ def new_author_path(*args); "/authors/new"; end
@mock_file.stub!(method).and_return(true)
end
- @new_post.stub!(:title)
- @new_post.stub!(:email)
- @new_post.stub!(:url)
- @new_post.stub!(:phone)
- @new_post.stub!(:search)
- @new_post.stub!(:to_ary)
- @new_post.stub!(:body)
- @new_post.stub!(:published)
- @new_post.stub!(:publish_at)
- @new_post.stub!(:created_at)
- @new_post.stub!(:secret).and_return(1)
- @new_post.stub!(:url)
- @new_post.stub!(:email)
- @new_post.stub!(:search)
- @new_post.stub!(:phone)
- @new_post.stub!(:time_zone)
- @new_post.stub!(:category_name)
- @new_post.stub!(:allow_comments).and_return(true)
- @new_post.stub!(:answer_comments)
- @new_post.stub!(:country)
- @new_post.stub!(:country_subdivision)
- @new_post.stub!(:country_code)
- @new_post.stub!(:document).and_return(@mock_file)
- @new_post.stub!(:column_for_attribute).with(:meta_description).and_return(mock('column', :type => :string, :limit => 255))
- @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :string, :limit => 50))
- @new_post.stub!(:column_for_attribute).with(:body).and_return(mock('column', :type => :text))
- @new_post.stub!(:column_for_attribute).with(:published).and_return(mock('column', :type => :boolean))
- @new_post.stub!(:column_for_attribute).with(:publish_at).and_return(mock('column', :type => :date))
- @new_post.stub!(:column_for_attribute).with(:time_zone).and_return(mock('column', :type => :string))
- @new_post.stub!(:column_for_attribute).with(:allow_comments).and_return(mock('column', :type => :boolean))
- @new_post.stub!(:column_for_attribute).with(:author).and_return(mock('column', :type => :integer))
- @new_post.stub!(:column_for_attribute).with(:country).and_return(mock('column', :type => :string, :limit => 255))
- @new_post.stub!(:column_for_attribute).with(:country_subdivision).and_return(mock('column', :type => :string, :limit => 255))
- @new_post.stub!(:column_for_attribute).with(:country_code).and_return(mock('column', :type => :string, :limit => 255))
- @new_post.stub!(:column_for_attribute).with(:email).and_return(mock('column', :type => :string, :limit => 255))
- @new_post.stub!(:column_for_attribute).with(:url).and_return(mock('column', :type => :string, :limit => 255))
- @new_post.stub!(:column_for_attribute).with(:phone).and_return(mock('column', :type => :string, :limit => 255))
- @new_post.stub!(:column_for_attribute).with(:search).and_return(mock('column', :type => :string, :limit => 255))
- @new_post.stub!(:column_for_attribute).with(:document).and_return(nil)
-
- @new_post.stub!(:author).and_return(@bob)
- @new_post.stub!(:author_id).and_return(@bob.id)
-
- @new_post.stub!(:reviewer).and_return(@fred)
- @new_post.stub!(:reviewer_id).and_return(@fred.id)
-
- @new_post.should_receive(:publish_at=).any_number_of_times
- @new_post.should_receive(:title=).any_number_of_times
- @new_post.stub!(:main_post_id).and_return(nil)
-
+ [@new_post, @inherited_post].each do |post_var|
+ post_var.stub!(:title)
+ post_var.stub!(:email)
+ post_var.stub!(:url)
+ post_var.stub!(:phone)
+ post_var.stub!(:search)
+ post_var.stub!(:to_ary)
+ post_var.stub!(:body)
+ post_var.stub!(:published)
+ post_var.stub!(:publish_at)
+ post_var.stub!(:created_at)
+ post_var.stub!(:secret).and_return(1)
+ post_var.stub!(:url)
+ post_var.stub!(:email)
+ post_var.stub!(:search)
+ post_var.stub!(:phone)
+ post_var.stub!(:time_zone)
+ post_var.stub!(:category_name)
+ post_var.stub!(:allow_comments).and_return(true)
+ post_var.stub!(:answer_comments)
+ post_var.stub!(:country)
+ post_var.stub!(:country_subdivision)
+ post_var.stub!(:country_code)
+ post_var.stub!(:document).and_return(@mock_file)
+ post_var.stub!(:column_for_attribute).with(:meta_description).and_return(mock('column', :type => :string, :limit => 255))
+ post_var.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :string, :limit => 50))
+ post_var.stub!(:column_for_attribute).with(:body).and_return(mock('column', :type => :text))
+ post_var.stub!(:column_for_attribute).with(:published).and_return(mock('column', :type => :boolean))
+ post_var.stub!(:column_for_attribute).with(:publish_at).and_return(mock('column', :type => :date))
+ post_var.stub!(:column_for_attribute).with(:time_zone).and_return(mock('column', :type => :string))
+ post_var.stub!(:column_for_attribute).with(:allow_comments).and_return(mock('column', :type => :boolean))
+ post_var.stub!(:column_for_attribute).with(:author).and_return(mock('column', :type => :integer))
+ post_var.stub!(:column_for_attribute).with(:country).and_return(mock('column', :type => :string, :limit => 255))
+ post_var.stub!(:column_for_attribute).with(:country_subdivision).and_return(mock('column', :type => :string, :limit => 255))
+ post_var.stub!(:column_for_attribute).with(:country_code).and_return(mock('column', :type => :string, :limit => 255))
+ post_var.stub!(:column_for_attribute).with(:email).and_return(mock('column', :type => :string, :limit => 255))
+ post_var.stub!(:column_for_attribute).with(:url).and_return(mock('column', :type => :string, :limit => 255))
+ post_var.stub!(:column_for_attribute).with(:phone).and_return(mock('column', :type => :string, :limit => 255))
+ post_var.stub!(:column_for_attribute).with(:search).and_return(mock('column', :type => :string, :limit => 255))
+ post_var.stub!(:column_for_attribute).with(:document).and_return(nil)
+
+ post_var.stub!(:author).and_return(@bob)
+ post_var.stub!(:author_id).and_return(@bob.id)
+
+ post_var.stub!(:reviewer).and_return(@fred)
+ post_var.stub!(:reviewer_id).and_return(@fred.id)
+
+ post_var.should_receive(:publish_at=).any_number_of_times
+ post_var.should_receive(:title=).any_number_of_times
+ post_var.stub!(:main_post_id).and_return(nil)
+ end
+
end
def self.included(base)
Something went wrong with that request. Please try again.