Skip to content

Commit

Permalink
Merge ches/integration into andresf/integration
Browse files Browse the repository at this point in the history
  • Loading branch information
andresf committed Apr 22, 2013
2 parents 068fa08 + 75ca52d commit 6be91ca
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 92 deletions.
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: ruby

rvm:
- 1.8.7
- 1.9.2
- 1.9.3

60 changes: 60 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## Integration branch

I have continued to track Wilker's original `master` in hopes that we can merge
them some day -- we initially had some differences of opinion and I maintained
this fork for my own needs as well as pulling in a few ideas from forks. Changes
on this fork are noted here, with backwards-incompatible changes emphasized.

### Features

- Define `fieldname_before_type_cast` so that form fields "just work" with
String-formatted tag lists ([tmaier][])
- Ability to pass map/reduce options to the Mongo driver for the aggregation
feature ([andresf][])
- Tag aggregation can be called on-demand when it is not enabled to run
automatically with callbacks ([adkron][])
- Options given to the `taggable` macro that don't apply to the plugin are
passed to the Mongoid `field` definition ([ches][])
- Tag de-duplication (probably ought to just use Set...) (Wei Kong @
[cocoafish][])
- More robust parsing of tags from string input ([fagiani][])
- **Consolidation of `tags` and `tags_array` instance methods**. This is the
primary API wart I wanted to change -- the `tags` accessor (or whatever custom
field name you use) always returns an Array, and if a String is given to the
setter it is tokenized and stored as an Array. ([ches][])
- Field name for tags can be specified explicitly, instead of being forced as
`tags` ([ches][])
- Tag "indexing" feature renamed to "aggregation" **and disabled by default**.
This seems a more accurate named for map/reduce tag counting. ([ches][])
- **Use `taggable` macro method to invoke the plugin's behavior on a model**,
allowing plugin options to passed in one sensible place ([ches][])
- Spec suite isolated from needing a sample app ([ches][])
- Use ActiveSupport::Concern in the style of Mongoid 2.x ([ches][])
- `tagged_with` class method to find records by an Array or String of tags
([petRUShka][])
- Drop use of Jeweler, use Bundler ([ches][])


### Bug Fixes

- Rails 3.1 deprecation and gemspec warning fixes ([JangoSteve][])
- Aggregation skipping fixed for Mongoid 2.1's switch to ActiveModel-compliant
dirty tracking ([ches][])
- Map/reduce aggregation isn't run on record save if tags weren't changed
([ches][])


## 0.1.1 - 26 July, 2010

Wilker's last official gem release before this fork.


[adkron]: https://github.com/adkron
[andresf]: https://github.com/andresf
[ches]: https://github.com/ches
[cocoafish]: https://github.com/cocoafish
[fagiani]: https://github.com/fagiani
[JangoSteve]: https://github.com/JangoSteve
[petRUShka]: https://github.com/petRUShka
[tmaier]: https://github.com/tmaier

11 changes: 8 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ gemspec
gem 'rake'

platforms :mri_18 do
gem 'ruby-debug'
gem 'SystemTimer'
end

platforms :mri_19 do
gem 'ruby-debug19', :require => 'ruby-debug' if RUBY_VERSION < '1.9.3'
unless ENV['CI']
platforms :mri_18 do
gem 'ruby-debug'
end

platforms :mri_19 do
gem 'debugger'
end
end

57 changes: 39 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
Mongoid Taggable
================
# Mongoid Taggable [![Build Status][]][travis-project]

Mongoid Taggable provides some helpers to create taggable documents.

Installation
------------

You can simply install from rubygems:
This project is a fork that has drifted quite a bit from the original
maintainer's version, and currently has not been re-released as a gem. See
details on the fork changes in the `CHANGELOG` file.

gem install mongoid_taggable
Using Bundler, in your `Gemfile`:

or in `Gemfile`:

gem 'mongoid_taggable'
gem 'mongoid_taggable', :git => 'https://github.com/ches/mongoid_taggable.git',
:branch => 'integration'

or as a Rails Plugin:

rails plugin install git://github.com/wilkerlucio/mongoid_taggable.git
rails plugin install git://github.com/ches/mongoid_taggable.git

Basic Usage
-----------

To make a document taggable you need to include `Mongoid::Taggable` into your document and call the `taggable` macro with optional arguments:
To make a document taggable you need to include `Mongoid::Taggable` into your
document and call the `taggable` macro with optional arguments:

```ruby
class Post
Expand Down Expand Up @@ -49,22 +50,28 @@ Then in your form, for example:
</p>
<p>
<%= f.label :tags %><br />
<%= text_field_tag 'post[tags]', (@post.tags.join(', ') if @post.tags) %>
<%= f.text_field :tags %>
</p>
<p>
<button type="submit">Send</button>
</p>
<% end %>
```

You can of course use helpers or a `FormBuilder` extension to express this in a prettier way. If you're using SimpleForm for example, a custom input can be found in [this Gist](https://gist.github.com/1172956), usable as `f.input :tags` within `simple_form_for` blocks. The text field should receive a list of tags separated by comma (below in this document you will see how to change the separator).
The text field should receive a list of tags separated by comma (below in this
document you will see how to change the separator).

Your document will have a custom `tags=` setter which can accept either an ordinary Array or this separator-delineated String.
Your document will have a custom `tags=` setter which can accept either an
ordinary Array or this separator-delineated String.

Tag Aggregation with Counts
---------------------------

This lib can automatically create an aggregate collection of tags and their counts for you, updated as documents are saved. This is useful for getting a list of all tags used in documents of this collection or to create a tag cloud. This is disabled by default for sake of performance where it is unneeded -- see the following example to understand how to use it:
This lib can automatically create an aggregate collection of tags and their
counts for you, updated as documents are saved. This is useful for getting a
list of all tags used in documents of this collection or to create a tag cloud.
This is disabled by default for sake of performance where it is unneeded -- see
the following example to understand how to use it:

```ruby
class Post
Expand Down Expand Up @@ -96,9 +103,12 @@ Post.tags_with_weight # will retrieve:
# ]
```

You may also trigger aggregation on-demand rather than setting the automatic option, to run it from a background task for instance, by calling `Post.aggregate_tags!`.
You may also trigger aggregation on-demand rather than setting the automatic
option, to run it from a background task for instance, by calling
`Post.aggregate_tags!`.

If you need to modify the criteria used for the aggregation you may do so as an option to 'taggable':
If you need to modify the criteria used for the aggregation you may do so as an
option to 'taggable':

```ruby
class Post
Expand All @@ -113,12 +123,14 @@ class Post
end
```

