categoryz3 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ ...