talia_core 0.5.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/VERSION.yml +2 -2
  2. data/config/talia_core.yml.example +37 -35
  3. data/generators/talia_admin/templates/app/models/fake_source.rb +93 -0
  4. data/generators/talia_admin/templates/app/models/talia_collection.rb +13 -37
  5. data/generators/talia_base/talia_base_generator.rb +0 -1
  6. data/generators/talia_base/templates/app/controllers/custom_templates_controller.rb +2 -1
  7. data/generators/talia_base/templates/app/controllers/sources_controller.rb +1 -1
  8. data/generators/talia_base/templates/script/configure_talia +56 -73
  9. data/generators/talia_swicky/talia_swicky_generator.rb +18 -0
  10. data/generators/talia_swicky/templates/app/controllers/swicky_notebooks_controller.rb +111 -0
  11. data/generators/talia_swicky/templates/app/helpers/swicky_notebooks_helper.rb +29 -0
  12. data/generators/talia_swicky/templates/app/views/swicky_notebooks/index.builder +6 -0
  13. data/generators/talia_swicky/templates/app/views/swicky_notebooks/index.html.erb +10 -0
  14. data/generators/talia_swicky/templates/app/views/swicky_notebooks/show.html.erb +11 -0
  15. data/generators/talia_swicky/templates/test/fixtures/notebook.rdf +862 -0
  16. data/generators/talia_swicky/templates/test/functional/swicky_notebooks_controller_test.rb +44 -0
  17. data/lib/core_ext/boolean.rb +23 -0
  18. data/lib/core_ext/jdbc_rake_monkeypatch.rb +22 -0
  19. data/lib/core_ext/nil_class.rb +11 -0
  20. data/lib/core_ext/object.rb +34 -0
  21. data/lib/core_ext/string.rb +15 -0
  22. data/lib/custom_template.rb +3 -1
  23. data/lib/loader_helper.rb +16 -3
  24. data/lib/mysql.rb +7 -7
  25. data/lib/progressbar.rb +2 -2
  26. data/lib/swicky/exhibit_json/item.rb +129 -0
  27. data/lib/swicky/exhibit_json/item_collection.rb +129 -0
  28. data/lib/swicky/fragment.rb +0 -0
  29. data/lib/swicky/note.rb +7 -0
  30. data/lib/swicky/notebook.rb +78 -12
  31. data/lib/talia_core/active_source.rb +45 -13
  32. data/lib/talia_core/active_source_parts/class_methods.rb +154 -26
  33. data/lib/talia_core/active_source_parts/finders.rb +49 -26
  34. data/lib/talia_core/active_source_parts/predicate_handler.rb +71 -23
  35. data/lib/talia_core/active_source_parts/rdf/ntriples_reader.rb +13 -0
  36. data/lib/talia_core/active_source_parts/rdf/rdf_reader.rb +99 -0
  37. data/lib/talia_core/active_source_parts/rdf/rdfxml_reader.rb +12 -0
  38. data/lib/talia_core/active_source_parts/{rdf.rb → rdf_handler.rb} +52 -19
  39. data/lib/talia_core/active_source_parts/xml/generic_reader.rb +151 -260
  40. data/lib/talia_core/active_source_parts/xml/generic_reader_add_statements.rb +97 -0
  41. data/lib/talia_core/active_source_parts/xml/generic_reader_helpers.rb +88 -0
  42. data/lib/talia_core/active_source_parts/xml/generic_reader_import_statements.rb +239 -0
  43. data/lib/talia_core/active_source_parts/xml/rdf_builder.rb +14 -7
  44. data/lib/talia_core/active_source_parts/xml/source_builder.rb +7 -3
  45. data/lib/talia_core/active_source_parts/xml/source_reader.rb +17 -2
  46. data/lib/talia_core/collection.rb +192 -1
  47. data/lib/talia_core/data_types/data_loader.rb +88 -18
  48. data/lib/talia_core/data_types/data_record.rb +24 -2
  49. data/lib/talia_core/data_types/delayed_copier.rb +13 -3
  50. data/lib/talia_core/data_types/file_record.rb +24 -13
  51. data/lib/talia_core/data_types/file_store.rb +111 -94
  52. data/lib/talia_core/data_types/iip_data.rb +104 -23
  53. data/lib/talia_core/data_types/iip_loader.rb +102 -56
  54. data/lib/talia_core/data_types/image_data.rb +3 -1
  55. data/lib/talia_core/data_types/media_link.rb +4 -1
  56. data/lib/talia_core/data_types/mime_mapping.rb +65 -38
  57. data/lib/talia_core/data_types/path_helpers.rb +23 -17
  58. data/lib/talia_core/data_types/pdf_data.rb +9 -6
  59. data/lib/talia_core/data_types/simple_text.rb +5 -4
  60. data/lib/talia_core/data_types/xml_data.rb +53 -25
  61. data/lib/talia_core/dummy_handler.rb +3 -2
  62. data/lib/talia_core/errors.rb +13 -27
  63. data/lib/talia_core/initializer.rb +44 -4
  64. data/lib/talia_core/oai/active_source_model.rb +13 -6
  65. data/lib/talia_core/oai/active_source_oai_adapter.rb +13 -12
  66. data/lib/talia_core/rdf_import.rb +1 -1
  67. data/lib/talia_core/rdf_resource.rb +2 -1
  68. data/lib/talia_core/semantic_collection_wrapper.rb +143 -151
  69. data/lib/talia_core/semantic_property.rb +4 -0
  70. data/lib/talia_core/semantic_relation.rb +84 -33
  71. data/lib/talia_core/source.rb +45 -25
  72. data/lib/talia_core/source_fragment.rb +7 -0
  73. data/lib/talia_core/source_transfer_object.rb +3 -1
  74. data/lib/talia_core/source_types/agent.rb +16 -0
  75. data/lib/talia_core/source_types/dc_resource.rb +3 -3
  76. data/lib/talia_core/source_types/marcont_resource.rb +15 -0
  77. data/lib/talia_core/source_types/skos_concept.rb +17 -0
  78. data/lib/talia_dependencies.rb +1 -1
  79. data/lib/talia_util.rb +1 -1
  80. data/lib/talia_util/bar_progressor.rb +1 -1
  81. data/lib/talia_util/image_conversions.rb +8 -2
  82. data/lib/talia_util/import_job_helper.rb +40 -3
  83. data/lib/talia_util/io_helper.rb +15 -4
  84. data/lib/talia_util/progressable.rb +50 -1
  85. data/lib/talia_util/rake_tasks.rb +3 -21
  86. data/lib/talia_util/test_helpers.rb +6 -1
  87. data/lib/talia_util/util.rb +108 -27
  88. data/lib/talia_util/xml/base_builder.rb +28 -1
  89. data/lib/talia_util/xml/rdf_builder.rb +81 -5
  90. data/lib/tasks/talia_core_tasks.rake +2 -0
  91. data/test/core_ext/boolean_test.rb +26 -0
  92. data/test/core_ext/nil_class_test.rb +14 -0
  93. data/test/core_ext/object_test.rb +26 -0
  94. data/test/core_ext/string_test.rb +11 -0
  95. data/test/swicky/json_encoder_test.rb +51 -42
  96. data/test/swicky/notebook_test.rb +13 -6
  97. data/test/talia_core/active_source_finder_interface_test.rb +30 -0
  98. data/test/talia_core/active_source_test.rb +445 -34
  99. data/test/talia_core/collection_test.rb +332 -0
  100. data/test/talia_core/data_types/file_record_test.rb +2 -23
  101. data/test/talia_core/ntriples_reader_test.rb +49 -0
  102. data/test/talia_core/rdfxml_reader_test.rb +51 -0
  103. data/test/talia_core/source_test.rb +12 -0
  104. data/test/talia_util/import_job_helper_test.rb +19 -12
  105. metadata +190 -90
  106. data/config/database.yml +0 -19
  107. data/config/rdfstore.yml +0 -13
  108. data/config/talia_core.yml +0 -24
  109. data/generators/talia_base/templates/migrations/bj_migration.rb +0 -10
  110. data/lib/JXslt/jxslt.rb +0 -60
  111. data/lib/swicky/json_encoder.rb +0 -179
  112. data/lib/talia_core/agent.rb +0 -14
  113. data/lib/talia_core/background_jobs/job.rb +0 -82
  114. data/lib/talia_core/background_jobs/progress_job.rb +0 -68
  115. data/lib/talia_core/data_types/temp_file_handling.rb +0 -85
  116. data/lib/talia_core/ordered_source.rb +0 -228
  117. data/lib/talia_core/semantic_collection_item.rb +0 -94
  118. data/lib/talia_core/source_types/collection.rb +0 -15
  119. data/lib/talia_util/progressbar.rb +0 -236
  120. data/tasks/talia_core_tasks.rake +0 -2
  121. data/test/talia_core/ordered_source_test.rb +0 -394
  122. data/test/talia_core/semantic_collection_item_test.rb +0 -125
