acts_as_organizable 0.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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/acts_as_organizable.gemspec +19 -0
- data/init.rb +1 -0
- data/lib/acts_as_organizable.rb +133 -0
- data/lib/acts_as_organizable/core_ext/string.rb +5 -0
- data/lib/acts_as_organizable/tag.rb +15 -0
- data/lib/acts_as_organizable/tagging.rb +5 -0
- data/lib/acts_as_organizable/version.rb +3 -0
- data/lib/generators/acts_as_organizable/categorizable_migration_generator.rb +18 -0
- data/lib/generators/acts_as_organizable/taggable_migration_generator.rb +18 -0
- data/lib/generators/templates/create_taggables.rb +26 -0
- metadata +78 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "acts_as_organizable/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "acts_as_organizable"
|
7
|
+
s.version = ActsAsOrganizable::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Mike Hansen"]
|
10
|
+
s.email = ["mhansen@pathlightmedia.com"]
|
11
|
+
s.summary = %q{Simpler tagging.}
|
12
|
+
|
13
|
+
s.rubyforge_project = "acts_as_organizable"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'acts_as_organizable'
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# To find a person's available tags for the object we go through owned_tags, which pulls uniq items from join table
|
2
|
+
|
3
|
+
require "acts_as_organizable/version"
|
4
|
+
path = File.expand_path(File.dirname(__FILE__))
|
5
|
+
$LOAD_PATH << path unless $LOAD_PATH.include?(path)
|
6
|
+
require 'acts_as_organizable/tag'
|
7
|
+
require 'acts_as_organizable/tagging'
|
8
|
+
require 'acts_as_organizable/core_ext/string'
|
9
|
+
|
10
|
+
module ActsAsOrganizable
|
11
|
+
class TagList < Array
|
12
|
+
cattr_accessor :delimiter
|
13
|
+
@@delimiter = ','
|
14
|
+
|
15
|
+
def initialize(list)
|
16
|
+
list = list.is_a?(Array) ? list : list.split(@@delimiter).collect(&:strip).reject(&:blank?)
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
join(@@delimiter)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ActiveRecordExtension
|
26
|
+
def acts_as_organizable(*kinds)
|
27
|
+
# Example: acts_as_organizable :tags, :languages
|
28
|
+
class_inheritable_accessor :tag_kinds
|
29
|
+
self.tag_kinds = kinds.map(&:to_s).map(&:singularize)
|
30
|
+
self.tag_kinds << :tag if kinds.empty?
|
31
|
+
|
32
|
+
include ActsAsOrganizable::TaggableMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
def acts_as_tagger(opts={})
|
36
|
+
class_eval do
|
37
|
+
has_many :owned_tags, :through => :owned_taggings, :source => :tag, :class_name => "Tag", :uniq => true
|
38
|
+
has_many :owned_taggings, opts.merge(:as => :owner, :dependent => :destroy, :include => :tag, :class_name => "Tagging")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module TaggableMethods
|
44
|
+
def self.included(klass)
|
45
|
+
klass.class_eval do
|
46
|
+
include ActsAsOrganizable::TaggableMethods::InstanceMethods
|
47
|
+
|
48
|
+
has_many :taggings, :as => :taggable, :dependent => :destroy
|
49
|
+
has_many :tags, :through => :taggings
|
50
|
+
before_save :cache_tags
|
51
|
+
|
52
|
+
tag_kinds.each do |k|
|
53
|
+
# Example: language gets passed in and becomes language_list
|
54
|
+
define_method("#{k}_list") { get_tag_list(k) }
|
55
|
+
# this is expexting language_list = "spanish, italian"
|
56
|
+
define_method("#{k}_list=") { |new_list| set_tag_list(k, new_list.clean_up_tags) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module InstanceMethods
|
62
|
+
def set_tag_list(kind, list)
|
63
|
+
tag_list = TagList.new(list) # ["spanish", "italian"]
|
64
|
+
# @language_list = ["spanish", "italian"]
|
65
|
+
instance_variable_set(tag_list_name_for_kind(kind), tag_list)
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_tag_list(kind)
|
69
|
+
# set instance variable unless it exists
|
70
|
+
set_tag_list(kind, tags.of_kind(kind).map(&:name)) if tag_list_instance_variable(kind).nil?
|
71
|
+
tag_list_instance_variable(kind)
|
72
|
+
end
|
73
|
+
|
74
|
+
def save_with_tags(tag_owner = nil)
|
75
|
+
self.save # save the parent object first
|
76
|
+
tag_kinds.each do |tag_kind|
|
77
|
+
delete_unused_tags(tag_kind)
|
78
|
+
create_taggings(tag_kind, tag_owner)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
def tag_list_name_for_kind(kind)
|
84
|
+
"@#{kind}_list"
|
85
|
+
end
|
86
|
+
|
87
|
+
def tag_list_instance_variable(kind)
|
88
|
+
instance_variable_get(tag_list_name_for_kind(kind))
|
89
|
+
end
|
90
|
+
|
91
|
+
def delete_unused_tags(tag_kind)
|
92
|
+
# if the new list does not have the previous tag, kill the old tag associated to the object
|
93
|
+
tags.of_kind(tag_kind).each { |t| tags.delete(t) unless get_tag_list(tag_kind).include?(t.name) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_taggings(tag_kind, tag_owner)
|
97
|
+
# get tags passed in, such as ["russian", "english"] from @language_list
|
98
|
+
previous_tags = tags.of_kind(tag_kind).map(&:name)
|
99
|
+
get_tag_list(tag_kind).each do |tag_name|
|
100
|
+
tag = Tag.find_or_create_with_name_like_and_kind(tag_name, tag_kind) unless previous_tags.include?(tag_name)
|
101
|
+
if tag_owner
|
102
|
+
# save taggings to tag_owner passed in explicitly
|
103
|
+
# NOTE: this includes visitors - they must be passed in explicitly
|
104
|
+
taggings.create!(:tag_id => tag.id, :owner => tag_owner)
|
105
|
+
# look for user or owner of object to attach to tagging
|
106
|
+
elsif user = (self.respond_to?(:owner) && self.try(:owner)) || (self.respond_to?(:user) && self.try(:user))
|
107
|
+
taggings.create!(:tag_id => tag.id, :owner => user)
|
108
|
+
else
|
109
|
+
raise "No user asociated to tagging. Please specify one."
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def cache_tags
|
115
|
+
tag_kinds.each do |tag_kind|
|
116
|
+
t = tag_kind.to_s
|
117
|
+
if self.class.column_names.include?("cached_#{t}_list")
|
118
|
+
list = get_tag_list(tag_kind).join(", ")
|
119
|
+
self["cached_#{t}_list"] = list
|
120
|
+
else
|
121
|
+
logger.info("
|
122
|
+
************************************************************************************************
|
123
|
+
You should consider adding cached_#{t}_list to your #{t} for performance benefits!
|
124
|
+
************************************************************************************************
|
125
|
+
")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
ActiveRecord::Base.send(:extend, ActsAsOrganizable::ActiveRecordExtension)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Tag < ActiveRecord::Base
|
2
|
+
class << self
|
3
|
+
def find_or_create_with_name_like_and_kind(name, kind)
|
4
|
+
with_name_like_and_kind(name, kind).first || create!(:name => name, :kind => kind)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
has_many :taggings, :dependent => :destroy
|
9
|
+
|
10
|
+
validates_presence_of :name
|
11
|
+
validates_uniqueness_of :name, :scope => :kind
|
12
|
+
|
13
|
+
scope :with_name_like_and_kind, lambda { |name, kind| { :conditions => ["name like ? AND kind = ?", name, kind] } }
|
14
|
+
scope :of_kind, lambda { |kind| { :conditions => {:kind => kind} } }
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class ActsAsOrganizable::CategorizableMigrationGenerator < Rails::Generators::Base
|
4
|
+
def self.source_root
|
5
|
+
# source root must return path to where templates are stored
|
6
|
+
@source_root ||= File.join(File.dirname(__FILE__), 'templates')
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def self.next_migration_number(path)
|
11
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_migration_file
|
15
|
+
template 'categorizable_migration.rb', "db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_create_categorizables.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class ActsAsOrganizable::TaggableMigrationGenerator < Rails::Generators::Base
|
4
|
+
def self.source_root
|
5
|
+
# source root must return path to where templates are stored
|
6
|
+
@source_root ||= File.join(File.dirname(__FILE__), '../templates')
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def self.next_migration_number(path)
|
11
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_migration_file
|
15
|
+
template 'create_taggables.rb', "db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_create_taggables.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class CreateTaggables < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :tags do |t|
|
4
|
+
t.string :name
|
5
|
+
t.string :kind
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :taggings do |t|
|
10
|
+
t.integer :tag_id
|
11
|
+
t.references :taggable, :polymorphic => true
|
12
|
+
t.references :owner, :polymorphic => true
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :tags, [:name, :kind]
|
17
|
+
add_index :taggings, [:tag_id]
|
18
|
+
add_index :taggings, [:taggable_id, :taggable_type]
|
19
|
+
add_index :taggings, [:owner_id, :owner_type]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.down
|
23
|
+
drop_table :taggings
|
24
|
+
drop_table :tags
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_organizable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mike Hansen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-28 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email:
|
23
|
+
- mhansen@pathlightmedia.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- Gemfile
|
33
|
+
- Rakefile
|
34
|
+
- acts_as_organizable.gemspec
|
35
|
+
- init.rb
|
36
|
+
- lib/acts_as_organizable.rb
|
37
|
+
- lib/acts_as_organizable/core_ext/string.rb
|
38
|
+
- lib/acts_as_organizable/tag.rb
|
39
|
+
- lib/acts_as_organizable/tagging.rb
|
40
|
+
- lib/acts_as_organizable/version.rb
|
41
|
+
- lib/generators/acts_as_organizable/categorizable_migration_generator.rb
|
42
|
+
- lib/generators/acts_as_organizable/taggable_migration_generator.rb
|
43
|
+
- lib/generators/templates/create_taggables.rb
|
44
|
+
homepage:
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project: acts_as_organizable
|
73
|
+
rubygems_version: 1.8.5
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Simpler tagging.
|
77
|
+
test_files: []
|
78
|
+
|