categoryz3 0.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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,112 @@
1
+ = Categoryz3
2
+ {<img src="https://secure.travis-ci.org/tscolari/categoryz3.png" />}[http://travis-ci.org/tscolari/categoryz3]
3
+ {<img src="https://gemnasium.com/tscolari/categoryz3.png" />}[https://gemnasium.com/tscolari/categoryz3]
4
+ {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/tscolari/categoryz3]
5
+
6
+ Simple categorization to ActiveRecord models.
7
+
8
+ Works like a simple tagging system, but instead of tags it has categories, and categories may have an ilimited level of subcategories.
9
+
10
+ Using an auxiliary table, it's easy and fast to fetch items from any category, including all level of subcategories it may have.
11
+
12
+ == Motivation
13
+
14
+ genres_category = Categoryz3::Category.create(name: 'genres')
15
+ horror_category = Categoryz3::Category.create(name: 'horror', parent: genres_category)
16
+ cult_horror_category = Categoryz3::Category.create(name: 'horror', parent: horror_category)
17
+ action_category = Categoryz3::Category.create(name: 'horror', parent: genres_category)
18
+
19
+ horror_movie = Movie.find(1)
20
+ horror_movie.category = cult_horror_category
21
+ horror_movie.categories
22
+ #=> [cult_horror_category]
23
+
24
+ action_movie = Movie.find(2)
25
+ action_movie.category = action_category
26
+
27
+ Movie.inside_category(genres_category).all
28
+ #=> [horror_movie, action_movie]
29
+ Movie.inside_category(horror_category).all
30
+ #=> [horror_movie]
31
+ Movie.inside_category(cult_horror_category).all
32
+ #=> [horror_movie]
33
+
34
+ horror_category.path
35
+ #=> [movie_category, horror_category]
36
+
37
+ == Installation
38
+
39
+ Insert the gem in your Gemfile:
40
+
41
+ gem 'categoryz3'
42
+
43
+ Generate and run the migrations:
44
+
45
+ rails g categoryz3:migrations
46
+ rake db:migrate
47
+
48
+ Include the categorizable module in the models you want to categorize:
49
+
50
+ class Article < ActiveRecord::Base
51
+ include Categoryz3::Categorizable
52
+ end
53
+
54
+ == Usage
55
+
56
+ === Listing categories from an object
57
+
58
+ You can use the method `categories` to list all categories from a model:
59
+
60
+ model.categories
61
+ #=> [category1, category2]
62
+
63
+ You can also use the `categories_list`, this will return the categories ids:
64
+
65
+ model.categories_list
66
+ #=> "1, 2"
67
+
68
+ === Adding categories to an object
69
+
70
+ There are 2 ways for adding categories to an object:
71
+
72
+ model.category = category
73
+ model.category = [category1, category2, category3]
74
+ # alias_method :categories=, :category=
75
+
76
+ Replaces the object categories for the given ones.
77
+
78
+ model.categories_list = "1, 2, 3"
79
+
80
+ Replaces the object categories for the ones with ids 1, 2 and 3.
81
+
82
+ === Removing categories from an object
83
+
84
+ To remove a category from the model, you can use the `remove_category` method:
85
+
86
+ model.remove_category category
87
+ model.remove_categories category1, category2
88
+
89
+ === Category Path
90
+
91
+ Lists all the categories in the path, from the root one, to the category:
92
+
93
+ category.path
94
+ #=> [root_category, some_subcategory, another_subcategory, category]
95
+
96
+ === Categorizable Scopes
97
+
98
+ ==== inside_category
99
+
100
+ Lists all objects that belongs to the category or any subcategory of it
101
+
102
+ Article.inside_category(category)
103
+
104
+ ==== having_category
105
+
106
+ Lists all objects that belongs to the category. (No subcategory objects).
107
+
108
+ Article.having_category(category)
109
+
110
+ = License
111
+
112
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Categoryz3'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
@@ -0,0 +1,30 @@
1
+ module Categoryz3
2
+ class Category < ActiveRecord::Base
3
+ belongs_to :parent , class_name: 'Categoryz3::Category' , inverse_of: :children
4
+ has_many :children , class_name: 'Categoryz3::Category' , foreign_key: :parent_id , inverse_of: :parent , dependent: :destroy
5
+ has_many :direct_items , class_name: 'Categoryz3::Item' , inverse_of: :category , dependent: :destroy
6
+ has_many :child_items , class_name: 'Categoryz3::ChildItem' , inverse_of: :category , dependent: :destroy
7
+ validates :name , presence: true
8
+
9
+ scope :parent_categories, -> { where(parent_id: nil) }
10
+ attr_accessible :name, :parent
11
+
12
+ # Public: Returns the full categories path from the root category until this category
13
+ #
14
+ # Example:
15
+ #
16
+ # subcategory3.path
17
+ # #=> [category, subcategory1, subcategory2, subcategory3]
18
+ #
19
+ def path
20
+ return @path_array if @path_array
21
+ parent_category = self
22
+ @path_array = [parent_category]
23
+ while (parent_category = parent_category.parent) != nil do
24
+ @path_array.insert(0, parent_category)
25
+ end
26
+ @path_array
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module Categoryz3
2
+ class ChildItem < ActiveRecord::Base
3
+ belongs_to :category, inverse_of: :child_items
4
+ belongs_to :master_item, class_name: 'Categoryz3::Item', inverse_of: :child_items
5
+ belongs_to :categorizable, polymorphic: true
6
+
7
+ validates :category, :master_item, :categorizable, presence: true
8
+ validates :category_id, uniqueness: { scope: [:categorizable_type, :categorizable_id] }
9
+ validates_with Categoryz3::Validators::ChildItemCategoryValidator
10
+ attr_accessible :master_item, :categorizable
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module Categoryz3
2
+ class Item < ActiveRecord::Base
3
+ belongs_to :category, inverse_of: :direct_items
4
+ belongs_to :categorizable, polymorphic: true
5
+ has_many :child_items, foreign_key: 'master_item_id', inverse_of: :master_item, dependent: :destroy
6
+
7
+ validates :category, :categorizable, presence: true
8
+ validates :category_id, uniqueness: { scope: [:categorizable_type, :categorizable_id] }
9
+
10
+ after_create :create_child_items
11
+ attr_accessible :categorizable
12
+
13
+ private
14
+
15
+ # Private: Creates a child item for the category
16
+ #
17
+ def create_child_item_for_category(category)
18
+ category.child_items.create(categorizable: self.categorizable, master_item: self)
19
+ end
20
+
21
+ # Private: Create a child_item for each subcategory the category from this item have
22
+ # Also creates a child_item for this category
23
+ #
24
+ def create_child_items
25
+ category.path.each do |category|
26
+ create_child_item_for_category category
27
+ end
28
+ end
29
+
30
+ end
31
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Categoryz3::Engine.routes.draw do
2
+ end
@@ -0,0 +1,14 @@
1
+ class CreateCategoryz3Categories < ActiveRecord::Migration
2
+ def change
3
+ create_table :categoryz3_categories do |t|
4
+ t.string :name
5
+ t.references :parent
6
+ t.integer :items_count , default: 0
7
+ t.integer :child_items_count , default: 0
8
+ t.integer :childrens_count , default: 0
9
+ t.timestamps
10
+ end
11
+ add_index :categoryz3_categories, :parent_id
12
+ add_index :categoryz3_categories, :name
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ class CreateCategoryz3Items < ActiveRecord::Migration
2
+ def change
3
+ create_table :categoryz3_items do |t|
4
+ t.references :category, null: false
5
+ t.references :categorizable, polymorphic: true, null: false
6
+ t.timestamps
7
+ end
8
+ add_index :categoryz3_items, [:category_id, :categorizable_type, :categorizable_id], unique: true, name: 'items_unq_idx'
9
+ add_index :categoryz3_items, [:category_id, :created_at]
10
+ add_index :categoryz3_items, [:categorizable_type, :categorizable_id], name: 'items_categorizable_idx'
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ class CreateCategoryz3ChildItems < ActiveRecord::Migration
2
+ def change
3
+ create_table :categoryz3_child_items do |t|
4
+ t.references :category, null: false
5
+ t.references :categorizable, polymorphic: true, null: false
6
+ t.references :master_item, null: false
7
+ t.timestamps
8
+ end
9
+ add_index :categoryz3_child_items, [:category_id, :created_at]
10
+ add_index :categoryz3_child_items, [:category_id, :categorizable_type, :categorizable_id], unique: true, name: 'child_items_unq_idx'
11
+ add_index :categoryz3_child_items, [:categorizable_type, :categorizable_id], name: 'child_items_categorizable_idx'
12
+ add_index :categoryz3_child_items, [:master_item_id]
13
+ end
14
+ end
@@ -0,0 +1,79 @@
1
+ module Categoryz3
2
+ # Adds the categorizable behavior to models
3
+ #
4
+ # This will give the model 2 scopes:
5
+ #
6
+ # * inside_category(category) : Filter objects that belongs to the category or any of it's subcategories
7
+ # * having_category(category) : Filter objects that belongs only to the category
8
+ #
9
+ # And 2 new relations:
10
+ #
11
+ # * direct_category_items : This maps the model to the categories it is directly linked
12
+ # * child_category_items : This is an auxiliary relation to fast map the objet to any subcategory
13
+ #
14
+ module Categorizable
15
+ extend ActiveSupport::Concern
16
+ included do
17
+ has_many :direct_category_items , class_name: 'Categoryz3::Item' , as: :categorizable
18
+ has_many :child_category_items , class_name: 'Categoryz3::ChildItem' , as: :categorizable
19
+ scope :inside_category , ->(category) { joins(:child_category_items).where('categoryz3_child_items.category_id = ?' , category.id) }
20
+ scope :having_category , ->(category) { joins(:direct_category_items).where('categoryz3_items.category_id = ?' , category.id) }
21
+ end
22
+
23
+ # Public: List all object categories
24
+ # Categories are retrieved from direct_category_items association, no subcategories included
25
+ #
26
+ def categories
27
+ return @categories if @categories
28
+ category_ids = direct_category_items.all.map { |category| category.id }
29
+ @categories = Categoryz3::Category.where(id: category_ids)
30
+ end
31
+
32
+ # Public: Adds a category, or categories, to the model
33
+ #
34
+ # Examples:
35
+ #
36
+ # categorizable_object.category = dummy_category
37
+ # categorizable_object.categories = [dummy_category1, dummy_category2]
38
+ #
39
+ def category=(categories)
40
+ self.direct_category_items.destroy_all
41
+ [categories].flatten.each do |category|
42
+ category.direct_items.create(categorizable: self) if category
43
+ end
44
+ @categories = nil
45
+ end
46
+ alias_method :categories=, :category=
47
+
48
+ # Public: Removes a category, or categories, from the model
49
+ #
50
+ # Examples:
51
+ #
52
+ # categorizable_object.remove_category dummy_category
53
+ # categorizable_object.remove_categories dummy_category1, dummy_category2
54
+ #
55
+ def remove_category(*categories)
56
+ categories.each do |category|
57
+ direct_category_items.where(category_id: category).destroy_all
58
+ end
59
+ @categories = nil
60
+ end
61
+ alias_method :remove_categories, :remove_category
62
+
63
+ # Public: Returns the id list of the categories linked to the model
64
+ #
65
+ def categories_list
66
+ categories.map(&:id).join(", ")
67
+ end
68
+
69
+ # Public: Accepts an array of category ids as parameter and adds all
70
+ # of them to the model
71
+ #
72
+ def categories_list=(ids)
73
+ self.categories = ids.split(",").map do |id|
74
+ Category.where(id: id).first
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,5 @@
1
+ module Categoryz3
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Categoryz3
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Categoryz3
2
+ module Validators
3
+ class ChildItemCategoryValidator < ActiveModel::Validator
4
+ def validate(record)
5
+ unless record.master_item.category.path.map{ |category| category.id }.include?(record.category_id)
6
+ record.errors[:category] << "Must match with master_item.category"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Categoryz3
2
+ VERSION = "0.1"
3
+ end
data/lib/categoryz3.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "categoryz3/engine"
2
+ require "categoryz3/categorizable"
3
+ require "categoryz3/validators/child_item_category_validator"
4
+
5
+ module Categoryz3
6
+ end
7
+
8
+
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate migrations Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,22 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class Categoryz3::MigrationsGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+ source_root File.expand_path('../../../../../db/migrate', __FILE__)
7
+
8
+ def copy_migrations
9
+ migration_template "20121003060539_create_categoryz3_categories.rb", "db/migrate/create_categoryz3_categories.rb"
10
+ migration_template "20121003060933_create_categoryz3_items.rb", "db/migrate/create_categoryz3_items.rb"
11
+ migration_template "20121003061056_create_categoryz3_child_items.rb", "db/migrate/create_categoryz3_child_items.rb"
12
+ end
13
+
14
+ def self.next_migration_number( dirname )
15
+ next_migration_number = current_migration_number(dirname) + 1
16
+ if ActiveRecord::Base.timestamped_migrations
17
+ [Time.now.utc.strftime("%Y%m%d%H%M%S%6N"), "%.20d" % next_migration_number].max
18
+ else
19
+ "%.3d" % next_migration_number
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :categoryz3 do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: categoryz3
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: '0.1'
6
+ platform: ruby
7
+ authors:
8
+ - Tiago Scolari
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: 3.2.8
21
+ none: false
22
+ requirement: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.8
27
+ none: false
28
+ prerelease: false
29
+ type: :runtime
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec-rails
32
+ version_requirements: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ! '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ none: false
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ none: false
44
+ prerelease: false
45
+ type: :development
46
+ - !ruby/object:Gem::Dependency
47
+ name: database_cleaner
48
+ version_requirements: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ none: false
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ none: false
60
+ prerelease: false
61
+ type: :development
62
+ - !ruby/object:Gem::Dependency
63
+ name: factory_girl_rails
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ none: false
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ none: false
76
+ prerelease: false
77
+ type: :development
78
+ - !ruby/object:Gem::Dependency
79
+ name: ffaker
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ none: false
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ none: false
92
+ prerelease: false
93
+ type: :development
94
+ description: Works like a simple tagging system, but instead of tags it has categories, and categories may have an ilimited level of subcategories.
95
+ email:
96
+ - tscolari@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - app/models/categoryz3/category.rb
102
+ - app/models/categoryz3/child_item.rb
103
+ - app/models/categoryz3/item.rb
104
+ - config/routes.rb
105
+ - db/migrate/20121003060539_create_categoryz3_categories.rb
106
+ - db/migrate/20121003060933_create_categoryz3_items.rb
107
+ - db/migrate/20121003061056_create_categoryz3_child_items.rb
108
+ - lib/categoryz3.rb
109
+ - lib/categoryz3/categorizable.rb
110
+ - lib/categoryz3/engine.rb
111
+ - lib/categoryz3/version.rb
112
+ - lib/categoryz3/validators/child_item_category_validator.rb
113
+ - lib/generators/categoryz3/migrations/migrations_generator.rb
114
+ - lib/generators/categoryz3/migrations/USAGE
115
+ - lib/tasks/categoryz3_tasks.rake
116
+ - MIT-LICENSE
117
+ - Rakefile
118
+ - README.rdoc
119
+ homepage: https://github.com/tscolari/categoryz3
120
+ licenses: []
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ segments:
130
+ - 0
131
+ hash: 2
132
+ version: '0'
133
+ none: false
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ segments:
139
+ - 0
140
+ hash: 2
141
+ version: '0'
142
+ none: false
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 1.8.24
146
+ signing_key:
147
+ specification_version: 3
148
+ summary: Simple categorization to ActiveRecord models.
149
+ test_files: []
150
+ ...