A full list of available options can be found at [the ruby driver API](http://api.mongodb.org/ruby/current/Mongo/Collection.html#map_reduce-instance_method) (consult the appropriate version).
A full list of available options can be found at [the ruby driver
API][mapreduce-doc] (consult the appropriate version).

Changing default separator
--------------------------

To change the default separator you may pass a `separator` argument to the macro:
To change the default separator you may pass a `separator` argument to the
macro:

```ruby
class Post
Expand All @@ -134,7 +146,16 @@ end

### TODO: ###

* Should subclasses output map/reduce aggregation results to their own collections?
* Should subclasses output map/reduce aggregation results to their own
collections?
* Perhaps implement operators, <<, etc. for full-on set semantics. See:
https://github.com/mlabs/mongoid_taggable/commit/13195805e110a7113b9f04710d9bade39440b63e


[Build Status]: https://secure.travis-ci.org/ches/mongoid_taggable.png?branch=integration
[travis-project]: http://travis-ci.org/ches/mongoid_taggable
[mapreduce-doc]: http://api.mongodb.org/ruby/current/Mongo/Collection.html#map_reduce-instance_method


<!-- vim: set tw=80 :-->

48 changes: 26 additions & 22 deletions lib/mongoid/taggable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ def define_tag_field_accessors(name)
end
alias_method_chain "#{name}=", :taggable

# Define value_before_type_cast method to format array in input field as comma separated list
# @see ActionView::Helpers::InstanceTag::value_before_type_cast
define_method "#{name}_before_type_cast" do
self.send(name).join("#{tags_separator} ") if self.send(name)
end

# Dynamically named class methods, for aggregation
(class << self; self; end).instance_eval do
# get an array with all defined tags for this model, this list returns
Expand All @@ -180,32 +186,30 @@ def define_tag_field_accessors(name)
end
end

module InstanceMethods
# Execute map/reduce operation to aggregate tag counts for document
# class, from the instance
def aggregate_tags!
options = self.class.tag_aggregation_options
options = options.call(self) if options.is_a?(Proc)

result = self.class.aggregate_tags!(options.clone)
# Execute map/reduce operation to aggregate tag counts for document
# class, from the instance
def aggregate_tags!
options = self.class.tag_aggregation_options
options = options.call(self) if options.is_a?(Proc)

if options[:save_as]
options[:save_as][:object].send(:"#{options[:save_as][:attribute].to_s}=",
format_aggregation_result(result))
options[:save_as][:object].save
end
result = self.class.aggregate_tags!(options.clone)

true
if options[:save_as]
options[:save_as][:object].send(:"#{options[:save_as][:attribute].to_s}=",
format_aggregation_result(result))
options[:save_as][:object].save
end

# De-duplicate tags, case-insensitively, but preserve case given first
def dedup_tags!
tags = read_attribute(tags_field)
tags = tags.reduce([]) do |uniques, tag|
uniques << tag unless uniques.map(&:downcase).include?(tag.downcase)
uniques
end
write_attribute(tags_field, tags)
true
end

# De-duplicate tags, case-insensitively, but preserve case given first
def dedup_tags!
tags = read_attribute(tags_field)
tags = tags.reduce([]) do |uniques, tag|
uniques << tag unless uniques.map(&:downcase).include?(tag.downcase)
uniques
end
write_attribute(tags_field, tags)
end
end
80 changes: 31 additions & 49 deletions spec/mongoid/taggable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

require File.join(File.dirname(__FILE__), %w[.. spec_helper])

class MyModel
include Mongoid::Document
include Mongoid::Taggable

field :attr
taggable
end

class Article
include Mongoid::Document
include Mongoid::Taggable

taggable :keywords, :default => []
end

class Editorial < Article
self.tags_separator = ' '
self.tag_aggregation = true
end

class Template
include Mongoid::Document
include Mongoid::Taggable
include Mongoid::Timestamps

taggable :aggregation => true
end

class Post
include Mongoid::Document
include Mongoid::Taggable

field :published, :type => Boolean
belongs_to_related :author

taggable :aggregation_options => {}
end

class Author
include Mongoid::Document

field :posts_with_weight, :type => Array
has_many_related :posts
end
require 'spec_helper'

describe Mongoid::Taggable do
context "saving tags" do
Expand Down Expand Up @@ -113,9 +68,12 @@ class Author
end

context "with unrecognized options to taggable" do
# NOTE: `defaults` apparently changed from returning a Hash to an Array in
# mongoid/mongoid@1b77d9cf09aa43c4a284b, so this spec fails on versions
# below 2.1.8 though the *setting* of options still actually works.
it "passes them to the Mongoid field definition" do
pending("I don't understand what this is testing.")
Article.defaults.should eq 'keywords' => []
Article.defaults.should eq ['keywords']
Article.fields['keywords'].options[:default].should eq []
end
end

Expand All @@ -126,6 +84,12 @@ class Author
article.keywords = "some,new,tag"
article.keywords.should == %w[some new tag]
end

describe "#keywords_before_type_cast" do
it "is defined" do
article.should respond_to(:keywords_before_type_cast)
end
end
end

context "changing separator" do
Expand Down Expand Up @@ -325,7 +289,18 @@ class Author
end
end

context "#self.tagged_with" do
# Perhaps a little white lie since we actually do store an array in Mongo, but
# it makes form fields "just work" with String lists of tags.
describe "#tags_before_type_cast" do
let(:model) { MyModel.new(:tags => %w[some new tag]) }
subject { model.tags_before_type_cast }

it "returns String representation of tags using separator" do
subject.should eq "some, new, tag"
end
end

describe ".tagged_with" do
let!(:models) do
[
MyModel.create!(:tags => "tag1,tag2,tag3"),
Expand Down Expand Up @@ -373,6 +348,13 @@ class Author
Article.keywords_with_weight.should include(['satire', 3])
Editorial.keywords_with_weight.should include(['satire', 3])
end

describe "#keywords_before_type_cast" do
it "uses subclass' configured separator" do
editorial.keywords = %w[some new tag]
editorial.keywords_before_type_cast.should eq "some new tag"
end
end
end

context "using taggable module along with other mongoid modules" do
Expand Down
Loading

0 comments on commit 6be91ca

Please sign in to comment.