File without changes
@@ -0,0 +1,7 @@
1
+ module Swicky
2
+
3
+ # A Swicky::Note is a note attached to an object (usually a Source or a fragment of a Source)
4
+ class Note
5
+ end
6
+
7
+ end
@@ -12,13 +12,20 @@ module Swicky
12
12
  class Notebook
13
13
 
14
14
  include TaliaUtil::UriHelper
15
+ include ActiveRDF::ResourceLike
15
16
  extend TaliaUtil::UriHelper
16
17
 
17
18
  attr_reader :user_url, :url
18
19
 
19
- def initialize(user_name, notebook_name)
20
- @user_url = self.class.user_url(user_name)
21
- @url = self.class.notebook_url(user_name, notebook_name)
20
+ alias :uri :url
21
+
22
+ def initialize(user_name_or_uri, notebook_name = nil)
23
+ if(notebook_name)
24
+ @user_url = self.class.user_url(user_name_or_uri)
25
+ @url = self.class.notebook_url(user_name_or_uri, notebook_name)
26
+ else
27
+ @url = sanitize_sparql(user_name_or_uri).to_uri
28
+ end
22
29
  end
23
30
 
24
31
  def data
@@ -62,36 +69,79 @@ module Swicky
62
69
  ActiveRDF::Query.new(N::URI).select(:user).where(:user, N::TALIA.hasSwickyNotebook, url).execute.size > 0
