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
@@ -0,0 +1,115 @@
1
+ # ActsAsAnnotationValue
2
+ module Annotations
3
+ module Acts #:nodoc:
4
+ module AnnotationValue #:nodoc:
5
+
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def acts_as_annotation_value(options)
12
+ cattr_accessor :ann_value_content_field, :is_annotation_value
13
+
14
+ if options[:content_field].blank?
15
+ raise ArgumentError.new("Must specify the :content_field option that will be used as the field for the content")
16
+ end
17
+
18
+ self.ann_value_content_field = options[:content_field]
19
+
20
+ has_many :annotations,
21
+ :as => :value
22
+
23
+ has_many :annotation_value_seeds,
24
+ :as => :value
25
+
26
+ __send__ :extend, SingletonMethods
27
+ __send__ :include, InstanceMethods
28
+
29
+ self.is_annotation_value = true
30
+ end
31
+ end
32
+
33
+ # Class methods added to the model that has been made acts_as_annotation_value (the mixin target class).
34
+ module SingletonMethods
35
+
36
+ # This class level method is used to determine whether there is an existing
37
+ # annotation on an annotatable object, regardless of source. So, it is used to
38
+ # determine whether a "duplicate" exists (but the notion of "duplicate"
39
+ # may vary between annotation value classes).
40
+ #
41
+ # This method may be redefined in your model.
42
+ # A default implementation is provided that may suffice for most cases.
43
+ # But note that it makes certain assumptions that may not be valid for all
44
+ # kinds of annotation value models:
45
+ # - the joins for the value model use +ActiveRecord::Base::table_name+ to
46
+ # determine the name of the table to join on.
47
+ # - the field used make the comparison on the content is defined by the
48
+ # ':content_field' option passed into acts_as_annotation_value.
49
+ #
50
+ # Note: A precondition to this method is: this expects a valid
51
+ # +annotation+ object (i.e. one that contains a valid +value+
52
+ # object, valid +annotatable+ object, valid +attribute+ and so on).
53
+ def has_duplicate_annotation?(annotation)
54
+ return false unless annotation.value.is_a?(self)
55
+
56
+ val_table_name = self.table_name
57
+
58
+ existing = Annotation.find(:all,
59
+ :joins => "INNER JOIN annotation_attributes ON annotation_attributes.id = annotations.attribute_id
60
+ INNER JOIN #{val_table_name} ON annotations.value_type = '#{self.name}' AND #{val_table_name}.id = annotations.value_id",
61
+ :conditions => [ "annotations.annotatable_type = ? AND
62
+ annotations.annotatable_id = ? AND
63
+ annotation_attributes.name = ? AND
64
+ #{val_table_name}.#{self.ann_value_content_field} = ?",
65
+ annotation.annotatable_type,
66
+ annotation.annotatable_id,
67
+ annotation.attribute_name,
68
+ annotation.value.send(self.ann_value_content_field) ])
69
+
70
+ if existing.length == 0 || existing.first.id == annotation.id
71
+ return false
72
+ else
73
+ return true
74
+ end
75
+
76
+ end
77
+
78
+ #A set of all values that have been used, or seeded, with one of the provided attribute names
79
+ def with_attribute_names attributes
80
+ attributes = Array(attributes)
81
+ annotations = Annotation.with_attribute_names(attributes).with_value_type(self.name).include_values.collect{|ann| ann.value}
82
+ seeds = AnnotationValueSeed.with_attribute_names(attributes).with_value_type(self.name).include_values.collect{|ann| ann.value}
83
+ (annotations | seeds).uniq
84
+ end
85
+ end
86
+
87
+ # This module contains instance methods
88
+ module InstanceMethods
89
+
90
+ #Whether this value exists with a given attribute name
91
+ def has_attribute_name? attr
92
+ !annotations.with_attribute_name(attr).empty? || !annotation_value_seeds.with_attribute_name(attr).empty?
93
+ end
94
+
95
+ #The total number of annotations that match one or more attribute names.
96
+ def annotation_count attributes
97
+ attributes = Array(attributes)
98
+ annotations.with_attribute_names(attributes).count
99
+ end
100
+
101
+ # The actual content of the annotation value
102
+ def ann_content
103
+ self.send(self.class.ann_value_content_field)
104
+ end
105
+
106
+ # Set the actual content of the annotation value
107
+ def ann_content=(val)
108
+ self.send("#{self.class.ann_value_content_field}=", val)
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,148 @@
1
+ module Annotations
2
+ module Config
3
+ # List of attribute name(s) that need the corresponding value to be downcased (made all lowercase).
4
+ #
5
+ # NOTE: The attribute names specified MUST all be in lowercase.
6
+ @@attribute_names_for_values_to_be_downcased = [ ]
7
+
8
+ # List of attribute name(s) that need the corresponding value to be upcased (made all uppercase).
9
+ #
10
+ # NOTE: The attribute names specified MUST all be in lowercase.
11
+ @@attribute_names_for_values_to_be_upcased = [ ]
12
+
13
+ # This defines a hash of attributes, and the characters/strings that need to be stripped (removed) out of values of the attributes specified.
14
+ # Regular expressions can also be used instead of characters/strings.
15
+ # ie: { attribute_name => [ array of characters to strip out ] } (note: doesn't have to be an array, can be a single string)
16
+ #
17
+ # e.g: { "tag" => [ '"', ','] } or { "tag" => '"' }
18
+ #
19
+ # NOTE: The attribute name(s) specified MUST all be in lowercase.
20
+ @@strip_text_rules = { }
21
+
22
+ # This allows you to specify a different model name for users in the system (if different from the default: "User").
23
+ @@user_model_name = "User"
24
+
25
+ # This allows you to limit the number of annotations (of specified attribute names) per source per annotatable.
26
+ #
27
+ # Key/value pairs in hash should follow the spec:
28
+ # { attribute_name => max_number_allowed }
29
+ #
30
+ # e.g: { "rating" =>1 } - will only ever allow 1 "rating" annotation per annotatable by each source.
31
+ #
32
+ # NOTE (1): The attribute name(s) specified MUST all be in lowercase.
33
+ @@limits_per_source = { }
34
+
35
+ # By default, duplicate annotations CANNOT be created (same value for the same attribute, on the same annotatable object, regardless of source).
36
+ # For example: a user cannot add a description to a specific book that matches an existing description for that book.
37
+ #
38
+ # This config setting allows exceptions to this rule, on a per attribute basis.
39
+ # I.e: allow annotations with certain attribute names to have duplicate values (per annotatable).
40
+ #
41
+ # e.g: [ "tag", "rating" ] - allows tags and ratings to have the same value more than once.
42
+ #
43
+ # NOTE (1): The attribute name(s) specified MUST all be in lowercase.
44
+ # NOTE (2): This setting can be used in conjunction with the limits_per_source setting to allow
45
+ # duplicate annotations BUT limit the number of annotations (per attribute) per user.
46
+ @@attribute_names_to_allow_duplicates = [ ]
47
+
48
+ # This allows you to restrict the content of the values for annotations with a specific attribute name.
49
+ #
50
+ # Key/value pairs in the hash should follow the spec:
51
+ # { attribute_name => { :in => array_or_range, :error_message => error_msg_to_show_if_value_not_allowed }
52
+ #
53
+ # e.g: { "rating" => { :in => 1..5, :error_message => "Please provide a rating between 1 and 5" } }
54
+ #
55
+ # NOTE (1): The attribute name(s) specified MUST all be in lowercase.
56
+ # NOTE (2): values will be checked in a case insensitive manner.
57
+ @@content_restrictions = { }
58
+
59
+ # This determines what template to use to generate the unique 'identifier' for new AnnotationAttribute objects.
60
+ #
61
+ # String interpolation will be used to place the 'name' of the annotation within the template,
62
+ # in order to generate a unique identifier (usually a URI).
63
+ #
64
+ # This uses the @@attribute_name_transform_for_identifier defined below when performing the substitution.
65
+ #
66
+ # For more info on this substitution algorithm, see AnnotationAttribute#before_validation.
67
+ @@default_attribute_identifier_template = "http://www.example.org/attribute#%s"
68
+
69
+ # Defines a Proc that will be used to transform the value of AnnotationAttribute#name when generating the
70
+ # AnnotationAttribute#identifier value. See AnnotationAttribute#before_validation for more info.
71
+ @@attribute_name_transform_for_identifier = Proc.new { |name| name.to_s }
72
+
73
+ # This stores the factory Procs that are used to generate the value objects
74
+ # for annotations, based on the attribute name.
75
+ #
76
+ # - Keys should be attribute names (as Strings, in lowercase).
77
+ # - Values should either be a Proc that takes in one argument - the raw value object, that is then used
78
+ # to output the actual value to be stored. IMPORTANT: the Procs must be exhibit consistent data behaviour.
79
+ # I.e. should be able to run them over and over again without causing data inconsistencies or harmful side effects.
80
+ #
81
+ # NOTE (1): this is run BEFORE the default value generation logic in the +Annotation+ model.
82
+ # The default value generation logic will still run after the Proc.
83
+ # NOTE (2): The attribute name(s) specified MUST all be in lowercase.
84
+ @@value_factories_for_attributes = { }
85
+
86
+ # This determines the valid value types that are allowed for certain attribute names.
87
+ #
88
+ # - Keys should be attribute names (as Strings, in lowercase).
89
+ # - Values should be an Array of Strings, or single String, of valid class names for the value object type.
90
+ #
91
+ # NOTE (1): It is possible to use the above +value_factories_for_attributes+ option to achieve
92
+ # similar behaviour. However, this config option allows you to state explicitly what types are
93
+ # allowed as value objects.
94
+ # NOTE (2): The attribute name(s) specified MUST all be in lowercase.
95
+ @@valid_value_types = { }
96
+
97
+ # This determines whether versioning is enabled.
98
+ # The default behaviour is true, in which case when a new annotation is created or updated, a copy of the new version
99
+ # is stored in Annotation::Version and linked to the annotation. Likewise versions of the annotation values are created.
100
+ # By setting to false, no versions are recorded.
101
+ @@versioning_enabled = true
102
+
103
+ def self.reset
104
+ @@attribute_names_for_values_to_be_downcased = [ ]
105
+ @@attribute_names_for_values_to_be_upcased = [ ]
106
+ @@strip_text_rules = { }
107
+ @@user_model_name = "User"
108
+ @@limits_per_source = { }
109
+ @@attribute_names_to_allow_duplicates = [ ]
110
+ @@content_restrictions = { }
111
+ @@default_attribute_identifier_template = "http://www.example.org/attribute#%s"
112
+ @@attribute_name_transform_for_identifier = Proc.new { |name| name.to_s }
113
+ @@value_factories = { }
114
+ @@valid_value_types = { }
115
+ end
116
+
117
+ reset
118
+
119
+ # This makes the variables above available externally.
120
+ # Shamelessly borrowed from the GeoKit plugin.
121
+ [ :attribute_names_for_values_to_be_downcased,
122
+ :attribute_names_for_values_to_be_upcased,
123
+ :strip_text_rules,
124
+ :user_model_name,
125
+ :limits_per_source,
126
+ :attribute_names_to_allow_duplicates,
127
+ :content_restrictions,
128
+ :default_attribute_identifier_template,
129
+ :attribute_name_transform_for_identifier,
130
+ :value_factories,
131
+ :valid_value_types,
132
+ :versioning_enabled].each do |sym|
133
+ class_eval <<-EOS, __FILE__, __LINE__
134
+ def self.#{sym}
135
+ if defined?(#{sym.to_s.upcase})
136
+ #{sym.to_s.upcase}
137
+ else
138
+ @@#{sym}
139
+ end
140
+ end
141
+
142
+ def self.#{sym}=(obj)
143
+ @@#{sym} = obj
144
+ end
145
+ EOS
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,8 @@
1
+ module Annotations #:nodoc:
2
+ def self.map_routes(map, collection={}, member={}, requirements={})
3
+ map.resources :annotations,
4
+ :collection => { :create_multiple => :post }.merge(collection),
5
+ :member => {}.merge(member),
6
+ :requirements => { }.merge(requirements)
7
+ end
8
+ end
@@ -0,0 +1,82 @@
1
+ module Annotations
2
+ module Util
3
+
4
+ # Migrate existing annotations to the v3 db schema.
5
+ #
6
+ # Currently it just copies all values over to TextValue entries
7
+ # and assigns 'value' of the annotation accordingly and fixes up
8
+ # all the versions of that annotation in a naive way.
9
+ #
10
+ # NOTE (1): If you need tdifferent migration behaviour,
11
+ # redefine this method in your app (in the same namespace).
12
+ #
13
+ # NOTE (2): if individual annotations fail to migrate,
14
+ # their IDs and error info will be outputted to the console so you
15
+ # can inspect the issue.
16
+ #
17
+ # NOTE (3): this won't migrate any AnnotationValueSeed entries.
18
+ # You will need to write another migration script/method for these.
19
+ #
20
+ # NOTE (4): this makes some big assumptions about your current set of
21
+ # annotations. Please look through to make sure the logic applies.
22
+ def self.migrate_annotations_to_v3
23
+ Annotation.record_timestamps = false
24
+
25
+ Annotation.all.each do |ann|
26
+ begin
27
+ ann.transaction do
28
+ val = TextValue.new
29
+
30
+ # Handle versions
31
+ #
32
+ # NOTE: This will take a naive approach of assuming that
33
+ # only the 'old_value' field has been changed over time,
34
+ # nothing else!
35
+
36
+ # Build up the TextValue from the versions
37
+ ann.versions.each do |version|
38
+ val.text = version.old_value
39
+ val.created_at = version.created_at unless val.created_at
40
+ val.updated_at = version.updated_at
41
+ val.save!
42
+
43
+ val_version = val.versions(true).last
44
+ val_version.created_at = version.created_at
45
+ val_version.updated_at = version.updated_at
46
+ val_version.save!
47
+ end
48
+
49
+ # Assign new TextValue to Annotation
50
+ ann.value = val
51
+ ann.save!
52
+
53
+ # Only keep second to last version,
54
+ # deleting others, and resetting version
55
+ # numbers.
56
+ ann.versions(true).each do |version|
57
+ if version == ann.versions[-2]
58
+ # The one we want to keep
59
+ version.version = 1
60
+ version.value = val
61
+ version.save!
62
+ else
63
+ # Delete!
64
+ version.destroy
65
+ end
66
+ end
67
+ ann.version = 1
68
+ ann.save! # This shouldn't result in a new version
69
+ end
70
+ rescue Exception => ex
71
+ puts "FAILED to migrate annotation with ID #{ann.id}. Error message: #{ex.message}"
72
+ end
73
+ end
74
+
75
+ # TODO: similar kind of migration for annotation value seeds
76
+
77
+ Annotation.record_timestamps = true
78
+ end
79
+
80
+ end
81
+
82
+ end
@@ -0,0 +1,119 @@
1
+ # Kindly taken from the version_fu plugin (http://github.com/jmckible/version_fu/tree/master)
2
+
3
+ # Module and file renamed (and modified accordingly) on 2009-01-28 by Jits,
4
+ # to prevent conflicts with an external version_fu plugin installed in the main codebase.
5
+
6
+ module AnnotationsVersionFu
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def annotations_version_fu(options={}, &block)
13
+ return if self.included_modules.include? AnnotationsVersionFu::InstanceMethods
14
+ __send__ :include, AnnotationsVersionFu::InstanceMethods
15
+
16
+ cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name,
17
+ :version_column, :versioned_columns
18
+
19
+ self.versioned_class_name = options[:class_name] || 'Version'
20
+ self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
21
+ self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
22
+ self.version_column = options[:version_column] || 'version'
23
+
24
+ # Setup versions association
25
+ class_eval do
26
+ has_many :versions, :class_name => "#{self.to_s}::#{versioned_class_name}",
27
+ :foreign_key => versioned_foreign_key,
28
+ :dependent => :destroy do
29
+ def latest
30
+ find :first, :order=>'version desc'
31
+ end
32
+ end
33
+
34
+ before_save :check_for_new_version if Annotations::Config.versioning_enabled
35
+ end
36
+
37
+ # Versioned Model
38
+ const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
39
+ # find first version before the given version
40
+ def self.before(version)
41
+ find :first, :order => 'version desc',
42
+ :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]
43
+ end
44
+
45
+ # find first version after the given version.
46
+ def self.after(version)
47
+ find :first, :order => 'version',
48
+ :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]
49
+ end
50
+
51
+ def previous
52
+ self.class.before(self)
53
+ end
54
+
55
+ def next
56
+ self.class.after(self)
57
+ end
58
+ end
59
+
60
+ # Housekeeping on versioned class
61
+ versioned_class.cattr_accessor :original_class
62
+ versioned_class.original_class = self
63
+ versioned_class.set_table_name versioned_table_name
64
+
65
+ # Version parent association
66
+ versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
67
+ :class_name => "::#{self.to_s}",
68
+ :foreign_key => versioned_foreign_key
69
+
70
+ # Block extension
71
+ versioned_class.class_eval &block if block_given?
72
+
73
+ reload_versioned_columns_info
74
+ end
75
+
76
+ def reload_versioned_columns_info
77
+ self.reset_column_information
78
+ self.versioned_class.reset_column_information
79
+ if self.versioned_class.table_exists?
80
+ self.versioned_columns = versioned_class.new.attributes.keys -
81
+ [versioned_class.primary_key, versioned_foreign_key, version_column, 'created_at', 'updated_at']
82
+ else
83
+ ActiveRecord::Base.logger.warn "Version Table not found"
84
+ end
85
+ end
86
+
87
+ def versioned_class
88
+ const_get versioned_class_name
89
+ end
90
+ end
91
+
92
+
93
+ module InstanceMethods
94
+ def find_version(number)
95
+ versions.find :first, :conditions=>{:version=>number}
96
+ end
97
+
98
+ def check_for_new_version
99
+ instatiate_revision if create_new_version?
100
+ true # Never halt save
101
+ end
102
+
103
+ # This the method to override if you want to have more control over when to version
104
+ def create_new_version?
105
+ # Any versioned column changed?
106
+ self.class.versioned_columns.detect {|a| __send__ "#{a}_changed?"}
107
+ end
108
+
109
+ def instatiate_revision
110
+ new_version = versions.build
111
+ versioned_columns.each do |attribute|
112
+ new_version.__send__ "#{attribute}=", __send__(attribute)
113
+ end
114
+ version_number = new_record? ? 1 : version + 1
115
+ new_version.version = version_number
116
+ self.version = version_number
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,162 @@
1
+ class AnnotationsController < ApplicationController
2
+
3
+ before_filter :login_required, :only => [ :new, :create, :edit, :update, :destroy, :create_multiple ]
4
+
5
+ before_filter :find_annotation, :only => [ :show, :edit, :update, :destroy ]
6
+ before_filter :find_annotatable, :except => [ :show, :edit, :update, :destroy ]
7
+ before_filter :authorise_action, :only => [ :edit, :update, :destroy ]
8
+
9
+ # GET /annotations
10
+ # GET /annotations.xml
11
+ def index
12
+ params[:num] ||= 50
13
+
14
+ @annotations =
15
+ if @annotatable.nil?
16
+ Annotation.find(:all, :limit => params[:num])
17
+ else
18
+ @annotatable.latest_annotations(params[:num])
19
+ end
20
+
21
+ respond_to do |format|
22
+ format.html # index.html.erb
23
+ format.xml { render :xml => @annotations }
24
+ end
25
+ end
26
+
27
+ # GET /annotations/1
28
+ # GET /annotations/1.xml
29
+ def show
30
+ respond_to do |format|
31
+ format.html # show.html.erb
32
+ format.xml { render :xml => @annotation }
33
+ end
34
+ end
35
+
36
+ # GET /annotations/new
37
+ # GET /annotations/new.xml
38
+ def new
39
+ @annotation = Annotation.new
40
+
41
+ respond_to do |format|
42
+ format.html # new.html.erb
43
+ format.xml { render :xml => @annotation }
44
+ end
45
+ end
46
+
47
+ # POST /annotations
48
+ # POST /annotations.xml
49
+ def create
50
+ if params[:annotation][:source_type].blank? and params[:annotation][:source_id].blank?
51
+ if logged_in?
52
+ params[:annotation][:source_type] = current_user.class.name
53
+ params[:annotation][:source_id] = current_user.id
54
+ end
55
+ end
56
+
57
+ @annotation = Annotation.new(params[:annotation])
58
+ @annotation.annotatable = @annotatable
59
+
60
+ respond_to do |format|
61
+ if @annotation.save
62
+ flash[:notice] = 'Annotation was successfully created.'
63
+ format.html { redirect_to :back }
64
+ format.xml { render :xml => @annotation, :status => :created, :location => @annotation }
65
+ else
66
+ format.html { render :action => "new" }
67
+ format.xml { render :xml => @annotation.errors, :status => :unprocessable_entity }
68
+ end
69
+ end
70
+ end
71
+
72
+ # POST /annotations/create_multiple
73
+ # POST /annotations/create_multiple.xml
74
+ def create_multiple
75
+ if params[:annotation][:source_type].blank? and params[:annotation][:source_id].blank?
76
+ if logged_in?
77
+ params[:annotation][:source_type] = current_user.class.name
78
+ params[:annotation][:source_id] = current_user.id
79
+ end
80
+ end
81
+
82
+ success, annotations, errors = Annotation.create_multiple(params[:annotation], params[:separator])
83
+
84
+ respond_to do |format|
85
+ if success
86
+ flash[:notice] = 'Annotations were successfully created.'
87
+ format.html { redirect_to :back }
88
+ format.xml { render :xml => annotations, :status => :created, :location => @annotatable }
89
+ else
90
+ flash[:error] = 'Some or all annotations failed to be created.'
91
+ format.html { redirect_to :back }
92
+ format.xml { render :xml => annotations + errors, :status => :unprocessable_entity }
93
+ end
94
+ end
95
+ end
96
+
97
+ # GET /annotations/1/edit
98
+ def edit
99
+ end
100
+
101
+ # PUT /annotations/1
102
+ # PUT /annotations/1.xml
103
+ def update
104
+ @annotation.value = params[:annotation][:value]
105
+ @annotation.version_creator_id = current_user.id
106
+ respond_to do |format|
107
+ if @annotation.save
108
+ flash[:notice] = 'Annotation was successfully updated.'
109
+ format.html { redirect_to :back }
110
+ format.xml { head :ok }
111
+ else
112
+ format.html { render :action => "edit" }
113
+ format.xml { render :xml => @annotation.errors, :status => :unprocessable_entity }
114
+ end
115
+ end
116
+ end
117
+
118
+ # DELETE /annotations/1
119
+ # DELETE /annotations/1.xml
120
+ def destroy
121
+ @annotation.destroy
122
+
123
+ respond_to do |format|
124
+ flash[:notice] = 'Annotation successfully deleted.'
125
+ format.html { redirect_to :back }
126
+ format.xml { head :ok }
127
+ end
128
+ end
129
+
130
+ protected
131
+
132
+ def find_annotation
133
+ @annotation = Annotation.find(params[:id])
134
+ end
135
+
136
+ def find_annotatable
137
+ @annotatable = nil
138
+
139
+ if params[:annotation]
140
+ @annotatable = Annotation.find_annotatable(params[:annotation][:annotatable_type], params[:annotation][:annotatable_id])
141
+ end
142
+
143
+ # If still nil try again with alternative params
144
+ if @annotatable.nil?
145
+ @annotatable = Annotation.find_annotatable(params[:annotatable_type], params[:annotatable_id])
146
+ end
147
+ end
148
+
149
+ # Currently only checks that the source of the annotation matches the current user
150
+ def authorise_action
151
+ if !logged_in? or (@annotation.source != current_user)
152
+ # TODO: return either a 401 or 403 depending on authentication
153
+ respond_to do |format|
154
+ flash[:error] = 'You are not allowed to perform this action.'
155
+ format.html { redirect_to :back }
156
+ format.xml { head :forbidden }
157
+ end
158
+ return false
159
+ end
160
+ return true
161
+ end
162
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base # :nodoc:
2
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper # :nodoc:
2
+ end