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.
Files changed (43) hide show
  1. data/CHANGES.txt +38 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.txt +8 -0
  4. data/Rakefile +36 -0
  5. data/VERSION +1 -0
  6. data/bin/hobofields +19 -0
  7. data/hobo_fields.gemspec +31 -0
  8. data/lib/generators/hobo/migration/USAGE +47 -0
  9. data/lib/generators/hobo/migration/migration_generator.rb +162 -0
  10. data/lib/generators/hobo/migration/migrator.rb +445 -0
  11. data/lib/generators/hobo/migration/templates/migration.rb.erb +9 -0
  12. data/lib/generators/hobo/model/USAGE +19 -0
  13. data/lib/generators/hobo/model/model_generator.rb +11 -0
  14. data/lib/generators/hobo/model/templates/model_injection.rb.erb +18 -0
  15. data/lib/hobo_fields/extensions/active_record/attribute_methods.rb +48 -0
  16. data/lib/hobo_fields/extensions/active_record/fields_declaration.rb +21 -0
  17. data/lib/hobo_fields/field_declaration_dsl.rb +33 -0
  18. data/lib/hobo_fields/model/field_spec.rb +121 -0
  19. data/lib/hobo_fields/model/index_spec.rb +47 -0
  20. data/lib/hobo_fields/model.rb +226 -0
  21. data/lib/hobo_fields/railtie.rb +13 -0
  22. data/lib/hobo_fields/sanitize_html.rb +23 -0
  23. data/lib/hobo_fields/types/email_address.rb +26 -0
  24. data/lib/hobo_fields/types/enum_string.rb +101 -0
  25. data/lib/hobo_fields/types/html_string.rb +15 -0
  26. data/lib/hobo_fields/types/lifecycle_state.rb +16 -0
  27. data/lib/hobo_fields/types/markdown_string.rb +15 -0
  28. data/lib/hobo_fields/types/password_string.rb +15 -0
  29. data/lib/hobo_fields/types/raw_html_string.rb +13 -0
  30. data/lib/hobo_fields/types/raw_markdown_string.rb +13 -0
  31. data/lib/hobo_fields/types/serialized_object.rb +15 -0
  32. data/lib/hobo_fields/types/text.rb +16 -0
  33. data/lib/hobo_fields/types/textile_string.rb +22 -0
  34. data/lib/hobo_fields.rb +94 -0
  35. data/test/api.rdoctest +244 -0
  36. data/test/doc-only.rdoctest +96 -0
  37. data/test/generators.rdoctest +53 -0
  38. data/test/interactive_primary_key.rdoctest +54 -0
  39. data/test/migration_generator.rdoctest +639 -0
  40. data/test/migration_generator_comments.rdoctest +75 -0
  41. data/test/prepare_testapp.rb +8 -0
  42. data/test/rich_types.rdoctest +394 -0
  43. 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,13 @@
1
+ module HoboFields
2
+ module Types
3
+ class RawHtmlString < HoboFields::Types::Text
4
+
5
+ def to_html(xmldoctype = true)
6
+ self.html_safe
7
+ end
8
+
9
+ HoboFields.register_type(:raw_html, self)
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module HoboFields
2
+ module Types
3
+ class RawMarkdownString < HoboFields::Types::Text
4
+
5
+ HoboFields.register_type(:raw_markdown, self)
6
+
7
+ def to_html(xmldoctype = true)
8
+ blank? ? "" : Markdown.new(self).to_html.html_safe
9
+ end
10
+
11
+ end
12
+ end
13
+ 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