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.
Files changed (72) hide show
  1. data/.gitignore +6 -0
  2. data/.rvmrc +1 -0
  3. data/AUTHORS.rdoc +5 -0
  4. data/CHANGELOG.rdoc +64 -0
  5. data/INDEX.rdoc +17 -0
  6. data/generators/annotations_migration/annotations_migration_generator.rb +32 -0
  7. data/generators/annotations_migration/templates/migration_v1.rb +60 -0
  8. data/generators/annotations_migration/templates/migration_v2.rb +9 -0
  9. data/generators/annotations_migration/templates/migration_v3.rb +74 -0
  10. data/generators/annotations_migration/templates/migration_v4.rb +13 -0
  11. data/install.rb +1 -0
  12. data/lib/annotations/acts_as_annotatable.rb +271 -0
  13. data/lib/annotations/acts_as_annotation_source.rb +117 -0
  14. data/lib/annotations/acts_as_annotation_value.rb +115 -0
  15. data/lib/annotations/config.rb +148 -0
  16. data/lib/annotations/routing.rb +8 -0
  17. data/lib/annotations/util.rb +82 -0
  18. data/lib/annotations_version_fu.rb +119 -0
  19. data/lib/app/controllers/annotations_controller.rb +162 -0
  20. data/lib/app/controllers/application_controller.rb +2 -0
  21. data/lib/app/helpers/application_helper.rb +2 -0
  22. data/lib/app/models/annotation.rb +413 -0
  23. data/lib/app/models/annotation_attribute.rb +37 -0
  24. data/lib/app/models/annotation_value_seed.rb +48 -0
  25. data/lib/app/models/number_value.rb +23 -0
  26. data/lib/app/models/text_value.rb +23 -0
  27. data/my_annotations.gemspec +4 -9
  28. data/rails/init.rb +8 -0
  29. data/test/acts_as_annotatable_test.rb +186 -0
  30. data/test/acts_as_annotation_source_test.rb +84 -0
  31. data/test/acts_as_annotation_value_test.rb +17 -0
  32. data/test/annotation_attribute_test.rb +22 -0
  33. data/test/annotation_test.rb +213 -0
  34. data/test/annotation_value_seed_test.rb +14 -0
  35. data/test/annotation_version_test.rb +39 -0
  36. data/test/annotations_controller_test.rb +27 -0
  37. data/test/app_root/app/controllers/application_controller.rb +9 -0
  38. data/test/app_root/app/models/book.rb +5 -0
  39. data/test/app_root/app/models/chapter.rb +5 -0
  40. data/test/app_root/app/models/group.rb +3 -0
  41. data/test/app_root/app/models/tag.rb +6 -0
  42. data/test/app_root/app/models/user.rb +3 -0
  43. data/test/app_root/app/views/annotations/edit.html.erb +12 -0
  44. data/test/app_root/app/views/annotations/index.html.erb +1 -0
  45. data/test/app_root/app/views/annotations/new.html.erb +11 -0
  46. data/test/app_root/app/views/annotations/show.html.erb +3 -0
  47. data/test/app_root/config/boot.rb +115 -0
  48. data/test/app_root/config/environment.rb +16 -0
  49. data/test/app_root/config/environments/mysql.rb +0 -0
  50. data/test/app_root/config/routes.rb +4 -0
  51. data/test/app_root/db/migrate/001_create_test_models.rb +38 -0
  52. data/test/app_root/db/migrate/002_annotations_migration_v1.rb +60 -0
  53. data/test/app_root/db/migrate/003_annotations_migration_v2.rb +9 -0
  54. data/test/app_root/db/migrate/004_annotations_migration_v3.rb +72 -0
  55. data/test/config_test.rb +383 -0
  56. data/test/fixtures/annotation_attributes.yml +49 -0
  57. data/test/fixtures/annotation_value_seeds.csv +16 -0
  58. data/test/fixtures/annotation_versions.yml +259 -0
  59. data/test/fixtures/annotations.yml +239 -0
  60. data/test/fixtures/books.yml +13 -0
  61. data/test/fixtures/chapters.yml +27 -0
  62. data/test/fixtures/groups.yml +7 -0
  63. data/test/fixtures/number_value_versions.csv +2 -0
  64. data/test/fixtures/number_values.csv +2 -0
  65. data/test/fixtures/text_value_versions.csv +35 -0
  66. data/test/fixtures/text_values.csv +35 -0
  67. data/test/fixtures/users.yml +8 -0
  68. data/test/number_value_version_test.rb +40 -0
  69. data/test/routing_test.rb +27 -0
  70. data/test/test_helper.rb +41 -0
  71. data/test/text_value_version_test.rb +40 -0
  72. metadata +77 -7
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ test/app_root/log/
2
+ doc/
3
+ created.rid
4
+ *.*~
5
+ .svn/
6
+ *.gem
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create use ree-1.8.7-2010.02@annotations
data/AUTHORS.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = Authors
2
+
3
+ == Original Author
4
+
5
+ Jiten Bhagat (mailto:mail@jits.co.uk), as part of the BioCatalogue project.
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,9 @@
1
+ class AnnotationsMigrationV2 < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :annotation_attributes, :identifier, :string, :null => false
4
+ end
5
+
6
+ def self.down
7
+ remove_column :annotation_attributes, :identifier
8
+ end
9
+ 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