stringex 1.5.1 → 2.0.0

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 (63) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +16 -0
  3. data/Gemfile.lock +74 -0
  4. data/README.rdoc +22 -1
  5. data/Rakefile +46 -223
  6. data/VERSION +1 -0
  7. data/init.rb +1 -0
  8. data/lib/stringex.rb +11 -3
  9. data/lib/stringex/acts_as_url.rb +49 -97
  10. data/lib/stringex/acts_as_url/adapter.rb +26 -0
  11. data/lib/stringex/acts_as_url/adapter/active_record.rb +23 -0
  12. data/lib/stringex/acts_as_url/adapter/base.rb +188 -0
  13. data/lib/stringex/acts_as_url/adapter/data_mapper.rb +67 -0
  14. data/lib/stringex/acts_as_url/adapter/mongoid.rb +36 -0
  15. data/lib/stringex/configuration.rb +4 -0
  16. data/lib/stringex/configuration/acts_as_url.rb +44 -0
  17. data/lib/stringex/configuration/base.rb +58 -0
  18. data/lib/stringex/configuration/configurator.rb +25 -0
  19. data/lib/stringex/configuration/string_extensions.rb +19 -0
  20. data/lib/stringex/localization.rb +98 -0
  21. data/lib/stringex/localization/backend/i18n.rb +53 -0
  22. data/lib/stringex/localization/backend/internal.rb +51 -0
  23. data/lib/stringex/localization/conversion_expressions.rb +148 -0
  24. data/lib/stringex/localization/converter.rb +121 -0
  25. data/lib/stringex/localization/default_conversions.rb +88 -0
  26. data/lib/stringex/rails/railtie.rb +10 -0
  27. data/lib/stringex/string_extensions.rb +153 -208
  28. data/lib/stringex/unidecoder.rb +6 -101
  29. data/lib/stringex/unidecoder_data/x00.yml +1 -1
  30. data/lib/stringex/unidecoder_data/x02.yml +5 -5
  31. data/lib/stringex/unidecoder_data/x05.yml +1 -1
  32. data/lib/stringex/unidecoder_data/x06.yml +1 -1
  33. data/lib/stringex/unidecoder_data/x07.yml +3 -3
  34. data/lib/stringex/unidecoder_data/x09.yml +1 -1
  35. data/lib/stringex/unidecoder_data/x0e.yml +2 -2
  36. data/lib/stringex/unidecoder_data/x1f.yml +2 -2
  37. data/lib/stringex/unidecoder_data/x20.yml +1 -1
  38. data/lib/stringex/unidecoder_data/xfb.yml +1 -1
  39. data/lib/stringex/unidecoder_data/xff.yml +1 -1
  40. data/lib/stringex/version.rb +8 -0
  41. data/locales/da.yml +73 -0
  42. data/locales/en.yml +66 -0
  43. data/stringex.gemspec +77 -18
  44. data/test/acts_as_url/adapter/active_record.rb +72 -0
  45. data/test/acts_as_url/adapter/data_mapper.rb +82 -0
  46. data/test/acts_as_url/adapter/mongoid.rb +73 -0
  47. data/test/acts_as_url_configuration_test.rb +51 -0
  48. data/test/acts_as_url_integration_test.rb +271 -0
  49. data/test/localization/da_test.rb +117 -0
  50. data/test/localization/default_test.rb +113 -0
  51. data/test/localization/en_test.rb +117 -0
  52. data/test/localization_test.rb +123 -0
  53. data/test/redcloth_to_html_test.rb +37 -0
  54. data/test/string_extensions_test.rb +59 -91
  55. data/test/test_helper.rb +2 -0
  56. data/test/unicode_point_suite/basic_greek_test.rb +113 -0
  57. data/test/unicode_point_suite/basic_latin_test.rb +142 -0
  58. data/test/unicode_point_suite/codepoint_test_helper.rb +32 -0
  59. data/test/unidecoder/bad_localization.yml +1 -0
  60. data/test/unidecoder/localization.yml +4 -0
  61. data/test/unidecoder_test.rb +3 -5
  62. metadata +145 -37
  63. 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
- require 'stringex/acts_as_url' if defined?(ActiveRecord) || defined?(Mongoid::Document)
13
+ Stringex::ActsAsUrl::Adapter.load_available
8
14
 
9
- ActiveRecord::Base.send :include, Stringex::ActsAsUrl if defined?(ActiveRecord)
15
+ if defined?(Rails::Railtie)
16
+ require 'stringex/rails/railtie'
17
+ end
@@ -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.included(base)
5
- base.extend ClassMethods
6
+ def self.configure(&block)
7
+ Stringex::Configuration::ActsAsUrl.configure &block
6
8
  end
7
9
 
8
- class Configuration
9
- attr_accessor :allow_slash, :allow_duplicates, :attribute_to_urlify, :duplicate_count_separator,
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 ClassMethods # :doc:
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>:allow_slash</tt>:: If true, allow the generated url to contain slashes. Default is false[y].
78
- # <tt>:allow_duplicates</tt>:: If true, allow duplicate urls instead of appending numbers to
79
- # differentiate between urls. Default is false[y].
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[y].
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>:url_limit</tt>:: The maximum size a generated url should be. <strong>Note:</strong> this does not
92
- # include the characters needed to enforce uniqueness on duplicate urls.
93
- # Default is nil.
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
- cattr_accessor :acts_as_url_configuration
96
-
97
- options[:attribute] = attribute
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
- class_eval <<-"END"
111
- def #{acts_as_url_configuration.url_attribute}
112
- if !new_record? && errors[acts_as_url_configuration.attribute_to_urlify].present?
113
- self.class.find(id).send(acts_as_url_configuration.url_attribute)
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
- END
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
- find_each(:conditions => {acts_as_url_configuration.url_attribute => nil}) do |instance|
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
- private
137
-
138
- def ensure_unique_url
139
- # Just to save some typing
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