stringex 1.5.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +74 -0
- data/README.rdoc +22 -1
- data/Rakefile +46 -223
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/stringex.rb +11 -3
- data/lib/stringex/acts_as_url.rb +49 -97
- data/lib/stringex/acts_as_url/adapter.rb +26 -0
- data/lib/stringex/acts_as_url/adapter/active_record.rb +23 -0
- data/lib/stringex/acts_as_url/adapter/base.rb +188 -0
- data/lib/stringex/acts_as_url/adapter/data_mapper.rb +67 -0
- data/lib/stringex/acts_as_url/adapter/mongoid.rb +36 -0
- data/lib/stringex/configuration.rb +4 -0
- data/lib/stringex/configuration/acts_as_url.rb +44 -0
- data/lib/stringex/configuration/base.rb +58 -0
- data/lib/stringex/configuration/configurator.rb +25 -0
- data/lib/stringex/configuration/string_extensions.rb +19 -0
- data/lib/stringex/localization.rb +98 -0
- data/lib/stringex/localization/backend/i18n.rb +53 -0
- data/lib/stringex/localization/backend/internal.rb +51 -0
- data/lib/stringex/localization/conversion_expressions.rb +148 -0
- data/lib/stringex/localization/converter.rb +121 -0
- data/lib/stringex/localization/default_conversions.rb +88 -0
- data/lib/stringex/rails/railtie.rb +10 -0
- data/lib/stringex/string_extensions.rb +153 -208
- data/lib/stringex/unidecoder.rb +6 -101
- data/lib/stringex/unidecoder_data/x00.yml +1 -1
- data/lib/stringex/unidecoder_data/x02.yml +5 -5
- data/lib/stringex/unidecoder_data/x05.yml +1 -1
- data/lib/stringex/unidecoder_data/x06.yml +1 -1
- data/lib/stringex/unidecoder_data/x07.yml +3 -3
- data/lib/stringex/unidecoder_data/x09.yml +1 -1
- data/lib/stringex/unidecoder_data/x0e.yml +2 -2
- data/lib/stringex/unidecoder_data/x1f.yml +2 -2
- data/lib/stringex/unidecoder_data/x20.yml +1 -1
- data/lib/stringex/unidecoder_data/xfb.yml +1 -1
- data/lib/stringex/unidecoder_data/xff.yml +1 -1
- data/lib/stringex/version.rb +8 -0
- data/locales/da.yml +73 -0
- data/locales/en.yml +66 -0
- data/stringex.gemspec +77 -18
- data/test/acts_as_url/adapter/active_record.rb +72 -0
- data/test/acts_as_url/adapter/data_mapper.rb +82 -0
- data/test/acts_as_url/adapter/mongoid.rb +73 -0
- data/test/acts_as_url_configuration_test.rb +51 -0
- data/test/acts_as_url_integration_test.rb +271 -0
- data/test/localization/da_test.rb +117 -0
- data/test/localization/default_test.rb +113 -0
- data/test/localization/en_test.rb +117 -0
- data/test/localization_test.rb +123 -0
- data/test/redcloth_to_html_test.rb +37 -0
- data/test/string_extensions_test.rb +59 -91
- data/test/test_helper.rb +2 -0
- data/test/unicode_point_suite/basic_greek_test.rb +113 -0
- data/test/unicode_point_suite/basic_latin_test.rb +142 -0
- data/test/unicode_point_suite/codepoint_test_helper.rb +32 -0
- data/test/unidecoder/bad_localization.yml +1 -0
- data/test/unidecoder/localization.yml +4 -0
- data/test/unidecoder_test.rb +3 -5
- metadata +145 -37
- data/test/acts_as_url_test.rb +0 -272
data/lib/stringex.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'stringex/configuration'
|
5
|
+
require 'stringex/localization'
|
2
6
|
require 'stringex/string_extensions'
|
3
7
|
require 'stringex/unidecoder'
|
8
|
+
require 'stringex/acts_as_url'
|
4
9
|
|
5
|
-
String.send :include, Stringex::StringExtensions
|
10
|
+
String.send :include, Stringex::StringExtensions::PublicInstanceMethods
|
11
|
+
String.send :extend, Stringex::StringExtensions::PublicClassMethods
|
6
12
|
|
7
|
-
|
13
|
+
Stringex::ActsAsUrl::Adapter.load_available
|
8
14
|
|
9
|
-
|
15
|
+
if defined?(Rails::Railtie)
|
16
|
+
require 'stringex/rails/railtie'
|
17
|
+
end
|
data/lib/stringex/acts_as_url.rb
CHANGED
@@ -1,66 +1,17 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
require "stringex/acts_as_url/adapter"
|
3
|
+
|
2
4
|
module Stringex
|
3
5
|
module ActsAsUrl # :nodoc:
|
4
|
-
def self.
|
5
|
-
|
6
|
+
def self.configure(&block)
|
7
|
+
Stringex::Configuration::ActsAsUrl.configure &block
|
6
8
|
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
:exclude, :only_when_blank, :scope_for_url, :sync_url, :url_attribute, :url_limit
|
11
|
-
|
12
|
-
def initialize(klass, options = {})
|
13
|
-
self.allow_slash = options[:allow_slash]
|
14
|
-
self.allow_duplicates = options[:allow_duplicates]
|
15
|
-
self.attribute_to_urlify = options[:attribute]
|
16
|
-
self.duplicate_count_separator = options[:duplicate_count_separator] || "-"
|
17
|
-
self.exclude = options[:exclude] || []
|
18
|
-
self.only_when_blank = options[:only_when_blank]
|
19
|
-
self.scope_for_url = options[:scope]
|
20
|
-
self.sync_url = options[:sync_url]
|
21
|
-
self.url_attribute = options[:url_attribute] || "url"
|
22
|
-
self.url_limit = options[:limit]
|
23
|
-
end
|
24
|
-
|
25
|
-
def get_base_url!(instance)
|
26
|
-
base_url = instance.send(url_attribute)
|
27
|
-
if base_url.blank? || !only_when_blank
|
28
|
-
root = instance.send(attribute_to_urlify).to_s
|
29
|
-
base_url = root.to_url(:allow_slash => allow_slash, :limit => url_limit, :exclude => exclude)
|
30
|
-
end
|
31
|
-
instance.instance_variable_set "@acts_as_url_base_url", base_url
|
32
|
-
end
|
33
|
-
|
34
|
-
def get_conditions!(instance)
|
35
|
-
conditions = ["#{url_attribute} LIKE ?", instance.instance_variable_get("@acts_as_url_base_url") + '%']
|
36
|
-
unless instance.new_record?
|
37
|
-
conditions.first << " and id != ?"
|
38
|
-
conditions << instance.id
|
39
|
-
end
|
40
|
-
if scope_for_url
|
41
|
-
conditions.first << " and #{scope_for_url} = ?"
|
42
|
-
conditions << instance.send(scope_for_url)
|
43
|
-
end
|
44
|
-
conditions
|
45
|
-
end
|
46
|
-
|
47
|
-
def handle_duplicate_urls!(instance)
|
48
|
-
return if allow_duplicates
|
49
|
-
|
50
|
-
base_url = instance.instance_variable_get("@acts_as_url_base_url")
|
51
|
-
url_owners = instance.class.unscoped.find(:all, :conditions => get_conditions!(instance))
|
52
|
-
if url_owners.any?{|owner| owner.send(url_attribute) == base_url}
|
53
|
-
separator = duplicate_count_separator
|
54
|
-
n = 1
|
55
|
-
while url_owners.any?{|owner| owner.send(url_attribute) == "#{base_url}#{separator}#{n}"}
|
56
|
-
n = n.succ
|
57
|
-
end
|
58
|
-
instance.send :write_attribute, url_attribute, "#{base_url}#{separator}#{n}"
|
59
|
-
end
|
60
|
-
end
|
10
|
+
def self.unconfigure!
|
11
|
+
Stringex::Configuration::ActsAsUrl.unconfigure!
|
61
12
|
end
|
62
13
|
|
63
|
-
module
|
14
|
+
module ActsAsUrlClassMethods # :doc:
|
64
15
|
# Creates a callback to automatically create an url-friendly representation
|
65
16
|
# of the <tt>attribute</tt> argument. Example:
|
66
17
|
#
|
@@ -74,48 +25,58 @@ module Stringex
|
|
74
25
|
# The default attribute <tt>acts_as_url</tt> uses to save the permalink is <tt>url</tt>
|
75
26
|
# but this can be changed in the options hash. Available options are:
|
76
27
|
#
|
77
|
-
# <tt>:
|
78
|
-
#
|
79
|
-
#
|
28
|
+
# <tt>:adapter</tt>:: If specified, will indicate what ORM adapter to use. Default functionality
|
29
|
+
# is to use the first available adapter. This should work for most cases
|
30
|
+
# unless you are using multiple ORMs in a single project.
|
31
|
+
# <tt>:allow_slash</tt>:: If true, allows the generated url to contain slashes. Default is false[y].
|
32
|
+
# <tt>:allow_duplicates</tt>:: If true, allows duplicate urls instead of appending numbers to
|
33
|
+
# differentiate between urls. Default is false[y]. See note on <tt>:scope</tt>.
|
80
34
|
# <tt>:duplicate_count_separator</tt>:: String to use when forcing unique urls from non-unique strings.
|
81
35
|
# Default is "-".
|
36
|
+
# <tt>:force_downcase</tt>:: If false, allows generated url to contain uppercased letters. Default is false.
|
82
37
|
# <tt>:exclude_list</tt>:: List of complete strings that should not be transformed by <tt>acts_as_url</tt>.
|
83
38
|
# Default is empty.
|
84
39
|
# <tt>:only_when_blank</tt>:: If true, the url generation will only happen when <tt>:url_attribute</tt> is
|
85
40
|
# blank. Default is false[y] (meaning url generation will happen always).
|
86
41
|
# <tt>:scope</tt>:: The name of model attribute to scope unique urls to. There is no default here.
|
42
|
+
# <strong>Note:</strong> this will automatically act as if <tt>:allow_duplicates</tt>
|
43
|
+
# is set to true.
|
87
44
|
# <tt>:sync_url</tt>:: If set to true, the url field will be updated when changes are made to the
|
88
|
-
# attribute it is based on. Default is false
|
45
|
+
# attribute it is based on. Default is false.
|
89
46
|
# <tt>:url_attribute</tt>:: The name of the attribute to use for storing the generated url string.
|
90
47
|
# Default is <tt>:url</tt>.
|
91
|
-
# <tt>:
|
92
|
-
#
|
93
|
-
#
|
48
|
+
# <tt>:limit</tt>:: The maximum size a generated url should be. <strong>Note:</strong> this does not
|
49
|
+
# include the characters needed to enforce uniqueness on duplicate urls.
|
50
|
+
# Default is nil.
|
94
51
|
def acts_as_url(attribute, options = {})
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
self.acts_as_url_configuration = ActsAsUrl::Configuration.new(self, options)
|
99
|
-
|
100
|
-
if acts_as_url_configuration.sync_url
|
101
|
-
before_validation(:ensure_unique_url)
|
102
|
-
else
|
103
|
-
if defined?(ActiveModel::Callbacks)
|
104
|
-
before_validation(:ensure_unique_url, :on => :create)
|
105
|
-
else
|
106
|
-
before_validation_on_create(:ensure_unique_url)
|
52
|
+
class_eval do
|
53
|
+
class << self
|
54
|
+
attr_accessor :acts_as_url_configuration
|
107
55
|
end
|
108
|
-
end
|
109
56
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
else
|
115
|
-
read_attribute(acts_as_url_configuration.url_attribute)
|
57
|
+
define_method :acts_as_url_configuration do
|
58
|
+
klass = self.class
|
59
|
+
while klass.acts_as_url_configuration.nil?
|
60
|
+
klass = klass.superclass
|
116
61
|
end
|
62
|
+
klass.acts_as_url_configuration
|
117
63
|
end
|
118
|
-
|
64
|
+
end
|
65
|
+
|
66
|
+
options[:attribute_to_urlify] = attribute
|
67
|
+
self.acts_as_url_configuration = Stringex::Configuration::ActsAsUrl.new(options)
|
68
|
+
|
69
|
+
acts_as_url_configuration.adapter.create_callbacks! self
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# Some ORMs function as mixins not base classes and need to have a hook to reinclude
|
74
|
+
# and re-extend ActsAsUrl methods
|
75
|
+
def included(base)
|
76
|
+
super
|
77
|
+
|
78
|
+
base.send :include, Stringex::ActsAsUrl::ActsAsUrlInstanceMethods
|
79
|
+
base.send :extend, Stringex::ActsAsUrl::ActsAsUrlClassMethods
|
119
80
|
end
|
120
81
|
|
121
82
|
# Initialize the url fields for the records that need it. Designed for people who add
|
@@ -126,23 +87,14 @@ module Stringex
|
|
126
87
|
# on a large selection, you will get much better results writing your own version with
|
127
88
|
# using pagination.
|
128
89
|
def initialize_urls
|
129
|
-
|
130
|
-
instance.send :ensure_unique_url
|
131
|
-
instance.save
|
132
|
-
end
|
90
|
+
acts_as_url_configuration.adapter.initialize_urls! self
|
133
91
|
end
|
134
92
|
end
|
135
93
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
config = acts_as_url_configuration
|
141
|
-
url_attribute = config.url_attribute
|
142
|
-
|
143
|
-
config.get_base_url! self
|
144
|
-
write_attribute url_attribute, @acts_as_url_base_url
|
145
|
-
config.handle_duplicate_urls!(self) unless config.allow_duplicates
|
94
|
+
module ActsAsUrlInstanceMethods
|
95
|
+
def ensure_unique_url
|
96
|
+
acts_as_url_configuration.adapter.ensure_unique_url! self
|
97
|
+
end
|
146
98
|
end
|
147
99
|
end
|
148
100
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "stringex/acts_as_url/adapter/base"
|
2
|
+
require "stringex/acts_as_url/adapter/active_record"
|
3
|
+
require "stringex/acts_as_url/adapter/data_mapper"
|
4
|
+
require "stringex/acts_as_url/adapter/mongoid"
|
5
|
+
|
6
|
+
module Stringex
|
7
|
+
module ActsAsUrl
|
8
|
+
module Adapter
|
9
|
+
def self.add_loaded_adapter(adapter)
|
10
|
+
@loaded_adapters << adapter
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load_available
|
14
|
+
@loaded_adapters = []
|
15
|
+
constants.each do |name|
|
16
|
+
adapter = const_get(name)
|
17
|
+
adapter.load if adapter.loadable?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.first_available
|
22
|
+
@loaded_adapters[0]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Stringex
|
2
|
+
module ActsAsUrl
|
3
|
+
module Adapter
|
4
|
+
class ActiveRecord < Base
|
5
|
+
def self.load
|
6
|
+
ensure_loadable
|
7
|
+
orm_class.send :include, ActsAsUrlInstanceMethods
|
8
|
+
orm_class.send :extend, ActsAsUrlClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def klass_previous_instances(&block)
|
14
|
+
klass.find_each(:conditions => {settings.url_attribute => nil}, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.orm_class
|
18
|
+
::ActiveRecord::Base
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Stringex
|
2
|
+
module ActsAsUrl
|
3
|
+
module Adapter
|
4
|
+
class Base
|
5
|
+
attr_accessor :base_url, :configuration, :instance, :klass, :settings
|
6
|
+
|
7
|
+
def initialize(configuration)
|
8
|
+
ensure_loadable
|
9
|
+
self.configuration = configuration
|
10
|
+
self.settings = configuration.settings
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_callbacks!(klass)
|
14
|
+
self.klass = klass
|
15
|
+
create_method_to_callback
|
16
|
+
create_callback
|
17
|
+
end
|
18
|
+
|
19
|
+
def ensure_unique_url!(instance)
|
20
|
+
@url_owners = nil
|
21
|
+
self.instance = instance
|
22
|
+
|
23
|
+
handle_url!
|
24
|
+
handle_duplicate_url! unless settings.allow_duplicates
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize_urls!(klass)
|
28
|
+
self.klass = klass
|
29
|
+
klass_previous_instances do |instance|
|
30
|
+
ensure_unique_url_for! instance
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def url_attribute(instance)
|
35
|
+
# Retrieve from database record if there are errors on attribute_to_urlify
|
36
|
+
if !is_new?(instance) && is_present?(instance.errors[settings.attribute_to_urlify])
|
37
|
+
self.instance = instance
|
38
|
+
read_attribute instance_from_db, settings.url_attribute
|
39
|
+
else
|
40
|
+
read_attribute instance, settings.url_attribute
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.ensure_loadable
|
45
|
+
raise "The #{self} adapter cannot be loaded" unless loadable?
|
46
|
+
Stringex::ActsAsUrl::Adapter.add_loaded_adapter self
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.loadable?
|
50
|
+
orm_class
|
51
|
+
rescue NameError
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def add_new_record_url_owner_conditions
|
58
|
+
return if is_new?(instance)
|
59
|
+
@url_owner_conditions.first << " and #{instance.class.primary_key} != ?"
|
60
|
+
@url_owner_conditions << instance.id
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_scoped_url_owner_conditions
|
64
|
+
return unless settings.scope_for_url
|
65
|
+
@url_owner_conditions.first << " and #{settings.scope_for_url} = ?"
|
66
|
+
@url_owner_conditions << instance.send(settings.scope_for_url)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_callback
|
70
|
+
if settings.sync_url
|
71
|
+
klass.before_validation :ensure_unique_url
|
72
|
+
else
|
73
|
+
klass.before_validation :ensure_unique_url, :on => :create
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_method_to_callback
|
78
|
+
klass.class_eval <<-"END"
|
79
|
+
def #{settings.url_attribute}
|
80
|
+
acts_as_url_configuration.adapter.url_attribute self
|
81
|
+
end
|
82
|
+
END
|
83
|
+
end
|
84
|
+
|
85
|
+
def duplicate_for_base_url(n)
|
86
|
+
"#{base_url}#{settings.duplicate_count_separator}#{n}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def ensure_loadable
|
90
|
+
self.class.ensure_loadable
|
91
|
+
end
|
92
|
+
|
93
|
+
# NOTE: The <tt>instance</tt> here is not the cached instance but a block variable
|
94
|
+
# passed from <tt>klass_previous_instances</tt>, just to be clear
|
95
|
+
def ensure_unique_url_for!(instance)
|
96
|
+
instance.send :ensure_unique_url
|
97
|
+
instance.save
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_base_url_owner_conditions
|
101
|
+
@url_owner_conditions = ["#{settings.url_attribute} LIKE ?", base_url + '%']
|
102
|
+
end
|
103
|
+
|
104
|
+
def handle_duplicate_url!
|
105
|
+
return if url_owners.none?{|owner| url_attribute_for(owner) == base_url}
|
106
|
+
n = 1
|
107
|
+
while url_owners.any?{|owner| url_attribute_for(owner) == duplicate_for_base_url(n)}
|
108
|
+
n = n.succ
|
109
|
+
end
|
110
|
+
write_url_attribute duplicate_for_base_url(n)
|
111
|
+
end
|
112
|
+
|
113
|
+
def handle_url!
|
114
|
+
self.base_url = instance.send(settings.url_attribute)
|
115
|
+
modify_base_url if is_blank?(base_url) || !settings.only_when_blank
|
116
|
+
write_url_attribute base_url
|
117
|
+
end
|
118
|
+
|
119
|
+
def instance_from_db
|
120
|
+
instance.class.find(instance.id)
|
121
|
+
end
|
122
|
+
|
123
|
+
def is_blank?(object)
|
124
|
+
object.blank?
|
125
|
+
end
|
126
|
+
|
127
|
+
def is_new?(object)
|
128
|
+
object.new_record?
|
129
|
+
end
|
130
|
+
|
131
|
+
def is_present?(object)
|
132
|
+
object.present?
|
133
|
+
end
|
134
|
+
|
135
|
+
def loadable?
|
136
|
+
self.class.loadable?
|
137
|
+
end
|
138
|
+
|
139
|
+
def modify_base_url
|
140
|
+
root = instance.send(settings.attribute_to_urlify).to_s
|
141
|
+
self.base_url = root.to_url(configuration.string_extensions_settings)
|
142
|
+
end
|
143
|
+
|
144
|
+
def orm_class
|
145
|
+
self.class.orm_class
|
146
|
+
end
|
147
|
+
|
148
|
+
def read_attribute(instance, attribute)
|
149
|
+
instance.read_attribute attribute
|
150
|
+
end
|
151
|
+
|
152
|
+
def url_attribute_for(object)
|
153
|
+
object.send settings.url_attribute
|
154
|
+
end
|
155
|
+
|
156
|
+
def url_owner_conditions
|
157
|
+
get_base_url_owner_conditions
|
158
|
+
add_new_record_url_owner_conditions
|
159
|
+
add_scoped_url_owner_conditions
|
160
|
+
|
161
|
+
@url_owner_conditions
|
162
|
+
end
|
163
|
+
|
164
|
+
def url_owners
|
165
|
+
@url_owners ||= url_owners_class.unscoped.where(url_owner_conditions).to_a
|
166
|
+
end
|
167
|
+
|
168
|
+
def url_owners_class
|
169
|
+
return instance.class unless settings.enforce_uniqueness_on_sti_base_class
|
170
|
+
|
171
|
+
klass = instance.class
|
172
|
+
while klass.superclass < orm_class
|
173
|
+
klass = klass.superclass
|
174
|
+
end
|
175
|
+
klass
|
176
|
+
end
|
177
|
+
|
178
|
+
def write_attribute(instance, attribute, value)
|
179
|
+
instance.send :write_attribute, attribute, value
|
180
|
+
end
|
181
|
+
|
182
|
+
def write_url_attribute(value)
|
183
|
+
write_attribute instance, settings.url_attribute, value
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|