63
70
  end
64
71
 
72
+ def to_uri
73
+ N::URI.new(uri)
74
+ end
75
+
76
+ def ==(value)
77
+ (value.class == self.class) && (value.uri == self.uri)
78
+ end
79
+
65
80
  class << self
81
+
82
+ # Find all notebooks for the given user
66
83
  def find_all(user_name = nil)
67
- nb_query = ActiveRDF::Query.new(N::URI).select(:notebook).distinct
84
+ nb_query = ActiveRDF::Query.new(Notebook).select(:notebook).distinct
68
85
  nb_query.where(:notebook, N::RDF.type, N::TALIA.SwickyNotebook)
69
86
  nb_query.where(user_url(user_name), N::TALIA.hasSwickyNotebook, :notebook) if(user_name)
70
87
  nb_query.execute
71
88
  end
72
89
 
90
+ # Construct the "user" url for the given user name
73
91
  def user_url(user_name)
74
92
  sanitize_sparql(N::LOCAL + "users/#{user_name}").to_uri
75
93
  end
76
94
 
95
+ # Construct the URL for a notebook from the user and notebook name
77
96
  def notebook_url(user_name, notebook_name)
78
97
  sanitize_sparql(user_url(user_name) + '/swicky_notebooks/' + notebook_name).to_uri
79
98
  end
80
99
 
100
+ # Get the "coordinates" (an xpointer in the case of HTML fragments) for all the
101
+ # fragments that are part of the element with the given url.
81
102
  def coordinates_for(url)
103
+ result = []
82
104
  url = sanitize_sparql(url).to_uri
83
- frag_qry = ActiveRDF::Query.new(N::URI).select(:coordinates).distinct
105
+ frag_qry = ActiveRDF::Query.new(N::URI).select(:fragment, :coordinates).distinct
84
106
  frag_qry.where(:fragment, N::DISCOVERY.isPartOf, url)
85
107
  frag_qry.where(:fragment, N::SWICKY.hasCoordinates, :coordinates)
86
108
  frag_qry.where(:note, N::SWICKY.refersTo, :fragment)
87
- frag_qry.execute.collect { |coord| coord.to_s }
109
+ frag_qry.execute.each do |fragment, coordinates|
110
+ result << {'fragment' => fragment.to_s, 'coordinates' => coordinates.to_s}
111
+ end
112
+ result
113
+ end
114
+
115
+ def annotation_list_for_url(url)
116
+ qry = ActiveRDF::Query.new(N::URI).distinct.select(:note).where(:fragment, N::DISCOVERY.isPartOf, url.to_uri).where(:note, N::SWICKY.refersTo, :fragment).execute
88
117
  end
89
118
 
119
+ # Select all the triples for all the annotations (notes) that refer to the given
120
+ # URL
90
121
  def annotations_for_url(url)
91
122
  url = sanitize_sparql(url).to_uri
92
123
  select_annotations([:note, N::SWICKY.refersTo, url])
