tagtical 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -4,8 +4,8 @@ source :gemcutter
4
4
 
5
5
  # Cannot require these as dependency until there the fix is released:
6
6
  #
7
- #gem 'rails', '3.0.5'
8
- #gem 'rspec', '2.6.0'
7
+ gem 'rails', '<=3.0.5'
8
+ gem 'rspec', '<=2.6.0'
9
9
  # http://rubyforge.org/tracker/?func=detail&atid=575&aid=29163&group_id=126
10
10
 
11
11
  gem 'sqlite3-ruby', :require => 'sqlite3'
@@ -17,4 +17,4 @@ gem 'rcov'
17
17
 
18
18
  group :test do
19
19
  gem "mocha"
20
- end
20
+ end
data/README.rdoc CHANGED
@@ -115,8 +115,11 @@ rake spec:plugins
115
115
  @user.save
116
116
 
117
117
  @user.activities # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">,<Tag::Sport value:"boxing">]
118
- @user.activities(:only => :children) # => [<Tag::Sport value:"boxing">] - look at only the STI subclasses
119
- @user.activities(:only => :current) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - look at only the current STI class
118
+ @user.activities(:type => :children) # => [<Tag::Sport value:"boxing">] - look at only the STI subclasses
119
+ @user.activities(:type => :<) # => [<Tag::Sport value:"boxing">] - look at only the STI subclasses
120
+ @user.activities(:type => :current) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - look at only the current STI class
121
+ @user.activities(:type => :==) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - look at only the current STI class
122
+
120
123
  @user.activities.first.athletic? # => false
121
124
  @user.sports.all(&:ball?) # => true
122
125
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.6
1
+ 1.0.7
data/lib/tagtical/tag.rb CHANGED
@@ -60,7 +60,15 @@ module Tagtical
60
60
 
61
61
  def sti_name
62
62
  return @sti_name if instance_variable_defined?(:@sti_name)
63
- @sti_name = Tagtical::Tag==self ? nil : Type.new(name.demodulize).to_sti_name
63
+ @sti_name = Tagtical::Tag==self ? nil : Type[name.demodulize].to_sti_name
64
+ end
65
+
66
+ def define_methods_for_type(tag_type)
67
+ (@define_methods_for_type ||= {})[tag_type] ||= begin
68
+ scope(tag_type.scope_name, Proc.new { |options| tag_type.scoping(options || {}) })
69
+ define_method(:"#{tag_type}?") { is_a?(tag_type.klass!) }
70
+ true
71
+ end
64
72
  end
65
73
 
66
74
  protected
@@ -94,7 +102,7 @@ module Tagtical
94
102
  end
95
103
 
96
104
  def relevance
97
- (v = self["relevance"]) && v.to_f
105
+ (v = self[:relevance]) && v.to_f
98
106
  end
99
107
 
100
108
  # Try to sort by the relevance if provided.
@@ -110,23 +118,15 @@ module Tagtical
110
118
  value
111
119
  end
112
120
 
113
- # Overwrite these methods to provide your own storage mechanism for a tag.
114
- def load_value(value) value end
115
- def dump_value(value) value end
116
-
117
- def value
118
- @value ||= load_value(self[:value])
119
- end
120
-
121
- def value=(value)
122
- @value = nil
123
- self[:value] = dump_value(value)
121
+ def inspect
122
+ super.tap do |str|
123
+ str[-2] = "relevance: #{attribute_for_inspect(:relevance)}" if has_attribute?(:relevance)
124
+ end
124
125
  end
125
126
 
126
127
  # We return nil if we are *not* an STI class.
127
128
  def type
128
- type = self[:type]
129
- type && Type[type]
129
+ (type = self[:type]) && Type.new(type)
130
130
  end
131
131
 
132
132
  def count
@@ -146,17 +146,19 @@ module Tagtical
146
146
  # "tag" should always correspond with demodulize name of the base Tag class (ie Tagtical::Tag).
147
147
  BASE = "tag".freeze
148
148
 
