1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
require 'rails/version'
require 'rails' if Rails::VERSION::MAJOR > 2
module GettextI18nRails
#write all found models/columns to a file where GetTexts ruby parser can find them
def store_model_attributes(options)
file = options[:to] || 'locale/model_attributes.rb'
begin
File.open(file,'w') do |f|
f.puts "#DO NOT MODIFY! AUTOMATICALLY GENERATED FILE!"
ModelAttributesFinder.new.find(options).each do |model,column_names|
f.puts("_('#{model.humanize_class_name}')")
#all columns namespaced under the model
column_names.each do |attribute|
translation = model.gettext_translation_for_attribute_name(attribute)
f.puts("_('#{translation}')")
end
end
f.puts "#DO NOT MODIFY! AUTOMATICALLY GENERATED FILE!"
end
rescue
puts "[Error] Attribute extraction failed. Removing incomplete file (#{file})"
File.delete(file)
raise
end
end
module_function :store_model_attributes
class ModelAttributesFinder
# options:
# :ignore_tables => ['cars',/_settings$/,...]
# :ignore_columns => ['id',/_id$/,...]
# current connection ---> {'cars'=>['model_name','type'],...}
def find(options)
found = ActiveSupport::OrderedHash.new([])
models.each do |model|
attributes = model_attributes(model, options[:ignore_tables], options[:ignore_columns])
found[model] = attributes.sort if attributes.any?
end
found
end
def initialize
connection = ::ActiveRecord::Base.connection
@existing_tables = (Rails::VERSION::MAJOR >= 5 ? connection.data_sources : connection.tables)
end
# Rails < 3.0 doesn't have DescendantsTracker.
# Instead of iterating over ObjectSpace (slow) the decision was made NOT to support
# class hierarchies with abstract base classes in Rails 2.x
def model_attributes(model, ignored_tables, ignored_cols)
return [] if model.abstract_class? && Rails::VERSION::MAJOR < 3
if model.abstract_class?
model.direct_descendants.reject {|m| ignored?(m.table_name, ignored_tables)}.inject([]) do |attrs, m|
attrs.push(model_attributes(m, ignored_tables, ignored_cols)).flatten.uniq
end
elsif !ignored?(model.table_name, ignored_tables) && @existing_tables.include?(model.table_name)
model.columns.reject { |c| ignored?(c.name, ignored_cols) }.collect { |c| c.name }
else
[]
end
end
def models
if Rails::VERSION::MAJOR >= 3
# Ensure autoloaders are set up before we attempt to eager load!
Rails.application.autoloaders.each(&:setup) if Rails.application.respond_to?(:autoloaders)
Rails.application.eager_load! # make sure that all models are loaded so that direct_descendants works
descendants = ::ActiveRecord::Base.direct_descendants
# In rails 5+ user models are supposed to inherit from ApplicationRecord
if defined?(::ApplicationRecord)
descendants += ApplicationRecord.direct_descendants
descendants.delete ApplicationRecord
end
descendants
else
::ActiveRecord::Base.connection.tables \
.map { |t| table_name_to_namespaced_model(t) } \
.compact \
.collect { |c| c.superclass.abstract_class? ? c.superclass : c }
end.uniq.sort_by(&:name)
end
def ignored?(name,patterns)
return false unless patterns
patterns.detect{|p|p.to_s==name.to_s or (p.is_a?(Regexp) and name=~p)}
end
private
# Tries to find the model class corresponding to specified table name.
# Takes into account that the model can be defined in a namespace.
# Searches only up to one level deep - won't find models nested in two
# or more modules.
#
# Note that if we allow namespaces, the conversion can be ambiguous, i.e.
# if the table is named "aa_bb_cc" and AaBbCc, Aa::BbCc and AaBb::Cc are
# all defined there's no absolute rule that tells us which one to use.
# This method prefers the less nested one and, if there are two at
# the same level, the one with shorter module name.
def table_name_to_namespaced_model(table_name)
# First assume that there are no namespaces
model = to_class(table_name.singularize.camelcase)
return model if model != nil
# If you were wrong, assume that the model is in a namespace.
# Iterate over the underscores and try to substitute each of them
# for a slash that camelcase() replaces with the scope operator (::).
underscore_position = table_name.index('_')
while underscore_position != nil
namespaced_table_name = table_name.dup
namespaced_table_name[underscore_position] = '/'
model = to_class(namespaced_table_name.singularize.camelcase)
return model if model != nil
underscore_position = table_name.index('_', underscore_position + 1)
end
# The model either is not defined or is buried more than one level
# deep in a module hierarchy
return nil
end
# Checks if there is a class of specified name and if so, returns
# the class object. Otherwise returns nil.
def to_class(name)
# I wanted to use Module.const_defined?() here to avoid relying
# on exceptions for normal program flow but it's of no use.
# If class autoloading is enabled, the constant may be undefined
# but turn out to be present when we actually try to use it.
begin
constant = name.constantize
rescue NameError
return nil
rescue LoadError => e
$stderr.puts "failed to load '#{name}', ignoring (#{e.class}: #{e.message})"
return nil
end
return constant.is_a?(Class) ? constant : nil
end
end
end
|