hobo_fields 1.3.0.RC
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.
- data/CHANGES.txt +38 -0
- data/LICENSE.txt +22 -0
- data/README.txt +8 -0
- data/Rakefile +36 -0
- data/VERSION +1 -0
- data/bin/hobofields +19 -0
- data/hobo_fields.gemspec +31 -0
- data/lib/generators/hobo/migration/USAGE +47 -0
- data/lib/generators/hobo/migration/migration_generator.rb +162 -0
- data/lib/generators/hobo/migration/migrator.rb +445 -0
- data/lib/generators/hobo/migration/templates/migration.rb.erb +9 -0
- data/lib/generators/hobo/model/USAGE +19 -0
- data/lib/generators/hobo/model/model_generator.rb +11 -0
- data/lib/generators/hobo/model/templates/model_injection.rb.erb +18 -0
- data/lib/hobo_fields/extensions/active_record/attribute_methods.rb +48 -0
- data/lib/hobo_fields/extensions/active_record/fields_declaration.rb +21 -0
- data/lib/hobo_fields/field_declaration_dsl.rb +33 -0
- data/lib/hobo_fields/model/field_spec.rb +121 -0
- data/lib/hobo_fields/model/index_spec.rb +47 -0
- data/lib/hobo_fields/model.rb +226 -0
- data/lib/hobo_fields/railtie.rb +13 -0
- data/lib/hobo_fields/sanitize_html.rb +23 -0
- data/lib/hobo_fields/types/email_address.rb +26 -0
- data/lib/hobo_fields/types/enum_string.rb +101 -0
- data/lib/hobo_fields/types/html_string.rb +15 -0
- data/lib/hobo_fields/types/lifecycle_state.rb +16 -0
- data/lib/hobo_fields/types/markdown_string.rb +15 -0
- data/lib/hobo_fields/types/password_string.rb +15 -0
- data/lib/hobo_fields/types/raw_html_string.rb +13 -0
- data/lib/hobo_fields/types/raw_markdown_string.rb +13 -0
- data/lib/hobo_fields/types/serialized_object.rb +15 -0
- data/lib/hobo_fields/types/text.rb +16 -0
- data/lib/hobo_fields/types/textile_string.rb +22 -0
- data/lib/hobo_fields.rb +94 -0
- data/test/api.rdoctest +244 -0
- data/test/doc-only.rdoctest +96 -0
- data/test/generators.rdoctest +53 -0
- data/test/interactive_primary_key.rdoctest +54 -0
- data/test/migration_generator.rdoctest +639 -0
- data/test/migration_generator_comments.rdoctest +75 -0
- data/test/prepare_testapp.rb +8 -0
- data/test/rich_types.rdoctest +394 -0
- metadata +140 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
module HoboFields
|
2
|
+
module Model
|
3
|
+
|
4
|
+
class FieldSpec
|
5
|
+
|
6
|
+
class UnknownSqlTypeError < RuntimeError; end
|
7
|
+
|
8
|
+
def initialize(model, name, type, options={})
|
9
|
+
raise ArgumentError, "you cannot provide a field spec for the primary key" if name == model.primary_key
|
10
|
+
self.model = model
|
11
|
+
self.name = name.to_sym
|
12
|
+
self.type = type.is_a?(String) ? type.to_sym : type
|
13
|
+
position = options.delete(:position)
|
14
|
+
self.options = options
|
15
|
+
self.position = position || model.field_specs.length
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :model, :name, :type, :position, :options
|
19
|
+
|
20
|
+
TYPE_SYNONYMS = [[:timestamp, :datetime]]
|
21
|
+
|
22
|
+
begin
|
23
|
+
MYSQL_COLUMN_CLASS = ActiveRecord::ConnectionAdapters::MysqlColumn
|
24
|
+
rescue NameError
|
25
|
+
MYSQL_COLUMN_CLASS = NilClass
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
SQLITE_COLUMN_CLASS = ActiveRecord::ConnectionAdapters::SQLiteColumn
|
30
|
+
rescue NameError
|
31
|
+
SQLITE_COLUMN_CLASS = NilClass
|
32
|
+
end
|
33
|
+
|
34
|
+
def sql_type
|
35
|
+
options[:sql_type] or begin
|
36
|
+
if native_type?(type)
|
37
|
+
type
|
38
|
+
else
|
39
|
+
field_class = HoboFields.to_class(type)
|
40
|
+
field_class && field_class::COLUMN_TYPE or raise UnknownSqlTypeError, "#{type.inspect} for #{model}.#{name}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def limit
|
46
|
+
options[:limit] || native_types[sql_type][:limit]
|
47
|
+
end
|
48
|
+
|
49
|
+
def precision
|
50
|
+
options[:precision]
|
51
|
+
end
|
52
|
+
|
53
|
+
def scale
|
54
|
+
options[:scale]
|
55
|
+
end
|
56
|
+
|
57
|
+
def null
|
58
|
+
:null.in?(options) ? options[:null] : true
|
59
|
+
end
|
60
|
+
|
61
|
+
def default
|
62
|
+
options[:default]
|
63
|
+
end
|
64
|
+
|
65
|
+
def comment
|
66
|
+
options[:comment]
|
67
|
+
end
|
68
|
+
|
69
|
+
def same_type?(col_spec)
|
70
|
+
t = sql_type
|
71
|
+
TYPE_SYNONYMS.each do |synonyms|
|
72
|
+
if t.in? synonyms
|
73
|
+
return col_spec.type.in?(synonyms)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
t == col_spec.type
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def different_to?(col_spec)
|
81
|
+
!same_type?(col_spec) ||
|
82
|
+
# we should be able to use col_spec.comment, but col_spec has
|
83
|
+
# a nil table_name for some strange reason.
|
84
|
+
begin
|
85
|
+
if model.table_exists?
|
86
|
+
col_comment = ActiveRecord::Base.try.column_comment(col_spec.name, model.table_name)
|
87
|
+
col_comment != nil && col_comment != comment
|
88
|
+
else
|
89
|
+
false
|
90
|
+
end
|
91
|
+
end ||
|
92
|
+
begin
|
93
|
+
check_attributes = [:null, :default]
|
94
|
+
check_attributes += [:precision, :scale] if sql_type == :decimal && !col_spec.is_a?(SQLITE_COLUMN_CLASS) # remove when rails fixes https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2872
|
95
|
+
check_attributes -= [:default] if sql_type == :text && col_spec.is_a?(MYSQL_COLUMN_CLASS)
|
96
|
+
check_attributes << :limit if sql_type.in?([:string, :text, :binary, :integer])
|
97
|
+
check_attributes.any? do |k|
|
98
|
+
if k==:default && sql_type==:datetime
|
99
|
+
col_spec.default.try.to_datetime != default.try.to_datetime
|
100
|
+
else
|
101
|
+
col_spec.send(k) != self.send(k)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def native_type?(type)
|
111
|
+
type.in?(native_types.keys - [:primary_key])
|
112
|
+
end
|
113
|
+
|
114
|
+
def native_types
|
115
|
+
Generators::Hobo::Migration::Migrator.native_types
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module HoboFields
|
2
|
+
module Model
|
3
|
+
|
4
|
+
class IndexSpec
|
5
|
+
|
6
|
+
def initialize(model, fields, options={})
|
7
|
+
@model = model
|
8
|
+
self.table = options.delete(:table_name) || model.table_name
|
9
|
+
self.fields = Array.wrap(fields).*.to_s
|
10
|
+
self.name = options.delete(:name) || model.connection.index_name(self.table, :column => self.fields)
|
11
|
+
self.unique = options.delete(:unique) || false
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :table, :fields, :name, :unique
|
15
|
+
|
16
|
+
# extract IndexSpecs from an existing table
|
17
|
+
def self.for_model(model, old_table_name=nil)
|
18
|
+
t = old_table_name || model.table_name
|
19
|
+
model.connection.indexes(t).map do |i|
|
20
|
+
self.new(model, i.columns, :name => i.name, :unique => i.unique, :table_name => old_table_name) unless model.ignore_indexes.include?(i.name)
|
21
|
+
end.compact
|
22
|
+
end
|
23
|
+
|
24
|
+
def default_name?
|
25
|
+
name == @model.connection.index_name(table, :column => fields)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_add_statement(new_table_name)
|
29
|
+
r = "add_index :#{new_table_name}, #{fields.*.to_sym.inspect}"
|
30
|
+
r += ", :unique => true" if unique
|
31
|
+
r += ", :name => '#{name}'" unless default_name?
|
32
|
+
r
|
33
|
+
end
|
34
|
+
|
35
|
+
def hash
|
36
|
+
[table, fields, name, unique].hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(v)
|
40
|
+
v.hash == hash
|
41
|
+
end
|
42
|
+
alias_method :eql?, :==
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module HoboFields
|
2
|
+
|
3
|
+
Model = classy_module do
|
4
|
+
|
5
|
+
# ignore the model in the migration until somebody sets
|
6
|
+
# @include_in_migration via the fields declaration
|
7
|
+
inheriting_cattr_reader :include_in_migration => false
|
8
|
+
|
9
|
+
# attr_types holds the type class for any attribute reader (i.e. getter
|
10
|
+
# method) that returns rich-types
|
11
|
+
inheriting_cattr_reader :attr_types => HashWithIndifferentAccess.new
|
12
|
+
inheriting_cattr_reader :attr_order => []
|
13
|
+
|
14
|
+
# field_specs holds FieldSpec objects for every declared
|
15
|
+
# field. Note that attribute readers are created (by ActiveRecord)
|
16
|
+
# for all fields, so there is also an entry for the field in
|
17
|
+
# attr_types. This is redundant but simplifies the implementation
|
18
|
+
# and speeds things up a little.
|
19
|
+
inheriting_cattr_reader :field_specs => HashWithIndifferentAccess.new
|
20
|
+
|
21
|
+
# index_specs holds IndexSpec objects for all the declared indexes.
|
22
|
+
inheriting_cattr_reader :index_specs => []
|
23
|
+
inheriting_cattr_reader :ignore_indexes => []
|
24
|
+
|
25
|
+
# eval avoids the ruby 1.9.2 "super from singleton method ..." error
|
26
|
+
eval %(
|
27
|
+
def self.inherited(klass)
|
28
|
+
fields do |f|
|
29
|
+
f.field(inheritance_column, :string)
|
30
|
+
end
|
31
|
+
index(inheritance_column)
|
32
|
+
super
|
33
|
+
end
|
34
|
+
)
|
35
|
+
|
36
|
+
def self.index(fields, options = {})
|
37
|
+
# don't double-index fields
|
38
|
+
index_specs << HoboFields::Model::IndexSpec.new(self, fields, options) unless index_specs.*.fields.include?(Array.wrap(fields).*.to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
# tell the migration generator to ignore the named index. Useful for existing indexes, or for indexes
|
42
|
+
# that can't be automatically generated (for example: an prefix index in MySQL)
|
43
|
+
def self.ignore_index(index_name)
|
44
|
+
ignore_indexes << index_name.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Declares that a virtual field that has a rich type (e.g. created
|
50
|
+
# by attr_accessor :foo, :type => :email_address) should be subject
|
51
|
+
# to validation (note that the rich types know how to validate themselves)
|
52
|
+
def self.validate_virtual_field(*args)
|
53
|
+
validates_each(*args) {|record, field, value| msg = value.validate and record.errors.add(field, msg) if value.respond_to?(:validate) }
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# This adds a ":type => t" option to attr_accessor, where t is
|
58
|
+
# either a class or a symbolic name of a rich type. If this option
|
59
|
+
# is given, the setter will wrap values that are not of the right
|
60
|
+
# type.
|
61
|
+
def self.attr_accessor_with_rich_types(*attrs)
|
62
|
+
options = attrs.extract_options!
|
63
|
+
type = options.delete(:type)
|
64
|
+
attrs << options unless options.empty?
|
65
|
+
public
|
66
|
+
attr_accessor_without_rich_types(*attrs)
|
67
|
+
private
|
68
|
+
|
69
|
+
if type
|
70
|
+
type = HoboFields.to_class(type)
|
71
|
+
attrs.each do |attr|
|
72
|
+
declare_attr_type attr, type, options
|
73
|
+
type_wrapper = attr_type(attr)
|
74
|
+
define_method "#{attr}=" do |val|
|
75
|
+
if type_wrapper.not_in?(HoboFields::PLAIN_TYPES.values) && !val.is_a?(type) && HoboFields.can_wrap?(type, val)
|
76
|
+
val = type.new(val.to_s)
|
77
|
+
end
|
78
|
+
instance_variable_set("@#{attr}", val)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# Extend belongs_to so that it creates a FieldSpec for the foreign key
|
86
|
+
def self.belongs_to_with_field_declarations(name, options={}, &block)
|
87
|
+
column_options = {}
|
88
|
+
column_options[:null] = options.delete(:null) if options.has_key?(:null)
|
89
|
+
column_options[:comment] = options.delete(:comment) if options.has_key?(:comment)
|
90
|
+
|
91
|
+
index_options = {}
|
92
|
+
index_options[:name] = options.delete(:index) if options.has_key?(:index)
|
93
|
+
bt = belongs_to_without_field_declarations(name, options, &block)
|
94
|
+
refl = reflections[name.to_sym]
|
95
|
+
fkey = refl.primary_key_name
|
96
|
+
declare_field(fkey.to_sym, :integer, column_options)
|
97
|
+
if refl.options[:polymorphic]
|
98
|
+
declare_polymorphic_type_field(name, column_options)
|
99
|
+
index(["#{name}_type", fkey], index_options) if index_options[:name]!=false
|
100
|
+
else
|
101
|
+
index(fkey, index_options) if index_options[:name]!=false
|
102
|
+
end
|
103
|
+
bt
|
104
|
+
end
|
105
|
+
class << self
|
106
|
+
alias_method_chain :belongs_to, :field_declarations
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Declares the "foo_type" field that accompanies the "foo_id"
|
111
|
+
# field for a polyorphic belongs_to
|
112
|
+
def self.declare_polymorphic_type_field(name, column_options)
|
113
|
+
type_col = "#{name}_type"
|
114
|
+
declare_field(type_col, :string, column_options)
|
115
|
+
# FIXME: Before hobo_fields was extracted, this used to now do:
|
116
|
+
# never_show(type_col)
|
117
|
+
# That needs doing somewhere
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# Declare a rich-type for any attribute (i.e. getter method). This
|
122
|
+
# does not effect the attribute in any way - it just records the
|
123
|
+
# metadata.
|
124
|
+
def self.declare_attr_type(name, type, options={})
|
125
|
+
klass = HoboFields.to_class(type)
|
126
|
+
attr_types[name] = HoboFields.to_class(type)
|
127
|
+
klass.try.declared(self, name, options)
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# Declare named field with a type and an arbitrary set of
|
132
|
+
# arguments. The arguments are forwarded to the #field_added
|
133
|
+
# callback, allowing custom metadata to be added to field
|
134
|
+
# declarations.
|
135
|
+
def self.declare_field(name, type, *args)
|
136
|
+
options = args.extract_options!
|
137
|
+
try.field_added(name, type, args, options)
|
138
|
+
add_formatting_for_field(name, type, args)
|
139
|
+
add_validations_for_field(name, type, args)
|
140
|
+
add_index_for_field(name, args, options)
|
141
|
+
declare_attr_type(name, type, options) unless HoboFields.plain_type?(type)
|
142
|
+
field_specs[name] = HoboFields::Model::FieldSpec.new(self, name, type, options)
|
143
|
+
attr_order << name unless name.in?(attr_order)
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# Add field validations according to arguments in the
|
148
|
+
# field declaration
|
149
|
+
def self.add_validations_for_field(name, type, args)
|
150
|
+
validates_presence_of name if :required.in?(args)
|
151
|
+
validates_uniqueness_of name, :allow_nil => !:required.in?(args) if :unique.in?(args)
|
152
|
+
|
153
|
+
type_class = HoboFields.to_class(type)
|
154
|
+
if type_class && type_class.public_method_defined?("validate")
|
155
|
+
self.validate do |record|
|
156
|
+
v = record.send(name)._?.validate
|
157
|
+
record.errors.add(name, v) if v.is_a?(String)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.add_formatting_for_field(name, type, args)
|
163
|
+
type_class = HoboFields.to_class(type)
|
164
|
+
if type_class && "format".in?(type_class.instance_methods)
|
165
|
+
self.before_validation do |record|
|
166
|
+
record.send("#{name}=", record.send(name)._?.format)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.add_index_for_field(name, args, options)
|
172
|
+
to_name = options.delete(:index)
|
173
|
+
return unless to_name
|
174
|
+
index_opts = {}
|
175
|
+
index_opts[:unique] = :unique.in?(args) || options.delete(:unique)
|
176
|
+
# support :index => true declaration
|
177
|
+
index_opts[:name] = to_name unless to_name == true
|
178
|
+
index(name, index_opts)
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
# Extended version of the acts_as_list declaration that
|
183
|
+
# automatically delcares the 'position' field
|
184
|
+
def self.acts_as_list_with_field_declaration(options = {})
|
185
|
+
declare_field(options.fetch(:column, "position"), :integer)
|
186
|
+
acts_as_list_without_field_declaration(options)
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
# Returns the type (a class) for a given field or association. If
|
191
|
+
# the association is a collection (has_many or habtm) return the
|
192
|
+
# AssociationReflection instead
|
193
|
+
def self.attr_type(name)
|
194
|
+
if attr_types.nil? && self != self.name.constantize
|
195
|
+
raise RuntimeError, "attr_types called on a stale class object (#{self.name}). Avoid storing persistent references to classes"
|
196
|
+
end
|
197
|
+
|
198
|
+
attr_types[name] or
|
199
|
+
|
200
|
+
if (refl = reflections[name.to_sym])
|
201
|
+
if refl.macro.in?([:has_one, :belongs_to]) && !refl.options[:polymorphic]
|
202
|
+
refl.klass
|
203
|
+
else
|
204
|
+
refl
|
205
|
+
end
|
206
|
+
end or
|
207
|
+
|
208
|
+
(col = column(name.to_s) and HoboFields::PLAIN_TYPES[col.type] || col.klass)
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
# Return the entry from #columns for the named column
|
213
|
+
def self.column(name)
|
214
|
+
return unless (@table_exists ||= table_exists?)
|
215
|
+
name = name.to_s
|
216
|
+
columns.find {|c| c.name == name }
|
217
|
+
end
|
218
|
+
|
219
|
+
class << self
|
220
|
+
alias_method_chain :acts_as_list, :field_declaration if defined?(ActiveRecord::Acts::List)
|
221
|
+
alias_method_chain :attr_accessor, :rich_types
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'hobo_fields'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module HoboFields
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
|
7
|
+
ActiveSupport.on_load(:active_record) do
|
8
|
+
require 'hobo_fields/extensions/active_record/attribute_methods'
|
9
|
+
require 'hobo_fields/extensions/active_record/fields_declaration'
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module HoboFields
|
2
|
+
|
3
|
+
module SanitizeHtml
|
4
|
+
|
5
|
+
PERMITTED_TAGS = %w(a abbr acronym address b bdo big blockquote br caption center cite code colgroup dd del dfn dir
|
6
|
+
div dl dt em fieldset font h1 h2 h3 h4 h5 h6 i img ins kbd label legend li map menu ol optgroup
|
7
|
+
option p pre q s samp select small span strike strong sub sup table tbody td textarea tfoot
|
8
|
+
th thead tr tt u ul var)
|
9
|
+
|
10
|
+
PERMITTED_ATTRIBUTES = %w(href title class style align name src label target border)
|
11
|
+
|
12
|
+
class Helper
|
13
|
+
include ActionView::Helpers::SanitizeHelper
|
14
|
+
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.sanitize(s)
|
18
|
+
Helper.new.sanitize(s, :tags => PERMITTED_TAGS, :attributes => PERMITTED_ATTRIBUTES)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_support/core_ext/string/output_safety'
|
2
|
+
|
3
|
+
module HoboFields
|
4
|
+
module Types
|
5
|
+
class EmailAddress < String
|
6
|
+
|
7
|
+
COLUMN_TYPE = :string
|
8
|
+
|
9
|
+
def validate
|
10
|
+
I18n.t("errors.messages.invalid") unless valid? || blank?
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
self =~ /^\s*([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*$/i
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_html(xmldoctype = true)
|
18
|
+
ERB::Util.html_escape(self).sub('@', " at ").gsub('.', ' dot ')
|
19
|
+
end
|
20
|
+
|
21
|
+
HoboFields.register_type(:email_address, self)
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module HoboFields
|
2
|
+
module Types
|
3
|
+
class EnumString < String
|
4
|
+
|
5
|
+
module DeclarationHelper
|
6
|
+
|
7
|
+
def enum_string(*values)
|
8
|
+
EnumString.for(*values)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def with_values(*values)
|
16
|
+
@values = values.*.to_s
|
17
|
+
@translated_values = Hash.new do |hash, value|
|
18
|
+
if name.blank? || value.blank?
|
19
|
+
hash[value] = value
|
20
|
+
else
|
21
|
+
hash[value] = I18n.t("#{name.tableize}.#{value}", :default => value)
|
22
|
+
@detranslated_values[hash[value]] = value
|
23
|
+
hash[value]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
@detranslated_values = Hash.new do |hash, value|
|
28
|
+
if name.blank?
|
29
|
+
hash[value] = value
|
30
|
+
else
|
31
|
+
hash[value] = @values.detect(proc { value } ) {|v|
|
32
|
+
@translated_values[v]==value
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :values
|
39
|
+
|
40
|
+
attr_accessor :translated_values
|
41
|
+
|
42
|
+
attr_accessor :detranslated_values
|
43
|
+
|
44
|
+
def for(*values)
|
45
|
+
options = values.extract_options!
|
46
|
+
values = values.*.to_s
|
47
|
+
c = Class.new(EnumString) do
|
48
|
+
values.each do |v|
|
49
|
+
const_name = v.upcase.gsub(/[^a-z0-9_]/i, '_').gsub(/_+/, '_')
|
50
|
+
const_name = "V" + const_name if const_name =~ /^[0-9_]/ || const_name.blank?
|
51
|
+
const_set(const_name, self.new(v)) unless const_defined?(const_name)
|
52
|
+
|
53
|
+
method_name = "is_#{v.underscore}?"
|
54
|
+
define_method(method_name) { self == v } unless self.respond_to?(method_name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
c.with_values(*values)
|
58
|
+
c.set_name(options[:name]) if options[:name]
|
59
|
+
c
|
60
|
+
end
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
name.blank? ? "#<EnumString #{(values || []) * ' '}>" : name
|
64
|
+
end
|
65
|
+
alias_method :to_s, :inspect
|
66
|
+
|
67
|
+
def set_name(name)
|
68
|
+
@name = name
|
69
|
+
end
|
70
|
+
|
71
|
+
def name
|
72
|
+
@name || super
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
COLUMN_TYPE = :string
|
78
|
+
|
79
|
+
def initialize(value)
|
80
|
+
super(self.class.detranslated_values.nil? ? value : self.class.detranslated_values[value.to_s])
|
81
|
+
end
|
82
|
+
|
83
|
+
def validate
|
84
|
+
"must be one of #{self.class.values.map{|v| v.blank? ? '\'\'' : v} * ', '}" unless self.in?(self.class.values)
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_html(xmldoctype = true)
|
88
|
+
self.class.translated_values[self].html_safe
|
89
|
+
end
|
90
|
+
|
91
|
+
def ==(other)
|
92
|
+
if other.is_a?(Symbol)
|
93
|
+
super(other.to_s)
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module HoboFields
|
2
|
+
module Types
|
3
|
+
class HtmlString < RawHtmlString
|
4
|
+
|
5
|
+
include SanitizeHtml
|
6
|
+
|
7
|
+
def self.declared(model, name, options)
|
8
|
+
model.before_save { |record| record[name] = HoboFields::SanitizeHtml.sanitize(record[name]) }
|
9
|
+
end
|
10
|
+
|
11
|
+
HoboFields.register_type(:html, self)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module HoboFields
|
2
|
+
module Types
|
3
|
+
class LifecycleState < String
|
4
|
+
|
5
|
+
COLUMN_TYPE = :string
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :model_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_html(xmldoctype = true)
|
12
|
+
I18n.t("activerecord.attributes.#{self.class.model_name.underscore}.lifecycle.states.#{self}", :default => self).html_safe
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module HoboFields
|
2
|
+
module Types
|
3
|
+
class MarkdownString < RawMarkdownString
|
4
|
+
|
5
|
+
include SanitizeHtml
|
6
|
+
|
7
|
+
HoboFields.register_type(:markdown, self)
|
8
|
+
|
9
|
+
def to_html(xmldoctype = true)
|
10
|
+
blank? ? "" : HoboFields::SanitizeHtml.sanitize(Markdown.new(self).to_html)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module HoboFields
|
2
|
+
module Types
|
3
|
+
class PasswordString < String
|
4
|
+
|
5
|
+
COLUMN_TYPE = :string
|
6
|
+
|
7
|
+
HoboFields.register_type(:password, self)
|
8
|
+
|
9
|
+
def to_html(xmldoctype = true)
|
10
|
+
I18n.t("hobo.password_hidden", :default => "[password hidden]").html_safe
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module HoboFields
|
2
|
+
module Types
|
3
|
+
class SerializedObject < Object
|
4
|
+
|
5
|
+
COLUMN_TYPE = :text
|
6
|
+
|
7
|
+
def self.declared(model, name, options)
|
8
|
+
model.serialize name, options.delete(:class) || Object
|
9
|
+
end
|
10
|
+
|
11
|
+
HoboFields.register_type(:serialized, self)
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'active_support/core_ext/string/output_safety'
|
2
|
+
module HoboFields
|
3
|
+
module Types
|
4
|
+
class Text < String
|
5
|
+
|
6
|
+
COLUMN_TYPE = :text
|
7
|
+
|
8
|
+
def to_html(xmldoctype = true)
|
9
|
+
ERB::Util.html_escape(self).gsub("\n", "<br#{xmldoctype ? ' /' : ''}>\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
HoboFields.register_type(:text, self)
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|