149
- # Default to simply "tag", if none is provided. This will return Tagtical::Tag on calls to #klass
150
- def initialize(arg)
151
- super(arg.to_s.singularize.underscore.gsub(/_tag$/, ''))
152
- end
153
-
154
149
  class << self
155
150
  def find(input)
156
151
  return input.map { |c| self[c] } if input.is_a?(Array)
157
- input.is_a?(self) ? input : new(input)
152
+ input.is_a?(self) ? input : new(sanitize(input))
158
153
  end
159
154
  alias :[] :find
155
+
156
+ private
157
+
158
+ # Sanitize the input for type name consistency.
159
+ def sanitize(input)
160
+ input.to_s.singularize.underscore.gsub(/_tag$/, '')
161
+ end
160
162
  end
161
163
 
162
164
  # The STI name for the Tag model is the same as the tag type.
@@ -172,24 +174,24 @@ module Tagtical
172
174
  # <tt>only</tt> - An array of the following: :parents, :current, :children. Will construct conditions to query the current, parent, and/or children STI classes.
173
175
  #
174
176
  def finder_type_condition(options={})
175
- only = Array.wrap(options[:only] || (klass ? [:current, :children] : :current))
177
+ type = convert_type_options(options[:type])
176
178
 
177
179
  # If we want [:current, :children] or [:current, :children, :parents] and we don't need the finder type condition,
178
180
  # then that means we don't need a condition at all since we are at the top-level sti class and we are essentially
179
181
  # searching the whole range of sti classes.
180
182
  if klass && !klass.finder_needs_type_condition?
181
- only.delete(:parents) # we are at the topmost level.
182
- only = [] if only==[:current, :children] # no condition is required if we want the current AND the children.
183
+ type.delete(:parents) # we are at the topmost level.
184
+ type = [] if type==[:current, :children] # no condition is required if we want the current AND the children.
183
185
  end
184
186
 
185
187
  sti_names = []
186
- if only.include?(:current)
188
+ if type.include?(:current)
187
189
  sti_names << (klass ? klass.sti_name : to_sti_name)
188
190
  end
189
- if only.include?(:children) && klass
191
+ if type.include?(:children) && klass
190
192
  sti_names.concat(klass.descendants.map(&:sti_name))
191
193
  end
192
- if only.include?(:parents) && klass # include searches up the STI chain
194
+ if type.include?(:parents) && klass # include searches up the STI chain
193
195
  parent_class = klass.superclass
194
196
  while parent_class <= Tagtical::Tag
195
197
  sti_names << parent_class.sti_name
@@ -283,6 +285,19 @@ module Tagtical
283
285
  end
284
286
  end
285
287
 
288
+ # Take operator types (ie <, >, =) and convert them into :children, :current, or :parents.
289
+ def convert_type_options(input)
290
+ Array.wrap(input || (klass ? [:current, :children] : :current)).map do |type, i|
291
+ if (t = type.to_s)=~/^[=><]+$/
292
+ {"=" => :current, ">" => :parents, "<" => :children}.map do |operator, val|
293
+ val if t.include?(operator)
294
+ end.compact
295
+ else
296
+ type
297
+ end
298
+ end.flatten.uniq
299
+ end
300
+
286
301
  def find_tag_class
287
302
  candidates = derive_class_candidates
288
303
 
@@ -24,14 +24,14 @@ module Tagtical::Taggable
24
24
  # This keeps your reflections cleaner.
25
25
 
26
26
  # In the case of the base tag type, it will just use the :tags association defined above.
27
- Tagtical::Tag.scope(tag_type.scope_name, Proc.new { |options| tag_type.scoping(options || {}) }) unless Tagtical::Tag.respond_to?(tag_type.scope_name)
27
+ Tagtical::Tag.define_methods_for_type(tag_type)
28
28
 
29
29
  # If the tag_type is base? (type=="tag"), then we add additional functionality to the AR
30
30
  # has_many :tags.
31
31
  #
32
- # taggable_model.tags(:only => :children)
32
+ # taggable_model.tags(:type => :children)
33
33
  # taggable_model.tags <-- still works like normal has_many