93
124
  end
94
-
125
+
126
+ def annotations_for_image(url)
127
+ url = sanitize_sparql(url).to_uri
128
+ select_annotations([:note, N::SWICKY.refersTo, :fragment], [:fragment, N::DISCOVERY.isPartOf, url])
129
+ # result = []
130
+ # url = sanitize_sparql(url).to_uri
131
+ # q = ActiveRDF::Query.new(N::URI).select(:fragment).distinct.where(:fragment, N::DISCOVERY.isPartOf, url)
132
+ # q.execute.each do |fragment|
133
+ # result = {fragment.uri.to_s => {}}
134
+ # q2 = ActiveRDF::Query.new(N::URI).select(:predicate, :object).distinct
135
+ # q2.where fragment, :predicate, :object
136
+ # q2.execute.each do |predicate, object|
137
+ # result[fragment.uri.to_s][predicate.to_s] = object.to_s
138
+ # end
139
+ # end
140
+ # result
141
+ end
142
+
143
+ # Select all the annotations on the note that uses the fragment identified by the given XPOINTER
144
+ # string
95
145
  def annotations_for_xpointer(xpointer)
96
146
  xpointer = sanitize_sparql(xpointer).to_uri
97
147
  select_annotations([:note, N::SWICKY.refersTo, :fragment], [:fragment, N::SWICKY.hasCoordinates, xpointer])
@@ -99,16 +149,33 @@ module Swicky
99
149
 
100
150
  private
101
151
 
152
+ # Select annotation triples. This expects an array of "where" conditions (that is, arrays with a
153
+ # subject-predicate-object pattern). One of the conditions must match a :note variable.
154
+ #
155
+ # This will return all triples where:
156
+ #
157
+ # * :note is the subject of the triple
158
+ # * That have :statement as their subject, and :statement is a statement on one of the notes above
159
+ # * That have any of the predicates or objects of the results above as their subject
102
160
  def select_annotations(*note_matching)
103
161
  # Select all triples on the notes
104
162
  note_triples_qry = ActiveRDF::Query.new(N::URI).select(:note, :predicate, :object).distinct
105
163
  note_matching.each { |conditions| note_triples_qry.where(*conditions) }
106
- note_triples = note_triples_qry.where(:note, :predicate, :object).execute
164
+ result_triples = note_triples_qry.where(:note, :predicate, :object).execute
165
+ # Select all on the fragments of the note
166
+ fragment_triples_qry = ActiveRDF::Query.new(N::URI).select(:fragment, :predicate, :object).distinct
167
+ note_matching.each { |conditions| fragment_triples_qry.where(*conditions) }
168
+ fragment_triples_qry.where(:note, N::SWICKY.refersTo, :fragment)
169
+ fragment_triples_qry.where(:fragment, :predicate, :object)
170
+ result_triples += fragment_triples_qry.execute
107
171
  # Select all statements on the triples
108
172
  statement_triples_qry = ActiveRDF::Query.new(N::URI).select(:statement, :predicate, :object).distinct
109
173
  note_matching.each { |conditions| statement_triples_qry.where(*conditions) }
110
- statement_triples_qry.where(:note, N::SWICKY.hasStatement, :statement).where(:statement, :predicate, :object)
111
- result_triples = note_triples + statement_triples_qry.execute
174
+ statement_triples_qry.where(:note, N::SWICKY.refersTo, :fragment)
175
+ statement_triples_qry.where(:fragment, N::SWICKY.hasStatement, :statement)
176
+ statement_triples_qry.where(:statement, :predicate, :object)
177
+
178
+ result_triples += statement_triples_qry.execute
112
179
  # TODO: Fix this to better query once available in ActiveRDF
113
180
  additional_triples = []
114
181
  result_triples.each do |trip|
@@ -117,7 +184,6 @@ module Swicky
117
184
  additional_triples += ActiveRDF::Query.new(N::URI).select(:predicate, :object).distinct.where(trip.last, :predicate, :object).execute.collect { |result| [trip.last] + result }
118
185
  end
119
186
  end
120
-
121
187
  # Return all results
122
188
  result_triples + additional_triples
123
189
  end
@@ -125,4 +191,4 @@ module Swicky
125
191
  end
126
192
 
127
193
  end
128
- end
194
+ end
@@ -28,7 +28,7 @@ module TaliaCore
28
28
  extend ActiveSourceParts::SqlHelper
29
29
  include ActiveSourceParts::PredicateHandler
