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.
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