34
- # taggable_model.tags(true, :only => :current) <-- reloads the tags association and appends scope for only current type.
34
+ # taggable_model.tags(true, :type => :current) <-- reloads the tags association and appends scope for only current type.
35
35
  if tag_type.has_many_name==:tags
36
36
  define_method("tags_with_finder_type_options") do |*args|
37
37
  options = args.pop if args.last.is_a?(Hash)
@@ -99,7 +99,7 @@ module Tagtical::Taggable
99
99
 
100
100
  options[:on] ||= Tagtical::Tag::Type::BASE
101
101
  tag_type = Tagtical::Tag::Type.find(options.delete(:on))
102
- finder_type_condition_options = options.extract!(:only)
102
+ finder_type_condition_options = options.extract!(:type)
103
103
 
104
104
  tag_table, tagging_table = Tagtical::Tag.table_name, Tagtical::Tagging.table_name
105
105
 
@@ -259,7 +259,7 @@ module Tagtical::Taggable
259
259
  tag_value_lookup = tag_type.scoping { find_or_create_tags(tag_list) }
260
260
  tags = tag_value_lookup.keys
261
261
 
262
- current_tags = tags_on(tag_type, :only => [:current, :parents, :children])
262
+ current_tags = tags_on(tag_type, :type => [:current, :parents, :children])
263
263
  old_tags = current_tags - tags
264
264
  new_tags = tags - current_tags
265
265
 
@@ -72,7 +72,7 @@ module Tagtical::Taggable
72
72
  tag_value_lookup = tag_type.scoping { find_or_create_tags(tag_list) }
73
73
  tags = tag_value_lookup.keys
74
74
 
75
- owned_tags = owner_tags_on(owner, tag_type, :only => [:current, :parents, :children])
75
+ owned_tags = owner_tags_on(owner, tag_type, :type => [:current, :parents, :children])
76
76
  old_tags = owned_tags - tags
77
77
  new_tags = tags - owned_tags
78
78
 
data/spec/spec_helper.rb CHANGED
@@ -25,7 +25,12 @@ end
25
25
 
26
26
  RSpec::Matchers.define :have_tag_values do |expected|
27
27
  match do |actual|
28
- actual.map(&:value).sort == expected.sort
28
+ actual.map(&:value).should have_same_elements(expected)
29
+ end
30
+ end
31
+ RSpec::Matchers.define :have_same_elements do |expected|
32
+ match do |actual|
33
+ actual.sort == expected.sort
29
34
  end
30
35
  end
31
36
 
@@ -57,7 +57,7 @@ describe Tagtical::Tag do
57
57
 
58
58
  end
59
59
 
60
- describe "tag scopes" do
60
+ describe "tag scopes Type#finder_type_conditions" do
61
61
  before do
62
62
  Tagtical::Tag.create(:value => "Plane")
63
63
  Tag::Skill.create(:value => "Kung Fu")
@@ -65,40 +65,71 @@ describe Tagtical::Tag do
65
65
  NeedTag.create(:value => "chair")
66
66
  end
67
67
 
68
- it "should retrieve tags finder type conditions" do
69
- Tagtical::Tag.skills.should have_tag_values ["Kung Fu", "Painting"]
70
- Tagtical::Tag.skills(:only => :current).should have_tag_values ["Kung Fu"]
71
- Tagtical::Tag.crafts(:only => :parents).should have_tag_values ["Kung Fu", "Plane"]
68
+ context "when :type => :current or the alias :==" do
69
+ it "should retrieve current STI level tags" do
70
+ Tagtical::Tag.skills.should have_tag_values ["Kung Fu", "Painting"]
71
+ Tagtical::Tag.skills(:type => :current).should have_tag_values ["Kung Fu"]
72
+ Tagtical::Tag.skills(:type => :==).should have_tag_values ["Kung Fu"]
73
+ end
72
74
  end
73
75
 