30
30
  extend ActiveSourceParts::PredicateHandler::ClassMethods
31
- include ActiveSourceParts::Rdf
31
+ include ActiveSourceParts::RdfHandler
32
32
  extend TaliaUtil::Progressable # Progress for import methods on class
33
33
 
34
34
  # Set the handlers for the callbacks defined in the other modules. The
@@ -36,6 +36,8 @@ module TaliaCore
36
36
  after_update :auto_update_rdf
37
37
  after_create :auto_create_rdf
38
38
  after_save :save_wrappers # Save the cache wrappers
39
+ before_destroy :destroy_dependent_props
40
+ after_destroy :clear_rdf
39
41
 
40
42
 
41
43
  # Relations where this source is the subject of the triple
@@ -97,6 +99,8 @@ module TaliaCore
97
99
  def [](attribute)
98
100
  if(db_attr?(attribute))
99
101
  super(attribute)
102
+ elsif(defined_property?(attribute))
103
+ self.send(attribute)
100
104
  else
101
105
  get_objects_on(attribute)
102
106
  end
@@ -107,10 +111,10 @@ module TaliaCore
107
111
  def []=(attribute, value)
108
112
  if(db_attr?(attribute))
109
113
  super(attribute, value)
114
+ elsif(defined_property?(attribute))
115
+ self.send("#{attribute}=", value)
110
116
  else
111
- pred = get_attribute(attribute)
112
- pred.remove
113
- pred << value
117
+ get_wrapper_on(attribute).replace(value)
114
118
  end
115
119
  end
116
120
 
@@ -176,7 +180,7 @@ module TaliaCore
176
180
  super(process_attributes(false, attributes))
177
181
  end
178
182
 
179
- # As update_attributes, but uses save! to save the source
183
+ # As update_attributes, but uses save! to save the source
180
184
  def update_attributes!(attributes)
181
185
  yield self if(block_given?)
182
186
  super(process_attributes(false, attributes))
@@ -204,10 +208,13 @@ module TaliaCore
204
208
  # the attribute values will be added to the existing ones
205
209
  def add_semantic_attributes(overwrite, attributes)
206
210
  attributes.each do |attr, value|
207
- value = [ value ] unless(value.is_a?(Array))
208
- attr_wrap = self[attr]
209
- attr_wrap.remove if(overwrite)
210
- value.each { |val |self[attr] << target_for(val) }
211
+ if(defined_property?(attr))
212
+ self[attr] = value
213
+ else
214
+ attr_wrap = self[attr]
215
+ attr_wrap.remove if(overwrite)
216
+ value.to_a.each { |val| self[attr] << target_for(val) }
217
+ end
211
218
  end
212
219
  end
213
220
 
@@ -280,6 +287,14 @@ module TaliaCore
280
287
  def db_attr?(attribute)
281
288
  ActiveSource.db_attr?(attribute)
282
289
  end
290
+
291
+ def defined_property?(prop_name)
292
+ self.class.defined_property?(prop_name)
293
+ end
294
+
295
+ def property_options_for(property)
296
+ self.class.property_options_for(property)
297
+ end
283
298
 
284
299
  # Writes the predicate directly to the database and the rdf store. The
285
300
  # Source does not need to be saved and no data is loaded from the database.
@@ -348,9 +363,10 @@ module TaliaCore
348
363
  def attach_files(files)
349
364
  files = [ files ] unless(files.is_a?(Array))
350
365
  files.each do |file|
351
- filename = file[:url] || file['url']
366
+ file.to_options!
367
+ filename = file[:url]
352
368
  assit(filename)
353
- options = file[:options] || file['options'] || {}
369
+ options = file[:options] || {}
354
370
  records = DataTypes::FileRecord.create_from_url(filename, options)
355
371
  records.each { |rec| self.data_records << rec }
356
372
  end
@@ -373,14 +389,30 @@ module TaliaCore
373
389
  def rdf_selftype
374
390
  (N::TALIA + self.class.name.demodulize)
375
391
  end
376
-
392
+
393
+ def reload
394
+ reset! # Clear the property cache
395
+ super
396
+ end
397
+
377
398
  private
378
399
 
400
+ # Removes dependent properties
401
+ def destroy_dependent_props
402
+ self.class.props_to_destroy.each do |prop|
403
+ values = self[prop]
404
+ values = [values] unless(values.is_a?(SemanticCollectionWrapper))
405
+ values.each do |val|
406
+ val.destroy if(val.is_a?(TaliaCore::ActiveSource))
407
+ end
408
+ end
409
+ end
410
+
379
411
  # Extracts the semantic attributes from the attribute hash and passes them
