my_annotations 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rvmrc +1 -0
- data/AUTHORS.rdoc +5 -0
- data/CHANGELOG.rdoc +64 -0
- data/INDEX.rdoc +17 -0
- data/generators/annotations_migration/annotations_migration_generator.rb +32 -0
- data/generators/annotations_migration/templates/migration_v1.rb +60 -0
- data/generators/annotations_migration/templates/migration_v2.rb +9 -0
- data/generators/annotations_migration/templates/migration_v3.rb +74 -0
- data/generators/annotations_migration/templates/migration_v4.rb +13 -0
- data/install.rb +1 -0
- data/lib/annotations/acts_as_annotatable.rb +271 -0
- data/lib/annotations/acts_as_annotation_source.rb +117 -0
- data/lib/annotations/acts_as_annotation_value.rb +115 -0
- data/lib/annotations/config.rb +148 -0
- data/lib/annotations/routing.rb +8 -0
- data/lib/annotations/util.rb +82 -0
- data/lib/annotations_version_fu.rb +119 -0
- data/lib/app/controllers/annotations_controller.rb +162 -0
- data/lib/app/controllers/application_controller.rb +2 -0
- data/lib/app/helpers/application_helper.rb +2 -0
- data/lib/app/models/annotation.rb +413 -0
- data/lib/app/models/annotation_attribute.rb +37 -0
- data/lib/app/models/annotation_value_seed.rb +48 -0
- data/lib/app/models/number_value.rb +23 -0
- data/lib/app/models/text_value.rb +23 -0
- data/my_annotations.gemspec +4 -9
- data/rails/init.rb +8 -0
- data/test/acts_as_annotatable_test.rb +186 -0
- data/test/acts_as_annotation_source_test.rb +84 -0
- data/test/acts_as_annotation_value_test.rb +17 -0
- data/test/annotation_attribute_test.rb +22 -0
- data/test/annotation_test.rb +213 -0
- data/test/annotation_value_seed_test.rb +14 -0
- data/test/annotation_version_test.rb +39 -0
- data/test/annotations_controller_test.rb +27 -0
- data/test/app_root/app/controllers/application_controller.rb +9 -0
- data/test/app_root/app/models/book.rb +5 -0
- data/test/app_root/app/models/chapter.rb +5 -0
- data/test/app_root/app/models/group.rb +3 -0
- data/test/app_root/app/models/tag.rb +6 -0
- data/test/app_root/app/models/user.rb +3 -0
- data/test/app_root/app/views/annotations/edit.html.erb +12 -0
- data/test/app_root/app/views/annotations/index.html.erb +1 -0
- data/test/app_root/app/views/annotations/new.html.erb +11 -0
- data/test/app_root/app/views/annotations/show.html.erb +3 -0
- data/test/app_root/config/boot.rb +115 -0
- data/test/app_root/config/environment.rb +16 -0
- data/test/app_root/config/environments/mysql.rb +0 -0
- data/test/app_root/config/routes.rb +4 -0
- data/test/app_root/db/migrate/001_create_test_models.rb +38 -0
- data/test/app_root/db/migrate/002_annotations_migration_v1.rb +60 -0
- data/test/app_root/db/migrate/003_annotations_migration_v2.rb +9 -0
- data/test/app_root/db/migrate/004_annotations_migration_v3.rb +72 -0
- data/test/config_test.rb +383 -0
- data/test/fixtures/annotation_attributes.yml +49 -0
- data/test/fixtures/annotation_value_seeds.csv +16 -0
- data/test/fixtures/annotation_versions.yml +259 -0
- data/test/fixtures/annotations.yml +239 -0
- data/test/fixtures/books.yml +13 -0
- data/test/fixtures/chapters.yml +27 -0
- data/test/fixtures/groups.yml +7 -0
- data/test/fixtures/number_value_versions.csv +2 -0
- data/test/fixtures/number_values.csv +2 -0
- data/test/fixtures/text_value_versions.csv +35 -0
- data/test/fixtures/text_values.csv +35 -0
- data/test/fixtures/users.yml +8 -0
- data/test/number_value_version_test.rb +40 -0
- data/test/routing_test.rb +27 -0
- data/test/test_helper.rb +41 -0
- data/test/text_value_version_test.rb +40 -0
- metadata +77 -7
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use ree-1.8.7-2010.02@annotations
|
data/AUTHORS.rdoc
ADDED
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
= Changelog
|
2
|
+
|
3
|
+
== 0.4.1
|
4
|
+
* Add +Annotation::include_values+ as a named scope that can be used to potentially improve performance.
|
5
|
+
* Added +include_values+ optional argument (default: false) to as many finder methods. This allows you to specify whether value records must be included in the query results or not, and *may* be used to improve performance.
|
6
|
+
|
7
|
+
== 0.4.0
|
8
|
+
* New config option: +Annotations::Config.value_factories+ - support for value object generation factories per attribute name.
|
9
|
+
Example:
|
10
|
+
Annotations::Config.value_factories["tag"] = Proc.new { |v|
|
11
|
+
case v
|
12
|
+
when String, Symbol
|
13
|
+
Tag.find_or_create_by_name(v.to_s)
|
14
|
+
else
|
15
|
+
v
|
16
|
+
end
|
17
|
+
}
|
18
|
+
IMPORTANT: don't use explicit +return+s in your Proc otherwise it will cause the returning method to exit too!
|
19
|
+
* The process of generating/setting the annotation's actual value object has been changed to "lazy generate" the actual +value+ object of the annotation. This is now done before validation. NOTE: this still allows +Annotation#value+ (which has been overridden) to be set at any time, but the actual setting of the value association at the ActiveRecord level now happens later.
|
20
|
+
* The +process_value_adjustments+ code in the +Annotation+ model now happens BEFORE setting the value association. This prevents the value object from being modified after it's been set. NOTE: this does also mean that it will only run when provided with a String or Symbol.
|
21
|
+
* New config option: +Annotations::Config.valid_value_types+ - support for validation checks based on the class of the value object.
|
22
|
+
Example:
|
23
|
+
Annotations::Config::valid_value_types["tag"] = "Tag"
|
24
|
+
|
25
|
+
== 0.3.1
|
26
|
+
* Minor bugfixes
|
27
|
+
* +annotations_version_fu+ nows allows reloading of the versioned columns (needed if doing something during a migration).
|
28
|
+
|
29
|
+
== 0.3.0
|
30
|
+
* +acts_as_annotatable+ now requires you to specify an option - +:name_field+ - on the model that is becoming an annotatable.
|
31
|
+
* +acts_as_annotatable+ now exposes an +is_annotatable+ attribute to allow you to check if a model can act as an annotatable.
|
32
|
+
|
33
|
+
== 0.2.1
|
34
|
+
* Updated the routes to allow +requirements+ to be passed in.
|
35
|
+
|
36
|
+
== 0.2.0
|
37
|
+
*Main change*:
|
38
|
+
Annotation values are now polymorphic rather than just plain strings.
|
39
|
+
Some basic +act_as_annotation_value+ models have been introduced for this.
|
40
|
+
Note: this has affected all methods that take in or work with annotation values.
|
41
|
+
See below for further details.
|
42
|
+
|
43
|
+
* New mixin module: +acts_as_annotation_value+.
|
44
|
+
* New basic annotation value models: +TextValue+ and +NumberValue+ (but note that you can use any model as a value by specifying +acts_as_annotation_value+ on it).
|
45
|
+
* Removed +Annotation::find_annotatables_with_attribute_name_and_value+.
|
46
|
+
* Removed +Annotation::find_annotatables_with_attribute_names_and_values+.
|
47
|
+
* Removed +with_annotations_with_attribute_name_and_value+ in the +acts_as_annotatable+ module.
|
48
|
+
* +Annotations::Config::value_restrictions+ has been renamed to +Annotations::Config::content_restrictions+
|
49
|
+
* Latest migration version = v3
|
50
|
+
* NOTE: the new migration script will keep the old +value+ column data in a new +old_value+ column for EXISTING annotations only. This can be used for verification/text purposes.
|
51
|
+
|
52
|
+
== 0.1.1
|
53
|
+
* Added +identifier+ to +AnnotationAttribute+. This can be used to specify what ontology term / URI the attribute can be
|
54
|
+
uniquely identified using. See +AnnotationAttribute#before_validation+ for more information on how this identifier
|
55
|
+
will be generated if not specified manually.
|
56
|
+
* Changed the +annotations+ association in +act_as_annotation_source+ to +annotations_by+, to fix cases when a model has both
|
57
|
+
+acts_as_annotatable+ AND +acts_as_annotation_source+.
|
58
|
+
* Latest migration version = v2
|
59
|
+
|
60
|
+
== 0.1.0 (July 23rd 2009)
|
61
|
+
* Initial import from the BioCatalogue codebase.
|
62
|
+
* Improved documentation. See README.rdoc for more info on features and usage.
|
63
|
+
* Latest migration version = v1
|
64
|
+
|
data/INDEX.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= Annotations Plugin (for Ruby on Rails applications)
|
2
|
+
|
3
|
+
Original Author:: Jiten Bhagat (mailto:mail@jits.co.uk)
|
4
|
+
Copyright:: (c) 2008-2011, the University of Manchester and the European Bioinformatics Institute (EMBL-EBI)
|
5
|
+
License:: BSD
|
6
|
+
Version:: 0.4.1
|
7
|
+
|
8
|
+
For information on the plugin and to get started, see README.rdoc
|
9
|
+
|
10
|
+
For credits and origins, see AUTHORS.rdoc
|
11
|
+
|
12
|
+
For license, see LICENSE
|
13
|
+
|
14
|
+
For the latest updates, see CHANGELOG.rdoc
|
15
|
+
|
16
|
+
To run the tests in the plugin, see RUNNING_TESTS.rdoc
|
17
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class AnnotationsMigrationGenerator < Rails::Generator::Base
|
2
|
+
|
3
|
+
attr_accessor :version
|
4
|
+
|
5
|
+
def initialize(*runtime_args)
|
6
|
+
super(*runtime_args)
|
7
|
+
if @args[0].nil?
|
8
|
+
@version = "all"
|
9
|
+
else
|
10
|
+
@version = @args[0].downcase
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def manifest
|
15
|
+
record do |m|
|
16
|
+
if @version
|
17
|
+
if @version == "all"
|
18
|
+
Dir.chdir(File.join(File.dirname(__FILE__), "templates")) do
|
19
|
+
Dir.glob("*.rb").each do |f|
|
20
|
+
version = f.gsub(/.rb/, '').split('_')[1]
|
21
|
+
m.migration_template "migration_#{version}.rb", 'db/migrate', { :migration_file_name => "annotations_migration_#{version}" }
|
22
|
+
m.sleep 1 # So that the timestamps on the migration are not the same!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
m.migration_template "migration_#{@version}.rb", 'db/migrate', { :migration_file_name => "annotations_migration_#{@version}" }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class AnnotationsMigrationV1 < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :annotations, :force => true do |t|
|
4
|
+
t.string :source_type, :null => false
|
5
|
+
t.integer :source_id, :null => false
|
6
|
+
t.string :annotatable_type, :limit => 50, :null => false
|
7
|
+
t.integer :annotatable_id, :null => false
|
8
|
+
t.integer :attribute_id, :null => false
|
9
|
+
t.text :value, :limit => 20000, :null => false
|
10
|
+
t.string :value_type, :limit => 50, :null => false
|
11
|
+
t.integer :version, :null => false
|
12
|
+
t.integer :version_creator_id, :null => true
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :annotations, [ :source_type, :source_id ]
|
17
|
+
add_index :annotations, [ :annotatable_type, :annotatable_id ]
|
18
|
+
add_index :annotations, [ :attribute_id ]
|
19
|
+
|
20
|
+
create_table :annotation_versions, :force => true do |t|
|
21
|
+
t.integer :annotation_id, :null => false
|
22
|
+
t.integer :version, :null => false
|
23
|
+
t.integer :version_creator_id, :null => true
|
24
|
+
t.string :source_type, :null => false
|
25
|
+
t.integer :source_id, :null => false
|
26
|
+
t.string :annotatable_type, :limit => 50, :null => false
|
27
|
+
t.integer :annotatable_id, :null => false
|
28
|
+
t.integer :attribute_id, :null => false
|
29
|
+
t.text :value, :limit => 20000, :null => false
|
30
|
+
t.string :value_type, :limit => 50, :null => false
|
31
|
+
t.timestamps
|
32
|
+
end
|
33
|
+
|
34
|
+
add_index :annotation_versions, [ :annotation_id ]
|
35
|
+
|
36
|
+
create_table :annotation_attributes, :force => true do |t|
|
37
|
+
t.string :name, :null => false
|
38
|
+
|
39
|
+
t.timestamps
|
40
|
+
end
|
41
|
+
|
42
|
+
add_index :annotation_attributes, [ :name ]
|
43
|
+
|
44
|
+
create_table :annotation_value_seeds, :force => true do |t|
|
45
|
+
t.integer :attribute_id, :null => false
|
46
|
+
t.string :value, :null => false
|
47
|
+
|
48
|
+
t.timestamps
|
49
|
+
end
|
50
|
+
|
51
|
+
add_index :annotation_value_seeds, [ :attribute_id ]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.down
|
55
|
+
drop_table :annotations
|
56
|
+
drop_table :annotation_versions
|
57
|
+
drop_table :annotation_attributes
|
58
|
+
drop_table :annotation_value_seeds
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class AnnotationsMigrationV3 < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
|
4
|
+
change_table :annotations do |t|
|
5
|
+
t.rename :value, :old_value
|
6
|
+
t.remove :value_type
|
7
|
+
t.string :value_type, :limit => 50, :null => false, :default => "TextValue"
|
8
|
+
t.integer :value_id, :null => false, :default => 0
|
9
|
+
end
|
10
|
+
change_column :annotations, :old_value, :string, :null => true
|
11
|
+
add_index :annotations, [ :value_type, :value_id ]
|
12
|
+
|
13
|
+
change_table :annotation_versions do |t|
|
14
|
+
t.rename :value, :old_value
|
15
|
+
t.remove :value_type
|
16
|
+
t.string :value_type, :limit => 50, :null => false, :default => "TextValue"
|
17
|
+
t.integer :value_id, :null => false, :default => 0
|
18
|
+
end
|
19
|
+
change_column :annotation_versions, :old_value, :string, :null => true
|
20
|
+
|
21
|
+
create_table :text_values, :force => true do |t|
|
22
|
+
t.integer :version, :null => false
|
23
|
+
t.integer :version_creator_id, :null => true
|
24
|
+
t.text :text, :limit => 16777214, :null => false
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :text_value_versions, :force => true do |t|
|
29
|
+
t.integer :text_value_id, :null => false
|
30
|
+
t.integer :version, :null => false
|
31
|
+
t.integer :version_creator_id, :null => true
|
32
|
+
t.text :text, :limit => 16777214, :null => false
|
33
|
+
t.timestamps
|
34
|
+
end
|
35
|
+
add_index :text_value_versions, [ :text_value_id ]
|
36
|
+
|
37
|
+
create_table :number_values, :force => true do |t|
|
38
|
+
t.integer :version, :null => false
|
39
|
+
t.integer :version_creator_id, :null => true
|
40
|
+
t.integer :number, :null => false
|
41
|
+
t.timestamps
|
42
|
+
end
|
43
|
+
|
44
|
+
create_table :number_value_versions, :force => true do |t|
|
45
|
+
t.integer :number_value_id, :null => false
|
46
|
+
t.integer :version, :null => false
|
47
|
+
t.integer :version_creator_id, :null => true
|
48
|
+
t.integer :number, :null => false
|
49
|
+
t.timestamps
|
50
|
+
end
|
51
|
+
add_index :number_value_versions, [ :number_value_id ]
|
52
|
+
|
53
|
+
# Migrate existing annotations to the v3 db schema
|
54
|
+
#
|
55
|
+
# TODO: IMPORTANT: please check the comments and logic in
|
56
|
+
# this util method to see if it is what you want.
|
57
|
+
# If you need to change the behaviour, redefine it in your app.
|
58
|
+
Annotation::reset_column_information
|
59
|
+
Annotation::reload_versioned_columns_info
|
60
|
+
Annotations::Util::migrate_annotations_to_v3
|
61
|
+
|
62
|
+
change_table :annotation_value_seeds do |t|
|
63
|
+
t.rename :value, :old_value
|
64
|
+
t.string :value_type, :limit => 50, :null => false, :default => "FIXME"
|
65
|
+
t.integer :value_id, :null => false, :default => 0
|
66
|
+
end
|
67
|
+
change_column :annotation_value_seeds, :old_value, :string, :null => true
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.down
|
72
|
+
raise ActiveRecord::IrreversibleMigration.new
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class AnnotationsMigrationV4 < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
change_column :annotations,:version,:integer,:null=>true
|
4
|
+
change_column :text_values,:version,:integer,:null=>true
|
5
|
+
change_column :number_values,:version,:integer,:null=>true
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.down
|
9
|
+
change_column :annotations,:version,:integer,:null=>false
|
10
|
+
change_column :text_values,:version,:integer,:null=>false
|
11
|
+
change_column :number_values,:version,:integer,:null=>false
|
12
|
+
end
|
13
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# ActsAsAnnotatable
|
2
|
+
module Annotations
|
3
|
+
module Acts #:nodoc:
|
4
|
+
module Annotatable #:nodoc:
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def acts_as_annotatable(options)
|
12
|
+
cattr_accessor :annotatable_name_field, :is_annotatable
|
13
|
+
|
14
|
+
if options[:name_field].blank?
|
15
|
+
raise ArgumentError.new("Must specify the :name_field option that will be used as the field for the name")
|
16
|
+
end
|
17
|
+
|
18
|
+
self.annotatable_name_field = options[:name_field]
|
19
|
+
|
20
|
+
has_many :annotations,
|
21
|
+
:as => :annotatable,
|
22
|
+
:dependent => :destroy,
|
23
|
+
:order => 'updated_at ASC'
|
24
|
+
|
25
|
+
__send__ :extend, SingletonMethods
|
26
|
+
__send__ :include, InstanceMethods
|
27
|
+
|
28
|
+
self.is_annotatable = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Class methods added to the model that has been made acts_as_annotatable (ie: the mixin target class).
|
33
|
+
module SingletonMethods
|
34
|
+
# Helper finder to get all annotations for an object of the mixin annotatable type with the ID provided.
|
35
|
+
# This is the same as object.annotations with the added benefit that the object doesnt have to be loaded.
|
36
|
+
# E.g: Book.find_annotations_for(34) will give all annotations for the Book with ID 34.
|
37
|
+
def find_annotations_for(id, include_values=false)
|
38
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
|
39
|
+
|
40
|
+
options = {
|
41
|
+
:conditions => { :annotatable_type => obj_type,
|
42
|
+
:annotatable_id => id },
|
43
|
+
:order => "updated_at DESC"
|
44
|
+
}
|
45
|
+
|
46
|
+
options[:include] = [ :value ] if include_values
|
47
|
+
|
48
|
+
Annotation.find(:all, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Helper finder to get all annotations for all objects of the mixin annotatable type, by the source specified.
|
52
|
+
# E.g: Book.find_annotations_by('User', 10) will give all annotations for all Books by User with ID 10.
|
53
|
+
def find_annotations_by(source_type, source_id, include_values=false)
|
54
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
|
55
|
+
|
56
|
+
options = {
|
57
|
+
:conditions => { :annotatable_type => obj_type,
|
58
|
+
:source_type => source_type,
|
59
|
+
:source_id => source_id },
|
60
|
+
:order => "updated_at DESC"
|
61
|
+
}
|
62
|
+
|
63
|
+
options[:include] = [ :value ] if include_values
|
64
|
+
|
65
|
+
Annotation.find(:all, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# This module contains instance methods
|
70
|
+
module InstanceMethods
|
71
|
+
|
72
|
+
# Gets the name of the annotatable object
|
73
|
+
def annotatable_name
|
74
|
+
self.send(self.class.annotatable_name_field)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Helper method to get latest annotations
|
78
|
+
def latest_annotations(limit=nil, include_values=false)
|
79
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self.class).to_s
|
80
|
+
|
81
|
+
options = {
|
82
|
+
:conditions => { :annotatable_type => obj_type,
|
83
|
+
:annotatable_id => self.id },
|
84
|
+
:order => "updated_at DESC",
|
85
|
+
:limit => limit
|
86
|
+
}
|
87
|
+
|
88
|
+
options[:include] = [ :value ] if include_values
|
89
|
+
|
90
|
+
Annotation.find(:all, options)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Finder to get annotations with a specific attribute.
|
94
|
+
# The input parameter is the attribute name
|
95
|
+
# (MUST be a String representing the attribute's name).
|
96
|
+
def annotations_with_attribute(attrib, include_values=false)
|
97
|
+
return [] if attrib.blank?
|
98
|
+
|
99
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self.class).to_s
|
100
|
+
|
101
|
+
options = {
|
102
|
+
:joins => :attribute,
|
103
|
+
:conditions => { :annotatable_type => obj_type,
|
104
|
+
:annotatable_id => self.id,
|
105
|
+
:annotation_attributes => { :name => attrib.strip.downcase } },
|
106
|
+
:order => "updated_at DESC"
|
107
|
+
}
|
108
|
+
|
109
|
+
options[:include] = [ :value ] if include_values
|
110
|
+
|
111
|
+
Annotation.find(:all, options)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Same as the {obj}.annotations_with_attribute method (above) but
|
115
|
+
# takes in an array for attribute names to look for.
|
116
|
+
#
|
117
|
+
# NOTE (1): the argument to this method MUST be an Array of Strings.
|
118
|
+
def annotations_with_attributes(attribs, include_values=false)
|
119
|
+
return [] if attribs.blank?
|
120
|
+
|
121
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self.class).to_s
|
122
|
+
|
123
|
+
options = {
|
124
|
+
:joins => :attribute,
|
125
|
+
:conditions => { :annotatable_type => obj_type,
|
126
|
+
:annotatable_id => self.id,
|
127
|
+
:annotation_attributes => { :name => attribs } },
|
128
|
+
:order => "updated_at DESC"
|
129
|
+
}
|
130
|
+
|
131
|
+
options[:include] = [ :value ] if include_values
|
132
|
+
|
133
|
+
Annotation.find(:all, options)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Finder to get annotations with a specific attribute by a specific source.
|
137
|
+
#
|
138
|
+
# The first input parameter is the attribute name (MUST be a String representing the attribute's name).
|
139
|
+
# The second input is the source object.
|
140
|
+
def annotations_with_attribute_and_by_source(attrib, source, include_values=false)
|
141
|
+
return [] if attrib.blank? or source.nil?
|
142
|
+
|
143
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self.class).to_s
|
144
|
+
|
145
|
+
options = {
|
146
|
+
:joins => :attribute,
|
147
|
+
:conditions => { :annotatable_type => obj_type,
|
148
|
+
:annotatable_id => self.id,
|
149
|
+
:source_type => source.class.name,
|
150
|
+
:source_id => source.id,
|
151
|
+
:annotation_attributes => { :name => attrib.strip.downcase } },
|
152
|
+
:order => "updated_at DESC"
|
153
|
+
}
|
154
|
+
|
155
|
+
options[:include] = [ :value ] if include_values
|
156
|
+
|
157
|
+
Annotation.find(:all, options)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Finder to get all annotations on this object excluding those that
|
161
|
+
# have the attribute names specified.
|
162
|
+
#
|
163
|
+
# NOTE (1): the argument to this method MUST be an Array of Strings.
|
164
|
+
# NOTE (2): the returned records will be Read Only.
|
165
|
+
def all_annotations_excluding_attributes(attribs, include_values=false)
|
166
|
+
return [] if attribs.blank?
|
167
|
+
|
168
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self.class).to_s
|
169
|
+
|
170
|
+
options = {
|
171
|
+
:joins => :attribute,
|
172
|
+
:conditions => [ "`annotations`.`annotatable_type` = ? AND `annotations`.`annotatable_id` = ? AND `annotation_attributes`.`name` NOT IN (?)",
|
173
|
+
obj_type,
|
174
|
+
self.id,
|
175
|
+
attribs ],
|
176
|
+
:order => "`annotations`.`updated_at` DESC"
|
177
|
+
}
|
178
|
+
|
179
|
+
options[:include] = [ :value ] if include_values
|
180
|
+
|
181
|
+
Annotation.find(:all, options)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns the number of annotations on this annotatable object by the source type specified.
|
185
|
+
# "all" (case insensitive) can be provided to get all annotations regardless of source type.
|
186
|
+
# E.g.: book.count_annotations_by("User") or book.count_annotations_by("All")
|
187
|
+
def count_annotations_by(source_type_in)
|
188
|
+
if source_type_in == nil || source_type_in.downcase == "all"
|
189
|
+
return self.annotations.count
|
190
|
+
else
|
191
|
+
return self.annotations.count(:conditions => { :source_type => source_type_in })
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Use this method to create many annotations from a Hash of data.
|
196
|
+
# Arrays for Hash values will be converted to multiple annotations.
|
197
|
+
# Blank values (nil or empty string) will be ignored and thus annotations
|
198
|
+
# will not be created for them.
|
199
|
+
#
|
200
|
+
# Returns an array of Annotation objects of the annotations that were
|
201
|
+
# successfully created.
|
202
|
+
#
|
203
|
+
# Code example:
|
204
|
+
# -------------
|
205
|
+
# data = { "tag" => [ "tag1", "tag2", "tag3" ], "description" => "This is a book" }
|
206
|
+
# book.create_annotations(data, current_user)
|
207
|
+
def create_annotations(annotations_data, source)
|
208
|
+
anns = [ ]
|
209
|
+
|
210
|
+
annotations_data.each do |attrib, val|
|
211
|
+
unless val.blank?
|
212
|
+
val = [ val ].flatten
|
213
|
+
val.each do |val_inner|
|
214
|
+
unless val_inner.blank?
|
215
|
+
ann = self.annotations.new(:attribute_name => attrib,
|
216
|
+
:source_type => source.class.name,
|
217
|
+
:source_id => source.id)
|
218
|
+
|
219
|
+
ann.value = val_inner
|
220
|
+
ann.save
|
221
|
+
|
222
|
+
if ann && ann.valid?
|
223
|
+
anns << ann
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Reload annotations collection
|
231
|
+
self.annotations(true)
|
232
|
+
|
233
|
+
return anns
|
234
|
+
end
|
235
|
+
|
236
|
+
# When used with the default style (:simple), returns a Hash of the +annotations+ values
|
237
|
+
# grouped by attribute name.
|
238
|
+
#
|
239
|
+
# Example output:
|
240
|
+
# {
|
241
|
+
# "Summary" => "Something interesting happens",
|
242
|
+
# "length" => 345,
|
243
|
+
# "Title" => "Harry Potter and the Exploding Men's Locker Room",
|
244
|
+
# "Tag" => [ "amusing rhetoric", "wizadry" ],
|
245
|
+
# "rating" => "4/5"
|
246
|
+
# }
|
247
|
+
def annotations_hash(style=:simple)
|
248
|
+
h = { }
|
249
|
+
|
250
|
+
unless self.annotations.blank?
|
251
|
+
self.annotations.each do |a|
|
252
|
+
if h.has_key?(a.attribute_name)
|
253
|
+
case h[a.attribute_name]
|
254
|
+
when Array
|
255
|
+
h[a.attribute_name] << a.value_content
|
256
|
+
else
|
257
|
+
h[a.attribute_name] = [ h[a.attribute_name], a.value_content ]
|
258
|
+
end
|
259
|
+
else
|
260
|
+
h[a.attribute_name] = a.value_content
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
return h
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# ActsAsAnnotationSource
|
2
|
+
module Annotations
|
3
|
+
module Acts #:nodoc:
|
4
|
+
module AnnotationSource #:nodoc:
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def acts_as_annotation_source
|
12
|
+
has_many :annotations_by,
|
13
|
+
:class_name => "Annotation",
|
14
|
+
:as => :source,
|
15
|
+
:order => 'updated_at ASC'
|
16
|
+
|
17
|
+
__send__ :extend, SingletonMethods
|
18
|
+
__send__ :include, InstanceMethods
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Class methods added to the model that has been made acts_as_annotation_source (the mixin target class).
|
23
|
+
module SingletonMethods
|
24
|
+
# Helper finder to get all annotations for an object of the mixin source type with the ID provided.
|
25
|
+
# This is the same as +#annotations+ on the object, with the added benefit that the object doesnt have to be loaded.
|
26
|
+
# E.g: +User.find_annotations_by(10)+ will give all annotations by User with ID 34.
|
27
|
+
def annotations_by(id, include_values=false)
|
28
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
|
29
|
+
|
30
|
+
options = {
|
31
|
+
:conditions => { :source_type => obj_type,
|
32
|
+
:source_id => id },
|
33
|
+
:order => "updated_at DESC"
|
34
|
+
}
|
35
|
+
|
36
|
+
options[:include] = [ :value ] if include_values
|
37
|
+
|
38
|
+
Annotation.find(:all, options)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Helper finder to get all annotations for all objects of the mixin source type, for the annotatable object provided.
|
42
|
+
# E.g: +User.find_annotations_for('Book', 28)+ will give all annotations made by all Users for Book with ID 28.
|
43
|
+
def annotations_for(annotatable_type, annotatable_id, include_values=false)
|
44
|
+
obj_type = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
|
45
|
+
|
46
|
+
options = {
|
47
|
+
:conditions => { :source_type => obj_type,
|
48
|
+
:annotatable_type => annotatable_type,
|
49
|
+
:annotatable_id => annotatable_id },
|
50
|
+
:order => "updated_at DESC"
|
51
|
+
}
|
52
|
+
|
53
|
+
options[:include] = [ :value ] if include_values
|
54
|
+
|
55
|
+
Annotation.find(:all, options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# This module contains instance methods
|
60
|
+
module InstanceMethods
|
61
|
+
# Helper method to get latest annotations
|
62
|
+
def latest_annotations(limit=nil, include_values=false)
|
63
|
+
options = {
|
64
|
+
:conditions => { :source_type => self.class.name,
|
65
|
+
:source_id => id },
|
66
|
+
:order => "updated_at DESC",
|
67
|
+
:limit => limit
|
68
|
+
}
|
69
|
+
|
70
|
+
options[:include] = [ :value ] if include_values
|
71
|
+
|
72
|
+
Annotation.find(:all, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def annotation_source_name
|
76
|
+
%w{ preferred_name display_name title name }.each do |w|
|
77
|
+
return eval("self.#{w}") if self.respond_to?(w)
|
78
|
+
end
|
79
|
+
return "#{self.class.name}_#{self.id}"
|
80
|
+
end
|
81
|
+
|
82
|
+
# When used with the default style (:simple), returns a Hash of the +annotations_by+ values
|
83
|
+
# grouped by attribute name.
|
84
|
+
#
|
85
|
+
# Example output:
|
86
|
+
# {
|
87
|
+
# "Summary" => "Something interesting happens",
|
88
|
+
# "length" => 345,
|
89
|
+
# "Title" => "Harry Potter and the Exploding Men's Locker Room",
|
90
|
+
# "Tag" => [ "amusing rhetoric", "wizadry" ],
|
91
|
+
# "rating" => "4/5"
|
92
|
+
# }
|
93
|
+
def annotations_by_hash(style=:simple)
|
94
|
+
h = { }
|
95
|
+
|
96
|
+
unless self.annotations_by.blank?
|
97
|
+
self.annotations_by.each do |a|
|
98
|
+
if h.has_key?(a.attribute_name)
|
99
|
+
case h[a.attribute_name]
|
100
|
+
when Array
|
101
|
+
h[a.attribute_name] << a.value_content
|
102
|
+
else
|
103
|
+
h[a.attribute_name] = [ h[a.attribute_name], a.value_content ]
|
104
|
+
end
|
105
|
+
else
|
106
|
+
h[a.attribute_name] = a.value_content
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
return h
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|