74
- end
76
+ context "when :type => :parent or the alias :>" do
77
+ it "should retrieve parent STI level tags" do
78
+ Tagtical::Tag.skills.should have_tag_values ["Kung Fu", "Painting"]
79
+ Tagtical::Tag.crafts(:type => :parents).should have_tag_values ["Kung Fu", "Plane"]
80
+ Tagtical::Tag.crafts(:type => :>).should have_tag_values ["Kung Fu", "Plane"]
81
+ Tagtical::Tag.skills(:type => :>).should have_tag_values ["Plane"]
82
+ end
83
+ end
75
84
 
76
- describe "#dump_value" do
77
- before do
78
- @tag = Tagtical::Tag::PartTag.new(:value => "FOO")
85
+ context "when :type => :childern or the alias :<" do
86
+ it "should retrieve child STI level tags" do
87
+ Tagtical::Tag.skills.should have_tag_values ["Kung Fu", "Painting"]
88
+ Tagtical::Tag.skills(:type => :children).should have_tag_values ["Painting"]
89
+ Tagtical::Tag.skills(:type => :<).should have_tag_values ["Painting"]
90
+ end
79
91
  end
80
92
 
81
- its(:value) { should == "foo" }
93
+ context "when :type => :\"><\"" do
94
+ it "should retrieve parent and child STI level tags" do
95
+ Tagtical::Tag.skills.should have_tag_values ["Kung Fu", "Painting"]
96
+ Tagtical::Tag.skills(:type => :"><").should have_tag_values ["Plane", "Painting"]
97
+ end
98
+ end
82
99
 
83
- it "should accept a nil value" do
84
- lambda { @tag.value = nil }.should_not raise_error
85
- @tag.value.should be_nil
100
+ context "when :type => :>=" do
101
+ it "should retrieve current and parent STI level tags" do
102
+ Tagtical::Tag.skills.should have_tag_values ["Kung Fu", "Painting"]
103
+ Tagtical::Tag.skills(:type => :>=).should have_tag_values ["Kung Fu", "Plane"]
104
+ end
105
+ end
106
+
107
+ context "when :type => :<=" do
108
+ it "should retrieve current and child STI level tags" do
109
+ Tagtical::Tag.skills.should have_tag_values ["Kung Fu", "Painting"]
110
+ Tagtical::Tag.skills(:type => :<=).should have_tag_values ["Kung Fu", "Painting"]
111
+ end
86
112
  end
87
113
  end
88
-
89
- describe "#load_value" do
114
+
115
+ describe ".define_methods_for_type" do
90
116
  before do
91
- @tag = Tag::Skill.new(:value => "basketball")
117
+ @skill = Tag::Skill.new(:value => "baskeball")
118
+ @craft = Tag::Craft.new(:value => "pottery")
92
119
  end
93
120
 
94
- specify { @tag[:value].should == "basketball" }
95
-
96
- its(:value) { should == "basketballer" }
121
+ it "should have a quester method" do
122
+ @skill.tag?.should be_true
123
+ @skill.skill?.should be_true
124
+ @skill.craft?.should be_false
125
+ end
97
126
 
98
- it "should accept a nil value" do
99
- lambda { @tag.value = nil }.should_not raise_error
100
- @tag.value.should be_nil
127
+ it "should have a quester method that considers inheritance" do
128
+ @craft.tag?.should be_true
129
+ @craft.skill?.should be_true
130
+ @craft.craft?.should be_true
101
131
  end
132
+
102
133
  end
103
134
 
104
135
  it "should refresh @value on value setter" do
@@ -254,10 +285,10 @@ describe Tagtical::Tag do
254
285
  its(:klass) { should == Tag::Skill }
255
286
  its(:scope_name) { should == :skills }
256
287
 
257
- describe "initialize" do
288
+ describe ".find" do
258
289
  it "converts string into correct format" do
259
290
  {"ClassNames" => "class_name", "photo_tags" => "photo", :photo => "photo"}.each do |input, result|
260
- @klass.new(input).should == result
291
+ @klass.find(input).should == result
261
292
  end
262
293
  end
263
294
  end
@@ -273,6 +304,21 @@ describe Tagtical::Tag do
273
304
  end