380
412
  # to add_semantic_attributes with the given overwrite flag.
381
413
  # The database attributes are returned by the method
382
414
  def process_attributes(overwrite, attributes)
383
- attributes = ActiveSource.split_attribute_hash(attributes)
415
+ attributes = self.class.split_attribute_hash(attributes)
384
416
  add_semantic_attributes(overwrite, attributes[:semantic_attributes])
385
417
  attributes[:db_attributes]
386
418
  end
@@ -1,8 +1,16 @@
1
1
  module TaliaCore
2
2
  module ActiveSourceParts
3
+
4
+ # Class methods for ActiveSource:
5
+ #
6
+ # * Property definitions for source classes (singular_property, multi_property, manual_property)
7
+ # * Logic for the creation of new sources, and things like exists?
8
+ # * "Import" methods for the class: create_from_xml, create_multi_from
9
+ # * autofill_uri logic
10
+ # * Various utility method
3
11
  module ClassMethods
4
12
 
5
- # Accessor for addtional rdf types that will automatically be added to each
13
+ # Accessor for additional rdf types that will automatically be added to each
6
14
  # object of that Source class
7
15
  def additional_rdf_types
8
16
  @additional_rdf_types ||= []
@@ -24,7 +32,12 @@ module TaliaCore
24
32
 
25
33
  # We have an option hash to init the source
26
34
  files = options.delete(:files)
27
- options[:uri] = uri_string_for(options[:uri])
35
+ options[:uri] = uri_string_for(options[:uri], false)
36
+ if(autofill_overwrites?)
37
+ options[:uri] = auto_uri
38
+ elsif(autofill_uri?)
39
+ options[:uri] ||= auto_uri
40
+ end
28
41
  attributes = split_attribute_hash(options)
29
42
  the_source = super(attributes[:db_attributes])
30
43
  the_source.add_semantic_attributes(false, attributes[:semantic_attributes])
@@ -33,6 +46,10 @@ module TaliaCore
33
46
  elsif(args.size == 1 && ( uri_s = uri_string_for(args[0]))) # One string argument should be the uri
34
47
  # Either the current object from the db, or a new one if it doesn't exist in the db
35
48
  find(:first, :conditions => { :uri => uri_s } ) || super(:uri => uri_s)
49
+ elsif(args.size == 0 && autofill_uri?)
50
+ auto = auto_uri
51
+ raise(ArgumentError, "Record already exists #{auto}") if(ActiveSource.exists?(auto))
52
+ super(:uri => auto)
36
53
  else
37
54
  # In this case, it's a generic "new" call
38
55
  super
@@ -41,6 +58,7 @@ module TaliaCore
41
58
  the_source
42
59
  end
43
60
 
61
+
44
62
  # Retrieves a new source with the given type. This gets a propety hash
45
63
  # like #new, but it will correctly initialize a source of the type given
46
64
  # in the hash. If no type is given, this will create a plain ActiveSource.
@@ -97,7 +115,7 @@ module TaliaCore
97
115
  props.to_options!
98
116
  src = nil
99
117
  begin
100
- props[:uri] = uri_string_for(props[:uri])
118
+ props[:uri] = uri_string_for(props[:uri], false)
101
119
  assit(props[:uri], "Must have a valid uri at this step")
102
120
  if(src = ActiveSource.find(:first, :conditions => { :uri => props[:uri] }))
103
121
  src.update_source(props, options[:duplicates])
@@ -107,7 +125,7 @@ module TaliaCore
107
125
  src.save!
108
126
  rescue Exception => e
109
127
  if(options[:errors])
110
- err = ImportError.new("ERROR during import of #{props[:uri]}: #{e.message}")
128
+ err = Errors::ImportError.new("ERROR during import of #{props[:uri]}: #{e.message}")
111
129
  err.set_backtrace(e.backtrace)
112
130
  options[:errors] << err
113
131
  TaliaCore.logger.warn("Problems importing #{props[:uri]} (logged): #{e.message}")
@@ -142,7 +160,7 @@ module TaliaCore
142
160
  end
143
161
 
144
162
  # Like update, only that it will overwrite the given attributes instead
145
- # of adding to them
163
+ # of adding to themƒ
146
164
  def rewrite(id, attributes)
147
165
  record = find(id)
148
166
  raise(ActiveRecord::RecordNotFound) unless(record)
