my_annotations 0.5.0 → 0.5.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 +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
|