274
305
  end
275
306
 
307
+ describe "#convert_type_options" do
308
+ {:<= => [:children, :current],
309
+ :>= => [:parents, :current],
310
+ :"<>" => [:children, :parents],
311
+ :== => [:current],
312
+ "==" => [:current], # should work with strings as well.
313
+ :">" => [:parents],
314
+ :"<" => [:children]
315
+ }.each do |operator, expected|
316
+ it "should convert #{operator.inspect} to #{expected.inspect}" do
317
+ subject.send(:convert_type_options, operator).should have_same_elements(expected)
318
+ end
319
+ end
320
+ end
321
+
276
322
  describe "#derive_class_candidates" do
277
323
  specify do
278
324
  subject.send(:derive_class_candidates).should include(
@@ -64,24 +64,35 @@ describe Tagtical::Taggable do
64
64
  end
65
65
 
66
66
  it "should be able to query tags" do
67
- @taggables[0].tags(:only => :current).should have_tag_values %w{bob}
67
+ @taggables[0].tags(:type => :current).should have_tag_values %w{bob}
68
+ @taggables[0].tags(:type => :==).should have_tag_values %w{bob}
68
69
  @taggables[0].tags.should have_tag_values %w{bob knitting ruby}
69
- @taggables[0].tags(:only => :children).should have_tag_values %w{knitting ruby}
70
- @taggables[1].crafts(:only => :parents).should have_tag_values %w{charlie css}
71
- @taggables[1].crafts(:only => [:parents, :current]).should have_tag_values %w{charlie css pottery}
72
- @taggables[1].skills(:only => [:parents, :children]).should have_tag_values %w{charlie pottery}
70
+ @taggables[0].tags(:type => :children).should have_tag_values %w{knitting ruby}
71
+ @taggables[0].tags(:type => :<).should have_tag_values %w{knitting ruby}
72
+ @taggables[1].crafts(:type => :parents).should have_tag_values %w{charlie css}
73
+ @taggables[1].crafts(:type => :>).should have_tag_values %w{charlie css}
74
+
75
+ @taggables[1].crafts(:type => [:parents, :current]).should have_tag_values %w{charlie css pottery}
76
+ @taggables[1].crafts(:type => :>=).should have_tag_values %w{charlie css pottery}
77
+ @taggables[1].skills(:type => [:parents, :children]).should have_tag_values %w{charlie pottery}
78
+ @taggables[1].skills(:type => :"><").should have_tag_values %w{charlie pottery}
73
79
  end
74
80
 
75
81
  it "should be able to select taggables by subset of tags using ActiveRelation methods" do
76
82
  TaggableModel.with_tags("bob").should == [@taggables[0]]
77
83
  TaggableModel.with_skills("ruby").should == [@taggables[0]]
78
84
  TaggableModel.with_tags("rUBy").should == [@taggables[0]]
79
- TaggableModel.with_tags("ruby", :only => :current).should == []
85
+ TaggableModel.with_tags("ruby", :type => :current).should == []
86
+ TaggableModel.with_tags("ruby", :type => :==).should == []
80
87
  TaggableModel.with_skills("knitting").should == [@taggables[0]]
81
- TaggableModel.with_skills("KNITTING", :only => :current).should == []
82
- TaggableModel.with_skills("knitting", :only => :parents).should == []
83
- TaggableModel.with_tags("bob", :only => :current).should == [@taggables[0]]
84
- TaggableModel.with_skills("bob", :only => :parents).should == [@taggables[0]]
88
+ TaggableModel.with_skills("KNITTING", :type => :current).should == []
89
+ TaggableModel.with_skills("KNITTING", :type => :==).should == []
90
+ TaggableModel.with_skills("knitting", :type => :parents).should == []
91
+ TaggableModel.with_skills("knitting", :type => :>).should == []
92
+ TaggableModel.with_tags("bob", :type => :current).should == [@taggables[0]]
93
+ TaggableModel.with_tags("bob", :type => :==).should == [@taggables[0]]
94
+ TaggableModel.with_skills("bob", :type => :parents).should == [@taggables[0]]
95
+ TaggableModel.with_skills("bob", :type => :>).should == [@taggables[0]]
85
96
  TaggableModel.with_crafts("knitting").should == [@taggables[0]]
86
97
  end
87
98
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tagtical
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,33 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-10 00:00:00.000000000Z
12
+ date: 2011-07-12 00:00:00.000000000Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: &2152043280 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - <=
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.5
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2152043280
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2152042800 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - <=
31
+ - !ruby/object:Gem::Version
32
+ version: 2.6.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2152042800
14
36
  - !ruby/object:Gem::Dependency
15
37
  name: sqlite3-ruby
16
- requirement: &2161646020 !ruby/object:Gem::Requirement
38
+ requirement: &2152042320 !ruby/object:Gem::Requirement
17
39
  none: false
18
40
  requirements:
19
41
  - - ! '>='
@@ -21,10 +43,10 @@ dependencies:
21
43
  version: '0'
22
44
  type: :runtime
23
45
  prerelease: false
24
- version_requirements: *2161646020
46
+ version_requirements: *2152042320
25
47
  - !ruby/object:Gem::Dependency
26
48
  name: mysql
27
- requirement: &2161645540 !ruby/object:Gem::Requirement
49
+ requirement: &2152041840 !ruby/object:Gem::Requirement
28
50
  none: false
29
51
  requirements:
30
52
  - - ! '>='
@@ -32,10 +54,10 @@ dependencies:
32
54
  version: '0'
33
55
  type: :runtime
34
56
  prerelease: false
35
- version_requirements: *2161645540
57
+ version_requirements: *2152041840
36
58
  - !ruby/object:Gem::Dependency
37
59
  name: jeweler
38
- requirement: &2161645060 !ruby/object:Gem::Requirement
60
+ requirement: &2152041360 !ruby/object:Gem::Requirement
39
61
  none: false
40
62
  requirements:
41
63
  - - ! '>='
@@ -43,10 +65,10 @@ dependencies:
43
65
  version: '0'
44
66
  type: :runtime
45
67
  prerelease: false
46
- version_requirements: *2161645060
68
+ version_requirements: *2152041360
47
69
  - !ruby/object:Gem::Dependency
48
70
  name: rcov
49
- requirement: &2161644580 !ruby/object:Gem::Requirement
71
+ requirement: &2152040880 !ruby/object:Gem::Requirement
50
72
  none: false
51
73
  requirements:
52
74
  - - ! '>='
@@ -54,7 +76,7 @@ dependencies:
54
76
  version: '0'
55
77
  type: :runtime
56
78
  prerelease: false
57
- version_requirements: *2161644580
79
+ version_requirements: *2152040880
58
80
  description: Tagtical allows you do create subclasses for Tag and add additional functionality
59
81
  in an STI fashion. For example. You could do Tag::Color.find_by_name('blue').to_rgb.
60
82
  It also supports storing weights or relevance on the taggings.
@@ -66,7 +88,6 @@ extra_rdoc_files:
66
88
  files:
67
89
  - CHANGELOG
68
90
  - Gemfile
69
- - Gemfile.lock
70
91
  - MIT-LICENSE
71
92
  - README.rdoc
72
93
  - Rakefile
data/Gemfile.lock DELETED
@@ -1,25 +0,0 @@
1
- GEM
2
- remote: http://rubygems.org/
3
- specs:
4
- git (1.2.5)
5
- jeweler (1.6.2)
6
- bundler (~> 1.0)
7
- git (>= 1.2.5)
8
- rake
9
- mocha (0.9.12)
10
- mysql (2.8.1)
11
- rake (0.9.2)
12
- rcov (0.9.9)
13
- sqlite3 (1.3.3)
14
- sqlite3-ruby (1.3.3)
15
- sqlite3 (>= 1.3.3)
16
-
17
- PLATFORMS
18
- ruby
19
-
20
- DEPENDENCIES
21
- jeweler
22
- mocha
23
- mysql
24
- rcov
25
- sqlite3-ruby