@@ -200,6 +218,8 @@ module TaliaCore
200
218
  attributes.each do |field, value|
201
219
  if(db_attr?(field))
202
220
  db_attributes[field] = value
221
+ elsif(defined_property?(field))
222
+ semantic_attributes[field] = value
203
223
  else
204
224
  semantic_attributes[expand_uri(field)] = value
205
225
  end
@@ -207,8 +227,37 @@ module TaliaCore
207
227
  { :semantic_attributes => semantic_attributes, :db_attributes => db_attributes }
208
228
  end
209
229
 
230
+ def property_options_for(property)
231
+ property = defined_props[property.to_s] if(defined_props[property.to_s])
232
+ this_options = my_property_options[property.to_s]
233
+ parent_options = superclass.try_call.property_options_for(property)
234
+ if(this_options && parent_options)
235
+ parent_options.merge(this_options)
236
+ else
237
+ this_options || parent_options || {}
238
+ end
239
+ end
240
+
241
+ def defined_property?(prop_name)
242
+ defined_props.include?(prop_name.to_s) || superclass.try_call.defined_property?(prop_name.to_s)
243
+ end
244
+
245
+ # All the options that should be destroy for :dependent => :destroy settings
246
+ def props_to_destroy
247
+ to_destroy = (superclass.try_call.props_to_destroy || [])
248
+ my_property_options.each do |prop, options|
249
+ to_destroy << prop if(options[:dependent] == :destroy)
250
+ end
251
+ to_destroy
252
+ end
253
+
210
254
  private
211
255
 
256
+ # Make URL for autofilling
257
+ def auto_uri
258
+ (N::LOCAL + self.name.tableize + "/#{rand Time.now.to_i}").to_s
259
+ end
260
+
212
261
  # The attributes stored in the database
213
262
  def db_attributes
214
263
  @db_attributes ||= (ActiveSource.new.attribute_names << 'id')
@@ -221,33 +270,73 @@ module TaliaCore
221
270
  types.each { |t| @additional_rdf_types << t.to_s }
222
271
  end
223
272
 
273
+ # Class helper to declare that this Source model is allowed to automatically
274
+ # create uri values for new elements. In that case, the model will
275
+ # automatically assign a URL to all new records to which no url value has
276
+ # been passed.
277
+ #
278
+ # If the :force option is set, the autofill will overwrite an existing uri that
279
+ # is passed in during creation.
280
+ def autofill_uri(options = {})
281
+ options.to_options!
282
+ options.assert_valid_keys(:force)
283
+ @can_autofill = true
284
+ @autofill_overwrites = options[:force]
285
+ end
286
+
287
+ def autofill_uri?
288
+ @can_autofill
289
+ end
290
+
291
+ def autofill_overwrites?
292
+ @autofill_overwrites
293
+ end
294
+
295
+
296
+ def singular_property(prop_name, property, options = {})
297
+ define_property(prop_name, property, options.merge(:singular_property => true))
298
+ end
299
+
300
+
301
+ # Defines a multi-value property in the same way as #singular_property
302
+ def multi_property(prop_name, property, options = {})
303
+ define_property(prop_name, property, options.merge(:singular_property => false))
304
+ end
305
+
306
+ # Defines a "manual" property. This means that getters and setters are provided
307
+ # by the user and this statement only declares that the system may autoassign to
308
+ # that property
309
+ def manual_property(prop_name)
310
+ defined_props[prop_name.to_s] = :manual
311
+ end
312
+
224
313
  # Helper to define a "singular accessor" for something (e.g. siglum, catalog)
225
314
  # This accessor will provide an "accessor" method that returns the
226
315
  # single property value directly and an assignment method that replaces
227
316
  # the property with the value.
228
317
  #
318
+ # A find_by_<property> finder method is also created.
319
+ #
229
320
  # The Source will cache newly set singular properties internally, so that
230
321
  # the new value is immediately reflected on the object. However, the
231
322
  # change will only be made permanent on #save! - and saving will also clear
232
323
  # the cache
233
- def singular_property(prop_name, property)
324
+ #
325
+ # [*:dependent*] You may pass :dependend => :destroy as for ActiveRecord relations
326
+ def define_property(prop_name, property, options = {})
234
327
  prop_name = prop_name.to_s
