ghazel-acts-as-taggable-on 2.0.6.1
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/CHANGELOG +25 -0
- data/Gemfile +10 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +221 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +7 -0
- data/generators/acts_as_taggable_on_migration/templates/migration.rb +29 -0
- data/lib/acts-as-taggable-on.rb +30 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +53 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +139 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +262 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +105 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +69 -0
- data/lib/acts_as_taggable_on/acts_as_tagger.rb +67 -0
- data/lib/acts_as_taggable_on/compatibility/Gemfile +8 -0
- data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +21 -0
- data/lib/acts_as_taggable_on/tag.rb +84 -0
- data/lib/acts_as_taggable_on/tag_list.rb +96 -0
- data/lib/acts_as_taggable_on/tagging.rb +24 -0
- data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
- data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +32 -0
- data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +28 -0
- data/rails/init.rb +1 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +268 -0
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +70 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +115 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +333 -0
- data/spec/acts_as_taggable_on/tagger_spec.rb +91 -0
- data/spec/acts_as_taggable_on/tagging_spec.rb +31 -0
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +28 -0
- data/spec/bm.rb +52 -0
- data/spec/database.yml.sample +17 -0
- data/spec/models.rb +31 -0
- data/spec/schema.rb +43 -0
- data/spec/spec_helper.rb +60 -0
- metadata +114 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
module ActsAsTaggableOn::Taggable
|
2
|
+
module Ownership
|
3
|
+
def self.included(base)
|
4
|
+
base.send :include, ActsAsTaggableOn::Taggable::Ownership::InstanceMethods
|
5
|
+
base.extend ActsAsTaggableOn::Taggable::Ownership::ClassMethods
|
6
|
+
|
7
|
+
base.class_eval do
|
8
|
+
after_save :save_owned_tags
|
9
|
+
end
|
10
|
+
|
11
|
+
base.initialize_acts_as_taggable_on_ownership
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def acts_as_taggable_on(*args)
|
16
|
+
initialize_acts_as_taggable_on_ownership
|
17
|
+
super(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize_acts_as_taggable_on_ownership
|
21
|
+
tag_types.map(&:to_s).each do |tag_type|
|
22
|
+
class_eval %(
|
23
|
+
def #{tag_type}_from(owner)
|
24
|
+
owner_tag_list_on(owner, '#{tag_type}')
|
25
|
+
end
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
def owner_tags_on(owner, context)
|
33
|
+
if owner.nil?
|
34
|
+
base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ?), context.to_s]).all
|
35
|
+
else
|
36
|
+
base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
|
37
|
+
#{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
|
38
|
+
#{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.to_s]).all
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def cached_owned_tag_list_on(context)
|
43
|
+
variable_name = "@owned_#{context}_list"
|
44
|
+
cache = instance_variable_get(variable_name) || instance_variable_set(variable_name, {})
|
45
|
+
end
|
46
|
+
|
47
|
+
def owner_tag_list_on(owner, context)
|
48
|
+
add_custom_context(context)
|
49
|
+
|
50
|
+
cache = cached_owned_tag_list_on(context)
|
51
|
+
cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class }
|
52
|
+
|
53
|
+
cache[owner] ||= ActsAsTaggableOn::TagList.new(*owner_tags_on(owner, context).map(&:name))
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_owner_tag_list_on(owner, context, new_list)
|
57
|
+
add_custom_context(context)
|
58
|
+
|
59
|
+
cache = cached_owned_tag_list_on(context)
|
60
|
+
cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class }
|
61
|
+
|
62
|
+
cache[owner] = ActsAsTaggableOn::TagList.from(new_list)
|
63
|
+
end
|
64
|
+
|
65
|
+
def reload(*args)
|
66
|
+
self.class.tag_types.each do |context|
|
67
|
+
instance_variable_set("@owned_#{context}_list", nil)
|
68
|
+
end
|
69
|
+
|
70
|
+
super(*args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def save_owned_tags
|
74
|
+
tagging_contexts.each do |context|
|
75
|
+
cached_owned_tag_list_on(context).each do |owner, tag_list|
|
76
|
+
# Find existing tags or create non-existing tags:
|
77
|
+
tag_list = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
|
78
|
+
|
79
|
+
owned_tags = owner_tags_on(owner, context)
|
80
|
+
old_tags = owned_tags - tag_list
|
81
|
+
new_tags = tag_list - owned_tags
|
82
|
+
|
83
|
+
# Find all taggings that belong to the taggable (self), are owned by the owner,
|
84
|
+
# have the correct context, and are removed from the list.
|
85
|
+
old_taggings = ActsAsTaggableOn::Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
|
86
|
+
:tagger_type => owner.class.to_s, :tagger_id => owner.id,
|
87
|
+
:tag_id => old_tags, :context => context).all
|
88
|
+
|
89
|
+
if old_taggings.present?
|
90
|
+
# Destroy old taggings:
|
91
|
+
ActsAsTaggableOn::Tagging.destroy_all(:id => old_taggings.map(&:id))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Create new taggings:
|
95
|
+
new_tags.each do |tag|
|
96
|
+
taggings.create!(:tag_id => tag.id, :context => context.to_s, :tagger => owner, :taggable => self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActsAsTaggableOn::Taggable
|
2
|
+
module Related
|
3
|
+
def self.included(base)
|
4
|
+
base.send :include, ActsAsTaggableOn::Taggable::Related::InstanceMethods
|
5
|
+
base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
|
6
|
+
base.initialize_acts_as_taggable_on_related
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def initialize_acts_as_taggable_on_related
|
11
|
+
tag_types.map(&:to_s).each do |tag_type|
|
12
|
+
class_eval %(
|
13
|
+
def find_related_#{tag_type}(options = {})
|
14
|
+
related_tags_for('#{tag_type}', self.class, options)
|
15
|
+
end
|
16
|
+
alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
|
17
|
+
|
18
|
+
def find_related_#{tag_type}_for(klass, options = {})
|
19
|
+
related_tags_for('#{tag_type}', klass, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_matching_contexts(search_context, result_context, options = {})
|
23
|
+
matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_matching_contexts_for(klass, search_context, result_context, options = {})
|
27
|
+
matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
|
28
|
+
end
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def acts_as_taggable_on(*args)
|
34
|
+
super(*args)
|
35
|
+
initialize_acts_as_taggable_on_related
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
def matching_contexts_for(search_context, result_context, klass, options = {})
|
41
|
+
tags_to_find = tags_on(search_context).collect { |t| t.name }
|
42
|
+
|
43
|
+
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
44
|
+
|
45
|
+
group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
|
46
|
+
|
47
|
+
klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
|
48
|
+
:from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
|
49
|
+
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context],
|
50
|
+
:group => group_columns,
|
51
|
+
:order => "count DESC" }.update(options))
|
52
|
+
end
|
53
|
+
|
54
|
+
def related_tags_for(context, klass, options = {})
|
55
|
+
tags_to_find = tags_on(context).collect { |t| t.name }
|
56
|
+
|
57
|
+
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
58
|
+
|
59
|
+
group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
|
60
|
+
|
61
|
+
klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
|
62
|
+
:from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
|
63
|
+
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find],
|
64
|
+
:group => group_columns,
|
65
|
+
:order => "count DESC" }.update(options))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
module Tagger
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
##
|
9
|
+
# Make a model a tagger. This allows an instance of a model to claim ownership
|
10
|
+
# of tags.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# class User < ActiveRecord::Base
|
14
|
+
# acts_as_tagger
|
15
|
+
# end
|
16
|
+
def acts_as_tagger(opts={})
|
17
|
+
class_eval do
|
18
|
+
has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
|
19
|
+
:include => :tag, :class_name => "ActsAsTaggableOn::Tagging")
|
20
|
+
has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true, :class_name => "ActsAsTaggableOn::Tag"
|
21
|
+
end
|
22
|
+
|
23
|
+
include ActsAsTaggableOn::Tagger::InstanceMethods
|
24
|
+
extend ActsAsTaggableOn::Tagger::SingletonMethods
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_tagger?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
##
|
34
|
+
# Tag a taggable model with tags that are owned by the tagger.
|
35
|
+
#
|
36
|
+
# @param taggable The object that will be tagged
|
37
|
+
# @param [Hash] options An hash with options. Available options are:
|
38
|
+
# * <tt>:with</tt> - The tags that you want to
|
39
|
+
# * <tt>:on</tt> - The context on which you want to tag
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
# @user.tag(@photo, :with => "paris, normandy", :on => :locations)
|
43
|
+
def tag(taggable, opts={})
|
44
|
+
opts.reverse_merge!(:force => true)
|
45
|
+
|
46
|
+
return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
|
47
|
+
|
48
|
+
raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
|
49
|
+
raise "You need to specify some tags using :with" unless opts.has_key?(:with)
|
50
|
+
raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
|
51
|
+
|
52
|
+
taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
|
53
|
+
taggable.save
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_tagger?
|
57
|
+
self.class.is_tagger?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module SingletonMethods
|
62
|
+
def is_tagger?
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
module ActiveRecord
|
3
|
+
module Backports
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
named_scope :where, lambda { |conditions| { :conditions => conditions } }
|
7
|
+
named_scope :joins, lambda { |joins| { :joins => joins } }
|
8
|
+
named_scope :group, lambda { |group| { :group => group } }
|
9
|
+
named_scope :order, lambda { |order| { :order => order } }
|
10
|
+
named_scope :select, lambda { |select| { :select => select } }
|
11
|
+
named_scope :limit, lambda { |limit| { :limit => limit } }
|
12
|
+
named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
|
13
|
+
|
14
|
+
def self.to_sql
|
15
|
+
construct_finder_sql({})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
class Tag < ::ActiveRecord::Base
|
3
|
+
include ActsAsTaggableOn::ActiveRecord::Backports if ::ActiveRecord::VERSION::MAJOR < 3
|
4
|
+
|
5
|
+
attr_accessible :name
|
6
|
+
|
7
|
+
### ASSOCIATIONS:
|
8
|
+
|
9
|
+
has_many :taggings, :dependent => :destroy, :class_name => 'ActsAsTaggableOn::Tagging'
|
10
|
+
|
11
|
+
### VALIDATIONS:
|
12
|
+
|
13
|
+
validates_presence_of :name
|
14
|
+
validates_uniqueness_of :name
|
15
|
+
|
16
|
+
### SCOPES:
|
17
|
+
|
18
|
+
def self.using_postgresql?
|
19
|
+
connection.adapter_name == 'PostgreSQL'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.named(name)
|
23
|
+
where(["name #{like_operator} ?", name])
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.named_any(list)
|
27
|
+
where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", tag.to_s]) }.join(" OR "))
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.named_like(name)
|
31
|
+
where(["name #{like_operator} ?", "%#{name}%"])
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.named_like_any(list)
|
35
|
+
where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", "%#{tag.to_s}%"]) }.join(" OR "))
|
36
|
+
end
|
37
|
+
|
38
|
+
### CLASS METHODS:
|
39
|
+
|
40
|
+
def self.find_or_create_with_like_by_name(name)
|
41
|
+
named_like(name).first || create(:name => name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.find_or_create_all_with_like_by_name(*list)
|
45
|
+
list = [list].flatten
|
46
|
+
|
47
|
+
return [] if list.empty?
|
48
|
+
|
49
|
+
existing_tags = Tag.named_any(list).all
|
50
|
+
new_tag_names = list.reject do |name|
|
51
|
+
name = comparable_name(name)
|
52
|
+
existing_tags.any? { |tag| comparable_name(tag.name) == name }
|
53
|
+
end
|
54
|
+
created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
|
55
|
+
|
56
|
+
existing_tags + created_tags
|
57
|
+
end
|
58
|
+
|
59
|
+
### INSTANCE METHODS:
|
60
|
+
|
61
|
+
def ==(object)
|
62
|
+
super || (object.is_a?(Tag) && name == object.name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
name
|
67
|
+
end
|
68
|
+
|
69
|
+
def count
|
70
|
+
read_attribute(:count).to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
class << self
|
74
|
+
private
|
75
|
+
def like_operator
|
76
|
+
using_postgresql? ? 'ILIKE' : 'LIKE'
|
77
|
+
end
|
78
|
+
|
79
|
+
def comparable_name(str)
|
80
|
+
RUBY_VERSION >= "1.9" ? str.downcase : str.mb_chars.downcase
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
class TagList < Array
|
3
|
+
cattr_accessor :delimiter
|
4
|
+
self.delimiter = ','
|
5
|
+
|
6
|
+
attr_accessor :owner
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
add(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Returns a new TagList using the given tag string.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
# tag_list = TagList.from("One , Two, Three")
|
17
|
+
# tag_list # ["One", "Two", "Three"]
|
18
|
+
def self.from(string)
|
19
|
+
glue = delimiter.ends_with?(" ") ? delimiter : "#{delimiter} "
|
20
|
+
string = string.join(glue) if string.respond_to?(:join)
|
21
|
+
|
22
|
+
new.tap do |tag_list|
|
23
|
+
string = string.to_s.dup
|
24
|
+
|
25
|
+
# Parse the quoted tags
|
26
|
+
string.gsub!(/(\A|#{delimiter})\s*"(.*?)"\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
|
27
|
+
string.gsub!(/(\A|#{delimiter})\s*'(.*?)'\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
|
28
|
+
|
29
|
+
tag_list.add(string.split(delimiter))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Add tags to the tag_list. Duplicate or blank tags will be ignored.
|
35
|
+
# Use the <tt>:parse</tt> option to add an unparsed tag string.
|
36
|
+
#
|
37
|
+
# Example:
|
38
|
+
# tag_list.add("Fun", "Happy")
|
39
|
+
# tag_list.add("Fun, Happy", :parse => true)
|
40
|
+
def add(*names)
|
41
|
+
extract_and_apply_options!(names)
|
42
|
+
concat(names)
|
43
|
+
clean!
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Remove specific tags from the tag_list.
|
49
|
+
# Use the <tt>:parse</tt> option to add an unparsed tag string.
|
50
|
+
#
|
51
|
+
# Example:
|
52
|
+
# tag_list.remove("Sad", "Lonely")
|
53
|
+
# tag_list.remove("Sad, Lonely", :parse => true)
|
54
|
+
def remove(*names)
|
55
|
+
extract_and_apply_options!(names)
|
56
|
+
delete_if { |name| names.include?(name) }
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Transform the tag_list into a tag string suitable for edting in a form.
|
62
|
+
# The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
|
63
|
+
#
|
64
|
+
# Example:
|
65
|
+
# tag_list = TagList.new("Round", "Square,Cube")
|
66
|
+
# tag_list.to_s # 'Round, "Square,Cube"'
|
67
|
+
def to_s
|
68
|
+
tags = frozen? ? self.dup : self
|
69
|
+
tags.send(:clean!)
|
70
|
+
|
71
|
+
tags.map do |name|
|
72
|
+
name.include?(delimiter) ? "\"#{name}\"" : name
|
73
|
+
end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Remove whitespace, duplicates, and blanks.
|
79
|
+
def clean!
|
80
|
+
reject!(&:blank?)
|
81
|
+
map!(&:strip)
|
82
|
+
uniq!
|
83
|
+
end
|
84
|
+
|
85
|
+
def extract_and_apply_options!(args)
|
86
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
87
|
+
options.assert_valid_keys :parse
|
88
|
+
|
89
|
+
if options[:parse]
|
90
|
+
args.map! { |a| self.class.from(a) }
|
91
|
+
end
|
92
|
+
|
93
|
+
args.flatten!
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|