hobofields 0.7.5
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/Manifest +25 -0
- data/README.txt +8 -0
- data/generators/hobo_migration/hobo_migration_generator.rb +95 -0
- data/generators/hobo_migration/templates/migration.rb +9 -0
- data/hobofields.gemspec +42 -0
- data/init.rb +4 -0
- data/lib/hobo_fields/email_address.rb +24 -0
- data/lib/hobo_fields/enum_string.rb +64 -0
- data/lib/hobo_fields/field_declaration_dsl.rb +29 -0
- data/lib/hobo_fields/field_spec.rb +72 -0
- data/lib/hobo_fields/fields_declaration.rb +22 -0
- data/lib/hobo_fields/html_string.rb +15 -0
- data/lib/hobo_fields/markdown_string.rb +13 -0
- data/lib/hobo_fields/migration_generator.rb +293 -0
- data/lib/hobo_fields/model_extensions.rb +172 -0
- data/lib/hobo_fields/password_string.rb +15 -0
- data/lib/hobo_fields/text.rb +17 -0
- data/lib/hobo_fields/textile_string.rb +29 -0
- data/lib/hobo_fields.rb +139 -0
- data/lib/hobofields.rb +1 -0
- data/test/hobofields.rdoctest +57 -0
- data/test/hobofields_api.rdoctest +247 -0
- data/test/migration_generator.rdoctest +410 -0
- data/test/rich_types.rdoctest +215 -0
- metadata +98 -0
data/CHANGES.txt
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
== HoboFields 0.7.4 ==
|
2
|
+
|
3
|
+
Fix to using the migration generator with STI
|
4
|
+
|
5
|
+
HoboFields::Text - fix to to_html
|
6
|
+
|
7
|
+
Ensure that the bundled rich types are only loaded on demand
|
8
|
+
|
9
|
+
HoboFields::TextileString -- adding require 'redcloth'
|
10
|
+
|
11
|
+
Fix for HoboFields::EnumString -- couldn't set values using
|
12
|
+
symbols
|
13
|
+
|
14
|
+
Adding to_html to PasswordString (returns '[password-hidden]')
|
15
|
+
|
16
|
+
EnumString -- now defines constants on the class for each value in
|
17
|
+
the enum
|
18
|
+
|
19
|
+
Also, won't define an is_foo? method if that already exists
|
20
|
+
|
21
|
+
Removing HoboFields::Percentage -- this is better handled as an
|
22
|
+
application specific type
|
23
|
+
|
24
|
+
For example, is a percentage an fixnum or a float? Is 101% valid
|
25
|
+
or invalid?
|
26
|
+
|
27
|
+
Fix to validates_virtual_field
|
28
|
+
|
29
|
+
*Breaking Change* EnumString: Change to automatically defined
|
30
|
+
class methods on EnumString classes
|
31
|
+
|
32
|
+
It used to be that if you did EnumString.for(:foo, :baa), your
|
33
|
+
class had methods foo, baa and the instance got methods foo?
|
34
|
+
and baa?. There was a big problem with name clashes with other
|
35
|
+
class methods. You now get no class methods, and the instance
|
36
|
+
methods are called is_foo? and is_baa?.
|
37
|
+
|
38
|
+
Rich field type fix: don't attempt to wrap booleans
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2008 Tom Locke
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
CHANGES.txt
|
2
|
+
generators/hobo_migration/hobo_migration_generator.rb
|
3
|
+
generators/hobo_migration/templates/migration.rb
|
4
|
+
init.rb
|
5
|
+
lib/hobo_fields/email_address.rb
|
6
|
+
lib/hobo_fields/enum_string.rb
|
7
|
+
lib/hobo_fields/field_declaration_dsl.rb
|
8
|
+
lib/hobo_fields/field_spec.rb
|
9
|
+
lib/hobo_fields/fields_declaration.rb
|
10
|
+
lib/hobo_fields/html_string.rb
|
11
|
+
lib/hobo_fields/markdown_string.rb
|
12
|
+
lib/hobo_fields/migration_generator.rb
|
13
|
+
lib/hobo_fields/model_extensions.rb
|
14
|
+
lib/hobo_fields/password_string.rb
|
15
|
+
lib/hobo_fields/text.rb
|
16
|
+
lib/hobo_fields/textile_string.rb
|
17
|
+
lib/hobo_fields.rb
|
18
|
+
lib/hobofields.rb
|
19
|
+
LICENSE.txt
|
20
|
+
README.txt
|
21
|
+
test/hobofields.rdoctest
|
22
|
+
test/hobofields_api.rdoctest
|
23
|
+
test/migration_generator.rdoctest
|
24
|
+
test/rich_types.rdoctest
|
25
|
+
Manifest
|
data/README.txt
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
class HoboMigrationGenerator < Rails::Generator::Base
|
2
|
+
|
3
|
+
def initialize(runtime_args, runtime_options = {})
|
4
|
+
super
|
5
|
+
@migration_name = runtime_args.first || begin
|
6
|
+
i = Dir["#{RAILS_ROOT}/db/migrate/*hobo_migration*"].length
|
7
|
+
"hobo_migration_#{i+1}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def manifest
|
12
|
+
generator = HoboFields::MigrationGenerator.new(self)
|
13
|
+
up, down = generator.generate
|
14
|
+
|
15
|
+
if up.blank?
|
16
|
+
puts "Database and models match -- nothing to change"
|
17
|
+
return record {|m| }
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "\n---------- Up Migration ----------", up, "----------------------------------"
|
21
|
+
puts "\n---------- Down Migration --------", down, "----------------------------------"
|
22
|
+
|
23
|
+
action = input("What now: [g]enerate migration, generate and [m]igrate now or [c]ancel?", %w(g m c))
|
24
|
+
|
25
|
+
if action == 'c'
|
26
|
+
# record nothing to keep the generator happy
|
27
|
+
record {|m| }
|
28
|
+
else
|
29
|
+
puts "\nMigration filename:", "(you can type spaces instead of '_' -- every little helps)"
|
30
|
+
migration_name = input("Filename [#@migration_name]:").strip.gsub(' ', '_')
|
31
|
+
migration_name = @migration_name if migration_name.blank?
|
32
|
+
|
33
|
+
at_exit { system "rake db:migrate" } if action == 'm'
|
34
|
+
|
35
|
+
up.gsub!("\n", "\n ")
|
36
|
+
down.gsub!("\n", "\n ")
|
37
|
+
|
38
|
+
record do |m|
|
39
|
+
m.migration_template 'migration.rb', 'db/migrate',
|
40
|
+
:assigns => { :up => up, :down => down, :migration_name => migration_name.camelize },
|
41
|
+
:migration_file_name => migration_name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rescue HoboFields::FieldSpec::UnknownSqlTypeError => e
|
45
|
+
puts "Invalid field type '#{e.message[2]}' for #{e.message[0]}.#{e.message[1]}"
|
46
|
+
record {|m| }
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def extract_renames!(to_create, to_drop, kind_str, name_prefix="")
|
51
|
+
to_rename = {}
|
52
|
+
rename_to_choices = to_create
|
53
|
+
to_drop.dup.each do |t|
|
54
|
+
if rename_to_choices.empty?
|
55
|
+
puts "\nCONFIRM DROP! #{kind_str} #{name_prefix}#{t}"
|
56
|
+
resp = input("Enter 'drop #{t}' to confirm:")
|
57
|
+
if resp.strip != "drop " + t.to_s
|
58
|
+
to_drop.delete(t)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
puts "\nDROP or RENAME?: #{kind_str} #{name_prefix}#{t}"
|
62
|
+
puts "Rename choices: #{to_create * ', '}"
|
63
|
+
resp = input("Enter either 'drop #{t}' or one of the rename choices:")
|
64
|
+
resp.strip!
|
65
|
+
|
66
|
+
if resp == "drop " + t
|
67
|
+
# Leave things as they are
|
68
|
+
else
|
69
|
+
to_drop.delete(t)
|
70
|
+
if resp.in?(rename_to_choices)
|
71
|
+
to_rename[t] = resp
|
72
|
+
to_create.delete(resp)
|
73
|
+
rename_to_choices.delete(resp)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
to_rename
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def input(prompt, options=nil)
|
83
|
+
print(prompt + " ")
|
84
|
+
if options
|
85
|
+
while !(response = STDIN.readline.strip.downcase).in?(options);
|
86
|
+
print(prompt + " ")
|
87
|
+
end
|
88
|
+
response
|
89
|
+
else
|
90
|
+
STDIN.readline
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
data/hobofields.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
# Gem::Specification for Hobofields-0.7.5
|
3
|
+
# Originally generated by Echoe
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{hobofields}
|
7
|
+
s.version = "0.7.5"
|
8
|
+
|
9
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.authors = ["Tom Locke"]
|
13
|
+
s.date = %q{2008-04-18}
|
14
|
+
s.description = %q{Rich field types and migration generator for Rails}
|
15
|
+
s.email = %q{tom@tomlocke.com}
|
16
|
+
s.extra_rdoc_files = ["lib/hobo_fields/email_address.rb", "lib/hobo_fields/enum_string.rb", "lib/hobo_fields/field_declaration_dsl.rb", "lib/hobo_fields/field_spec.rb", "lib/hobo_fields/fields_declaration.rb", "lib/hobo_fields/html_string.rb", "lib/hobo_fields/markdown_string.rb", "lib/hobo_fields/migration_generator.rb", "lib/hobo_fields/model_extensions.rb", "lib/hobo_fields/password_string.rb", "lib/hobo_fields/text.rb", "lib/hobo_fields/textile_string.rb", "lib/hobo_fields.rb", "lib/hobofields.rb", "LICENSE.txt", "README.txt"]
|
17
|
+
s.files = ["CHANGES.txt", "generators/hobo_migration/hobo_migration_generator.rb", "generators/hobo_migration/templates/migration.rb", "init.rb", "lib/hobo_fields/email_address.rb", "lib/hobo_fields/enum_string.rb", "lib/hobo_fields/field_declaration_dsl.rb", "lib/hobo_fields/field_spec.rb", "lib/hobo_fields/fields_declaration.rb", "lib/hobo_fields/html_string.rb", "lib/hobo_fields/markdown_string.rb", "lib/hobo_fields/migration_generator.rb", "lib/hobo_fields/model_extensions.rb", "lib/hobo_fields/password_string.rb", "lib/hobo_fields/text.rb", "lib/hobo_fields/textile_string.rb", "lib/hobo_fields.rb", "lib/hobofields.rb", "LICENSE.txt", "README.txt", "test/hobofields.rdoctest", "test/hobofields_api.rdoctest", "test/migration_generator.rdoctest", "test/rich_types.rdoctest", "Manifest", "hobofields.gemspec"]
|
18
|
+
s.has_rdoc = true
|
19
|
+
s.homepage = %q{http://hobocentral.net/hobofields}
|
20
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Hobofields", "--main", "README.txt"]
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
s.rubyforge_project = %q{hobo}
|
23
|
+
s.rubygems_version = %q{1.0.1}
|
24
|
+
s.summary = %q{Rich field types and migration generator for Rails}
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# # Original Rakefile source (requires the Echoe gem):
|
29
|
+
#
|
30
|
+
# require 'echoe'
|
31
|
+
#
|
32
|
+
# Echoe.new('hobofields') do |p|
|
33
|
+
# p.author = "Tom Locke"
|
34
|
+
# p.email = "tom@tomlocke.com"
|
35
|
+
# p.summary = "Rich field types and migration generator for Rails"
|
36
|
+
# p.url = "http://hobocentral.net/hobofields"
|
37
|
+
# p.project = "hobo"
|
38
|
+
#
|
39
|
+
# p.changelog = "CHANGES.txt"
|
40
|
+
# p.version = "0.7.5"
|
41
|
+
# end
|
42
|
+
#
|
data/init.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module HoboFields
|
2
|
+
|
3
|
+
class EmailAddress < String
|
4
|
+
|
5
|
+
COLUMN_TYPE = :string
|
6
|
+
|
7
|
+
def validate
|
8
|
+
"is not valid" unless valid? || blank?
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
self =~ /^\s*([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*$/i
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_html
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
HoboFields.register_type(:email_address, self)
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'hobo_fields/field_declaration_dsl'
|
2
|
+
|
3
|
+
module HoboFields
|
4
|
+
|
5
|
+
class EnumString < String
|
6
|
+
|
7
|
+
module DeclarationHelper
|
8
|
+
|
9
|
+
def enum_string(*values)
|
10
|
+
EnumString.for(*values)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
FieldDeclarationDsl.send(:include, DeclarationHelper)
|
16
|
+
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
def with_values(*values)
|
21
|
+
@values = values.*.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_accessor :values
|
25
|
+
|
26
|
+
def for(*values)
|
27
|
+
values = values.*.to_s
|
28
|
+
c = Class.new(EnumString) do
|
29
|
+
values.each do |v|
|
30
|
+
const_name = v.upcase
|
31
|
+
const_set(const_name, self.new(v)) unless const_defined?(const_name)
|
32
|
+
|
33
|
+
method_name = "is_#{v.underscore}?"
|
34
|
+
define_method(method_name) { self == v } unless self.respond_to?(method_name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
c.with_values(*values)
|
38
|
+
c
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
name.blank? ? "#<EnumString #{(values || []) * ' '}>" : name
|
43
|
+
end
|
44
|
+
alias_method :to_s, :inspect
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
COLUMN_TYPE = :string
|
49
|
+
|
50
|
+
def validate
|
51
|
+
"must be one of #{self.class.values * ', '}" unless self.in?(self.class.values)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
if other.is_a?(Symbol)
|
56
|
+
super(other.to_s)
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module HoboFields
|
2
|
+
|
3
|
+
class FieldDeclarationDsl < BlankSlate
|
4
|
+
|
5
|
+
def initialize(model)
|
6
|
+
@model = model
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :model
|
10
|
+
|
11
|
+
|
12
|
+
def timestamps
|
13
|
+
field(:created_at, :datetime)
|
14
|
+
field(:updated_at, :datetime)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def field(name, type, *args)
|
19
|
+
@model.declare_field(name, type, *args)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def method_missing(name, *args)
|
24
|
+
field(name, *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module HoboFields
|
2
|
+
|
3
|
+
class FieldSpec
|
4
|
+
|
5
|
+
class UnknownSqlTypeError < RuntimeError; end
|
6
|
+
|
7
|
+
def initialize(model, name, type, options={})
|
8
|
+
raise ArgumentError, "you cannot provide a field spec for the primary key" if name == model.primary_key
|
9
|
+
self.model = model
|
10
|
+
self.name = name.to_sym
|
11
|
+
self.type = type.is_a?(String) ? type.to_sym : type
|
12
|
+
self.options = options
|
13
|
+
self.position = model.field_specs.length
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :model, :name, :type, :position, :options
|
17
|
+
|
18
|
+
def sql_type
|
19
|
+
options[:sql_type] or begin
|
20
|
+
if native_type?(type)
|
21
|
+
type
|
22
|
+
else
|
23
|
+
field_class = HoboFields.to_class(type)
|
24
|
+
field_class && field_class::COLUMN_TYPE or raise UnknownSqlTypeError, "#{model}.#{name}::#{type}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def limit
|
30
|
+
options[:limit] || native_types[sql_type][:limit]
|
31
|
+
end
|
32
|
+
|
33
|
+
def precision
|
34
|
+
options[:precision]
|
35
|
+
end
|
36
|
+
|
37
|
+
def scale
|
38
|
+
options[:scale]
|
39
|
+
end
|
40
|
+
|
41
|
+
def null
|
42
|
+
:null.in?(options) ? options[:null] : true
|
43
|
+
end
|
44
|
+
|
45
|
+
def default
|
46
|
+
options[:default]
|
47
|
+
end
|
48
|
+
|
49
|
+
def different_to?(col_spec)
|
50
|
+
sql_type != col_spec.type ||
|
51
|
+
begin
|
52
|
+
check_cols = [:null, :default]
|
53
|
+
check_cols += [:precision, :scale] if sql_type == :decimal
|
54
|
+
check_cols << :limit if sql_type.in?([:string, :text, :binary, :integer])
|
55
|
+
check_cols.any? { |k| col_spec.send(k) != self.send(k) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def native_type?(type)
|
63
|
+
type.in?(native_types.keys - [:primary_key])
|
64
|
+
end
|
65
|
+
|
66
|
+
def native_types
|
67
|
+
model.connection.native_database_types
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module HoboFields
|
2
|
+
|
3
|
+
FieldsDeclaration = classy_module do
|
4
|
+
|
5
|
+
def self.fields(&b)
|
6
|
+
# Any model that calls 'fields' gets a bunch of other
|
7
|
+
# functionality included automatically, but make sure we only include it once
|
8
|
+
include HoboFields::ModelExtensions unless HoboFields::ModelExtensions.in?(included_modules)
|
9
|
+
|
10
|
+
if b
|
11
|
+
dsl = FieldDeclarationDsl.new(self)
|
12
|
+
if b.arity == 1
|
13
|
+
yield dsl
|
14
|
+
else
|
15
|
+
dsl.instance_eval(&b)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|