235
- @singular_props ||= []
236
- return if(@singular_props.include?(prop_name))
328
+ property_options(property, options) # Save options for the current property
329
+
330
+ return if(defined_props.include?(prop_name))
237
331
  raise(ArgumentError, "Cannot overwrite method #{prop_name}") if(self.instance_methods.include?(prop_name) || self.instance_methods.include?("#{prop_name}="))
332
+
238
333
  # define the accessor
239
334
  define_method(prop_name) do
240
- prop = self[property]
241
- assit_block { |err| (prop.size > 1) ? err << "Must have at most 1 value for singular property #{prop_name} on #{self.uri}. Values #{self[property]}" : true }
242
- prop.size > 0 ? prop[0] : nil
335
+ self[property]
243
336
  end
244
337
 
245
338
  # define the writer
246
- define_method("#{prop_name}=") do |value|
247
- prop = self[property]
248
- prop.remove
249
- prop << value
250
- end
339
+ define_writer(prop_name, property)
251
340
 
252
341
  # define the finder
253
342
  (class << self ; self; end).module_eval do
@@ -258,31 +347,70 @@ module TaliaCore
258
347
  find(:all, finder)
259
348
  end
260
349
  end
350
+ defined_props[prop_name] = property
351
+ end
261
352
 
262
- @singular_props << prop_name
263
- true
353
+ # Helper to dynamically define the singular or multi-value assignment accessor
354
+ def define_writer(prop_name, property)
355
+ define_method("#{prop_name}=") do |values|
356
+ self[property] = values
357
+ end
264
358
  end
265
359
 
266
- # Helper to creat an accessor for the given predicate. This will shortcut
267
- # the prop_name method to self[property]
268
- def simple_property(prop_name, property)
269
- define_method(prop_name) do
270
- self[property]
360
+ # The hash containing the mapping between defined property names and the
361
+ # RDF properties on which they are defined.
362
+ def defined_props
363
+ @defined_props ||= {}
364
+ end
365
+
366
+ # Hash that contains all options that are defined for the properties
367
+ def my_property_options
368
+ @my_property_options ||= {}
369
+ end
370
+
371
+ # Sets the options for handling semantic relations/properties with the predicate
372
+ # #property. The options are:
373
+ #
374
+ # [*force_relation*] Forces the the values to be relations. This means that
375
+ # each and every value passed to the generated accessors
376
+ # will be interpreted as a URL. *DEPRECATED*, use
377
+ # `:type => TaliaCore::ActiveSource` instead
378
+ # [*type*] Declare that the values of this property should be of the given
379
+ # type, which should be a Ruby runtime class or a symbol corresponding
380
+ # to an ActiveRecord field type. If this is an ActiveSource subclass,
381
+ # this will force all values that are passed to this property
382
+ # to be interpreted as the URI of an ActiveSource (if the value is not
383
+ # an ActiveSource already)
384
+ # [*singular_property*]
385
+ # be used to force each value passed to the accessors, #new and
386
+ # #update* will be interpreted as the uri of a source of the given type,
387
+ #
388
+ def property_options(property, options)
389
+ options.to_options!
390
+ options.assert_valid_keys(:force_relation, :dependent, :type, :singular_property)
391
+ if(force = options.delete(:force_relation).true?)
392
+ warn("Deprecation Warning: :force_relation is deprecated - use ':type => TaliaCore::ActiveSource' instead")
393
+ options[:type] ||= ActiveSource
271
394
  end
395
+ my_property_options[property.to_s] ||= {}
396
+ my_property_options[property.to_s].merge!(options)
272
397
  end
273
398
 
274
399
  # This gets the URI string from the given value. This will just return
275
400
  # the value if it's a string. It will return the result of value.uri, if
276
401
  # that method exists; otherwise it'll return nil
277
- def uri_string_for(value)
402
+ #
403
+ # If the id_aware flag is set this will return nil for any uri string that
404
+ # appears to be a numeric id.
405
+ def uri_string_for(value, id_aware = true)
278
406
  result = if value.is_a? String
279
- return nil if(value =~ /\A\d+(-.*)?\Z/) # This looks like a record id or record param, encoded as a string
407
+ return nil if((value =~ /\A\d+(-.*)?\Z/) && id_aware) # This looks like a record id or record param, encoded as a string
280
408
  # if this is a local name, prepend the local namespace
281
409
  (value =~ /:/) ? value : (N::LOCAL + value).uri
282
410
  elsif(value.respond_to?(:uri))
283
411
  value.uri
284
412
  else
285
- nil
413
+ id_aware ? nil : value
286
414
  end
287
415
  result = result.to_s if result
288
416
  result