changesets 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ CHANGESET.RB
2
+ ------------
3
+
4
+ A plugin for RDF.rb that provides support for creating Changesets that describe changes to an RDF
5
+ graph[1].
6
+
7
+ Changesets can be serialized as RDF and then applied to a triple store. Changesets are currently supported
8
+ by Talis Platform stores and Kasabi[0] datasets.
9
+
10
+ AUTHOR
11
+ ------
12
+
13
+ Leigh Dodds (ld@kasabi.com)
14
+
15
+ INSTALLATION
16
+ ------------
17
+
18
+ Changesets.rb is packaged as a Ruby Gem and can be installed as follows:
19
+
20
+ sudo gem install changesets
21
+
22
+ The source for the project is maintained in github at:
23
+
24
+ http://github.com/ldodds/changesets.rb
25
+
26
+ USAGE
27
+ -----
28
+
29
+ The test suite provides some simple examples of how to construct changesets.
30
+
31
+ [0]: [http://kasabi.com]
32
+ [1]: [http://vocab.org/changeset/schema.html]
@@ -0,0 +1,60 @@
1
+ require 'rake'
2
+
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/testtask'
6
+ require 'rake/clean'
7
+
8
+ NAME = "changesets"
9
+ VER = "0.1"
10
+
11
+ RDOC_OPTS = ['--quiet', '--title', 'RDF Changesets API']
12
+
13
+ PKG_FILES = %w( README.md Rakefile ) +
14
+ Dir.glob("{tests,lib}/**/*")
15
+
16
+ CLEAN.include ['*.gem', 'pkg']
17
+ SPEC =
18
+ Gem::Specification.new do |s|
19
+ s.name = NAME
20
+ s.version = VER
21
+ s.platform = Gem::Platform::RUBY
22
+ s.required_ruby_version = ">= 1.8.7"
23
+ s.has_rdoc = true
24
+ s.rdoc_options = RDOC_OPTS
25
+ s.summary = "RDF Changesets API"
26
+ s.description = s.summary
27
+ s.author = "Leigh Dodds"
28
+ s.email = 'ld@kasabi.com'
29
+ s.homepage = 'http://github.com/ldodds/changesets.rb'
30
+ s.files = PKG_FILES
31
+ s.require_path = "lib"
32
+ s.test_file = "tests/ts_changeset.rb"
33
+ s.add_dependency("rdf")
34
+ s.add_dependency("mocha", ">= 0.9.5")
35
+ end
36
+
37
+ Rake::GemPackageTask.new(SPEC) do |pkg|
38
+ pkg.need_tar = true
39
+ end
40
+
41
+ Rake::RDocTask.new do |rdoc|
42
+ rdoc.rdoc_dir = 'doc/rdoc'
43
+ rdoc.options += RDOC_OPTS
44
+ rdoc.rdoc_files.include("CHANGES", "lib/**/*.rb")
45
+ end
46
+
47
+ Rake::TestTask.new do |test|
48
+ test.test_files = FileList['tests/tc_*.rb']
49
+ end
50
+
51
+ desc "Install from a locally built copy of the gem"
52
+ task :install do
53
+ sh %{rake package}
54
+ sh %{sudo gem install pkg/#{NAME}-#{VER}}
55
+ end
56
+
57
+ desc "Uninstall the gem"
58
+ task :uninstall => [:clean] do
59
+ sh %{sudo gem uninstall #{NAME}}
60
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'rdf'
3
+
4
+ require 'changesets/rdf_changeset.rb'
@@ -0,0 +1,139 @@
1
+ module RDF
2
+ module Talis
3
+ class Changeset < RDF::Vocabulary('http://purl.org/vocab/changeset/schema#')
4
+ property :removal
5
+ property :addition
6
+ property :creatorName
7
+ property :createdDate
8
+ property :subjectOfChange
9
+ property :changeReason
10
+ property :ChangeSet
11
+ property :precedingChangeSet
12
+ end
13
+ end
14
+ end
15
+
16
+ module RDF
17
+
18
+ class Changeset
19
+
20
+ #Media type for Changesets
21
+ CONTENT_TYPE_TURTLE = "application/vnd.talis.changeset+turtle"
22
+ CONTENT_TYPE_XML = "application/vnd.talis.changeset+xml"
23
+ #Default reason for applying the update to a graph
24
+ DEFAULT_REASON = "Generated in changeset.rb"
25
+ #Default name/label for the change agent
26
+ DEFAULT_CREATOR = "changeset.rb"
27
+
28
+ attr_reader :statements, :subject_of_change
29
+
30
+ def initialize(subject_of_change, change_reason=DEFAULT_REASON, creator_name=DEFAULT_CREATOR)
31
+ @resource = RDF::Node.new
32
+ @subject_of_change = subject_of_change
33
+ @statements = []
34
+ @statements.concat [RDF::Statement.new(@resource, RDF.type, RDF::Talis::Changeset.ChangeSet),
35
+ RDF::Statement.new(@resource, RDF::Talis::Changeset.changeReason, change_reason),
36
+ RDF::Statement.new(@resource, RDF::Talis::Changeset.creatorName, creator_name),
37
+ RDF::Statement.new(@resource, RDF::Talis::Changeset.createdDate, Time.now),
38
+ RDF::Statement.new(@resource, RDF::Talis::Changeset.subjectOfChange, subject_of_change)]
39
+ if block_given?
40
+ yield self
41
+ end
42
+ end
43
+
44
+ #Remove these statements from the graph
45
+ def remove_statements(stmts)
46
+ stmts = [stmts] if stmts.is_a?(RDF::Statement)
47
+ stmts.each do |stmt|
48
+ raise ArgumentError unless stmt.subject == @subject_of_change
49
+ @statements.concat changeset_statement(stmt, :removal)
50
+ end
51
+ end
52
+
53
+ #Add these statements to the graph
54
+ def add_statements(stmts)
55
+ stmts = [stmts] if stmts.is_a?(RDF::Statement)
56
+ stmts.each do |stmt|
57
+ next unless stmt
58
+ raise ArgumentError unless stmt.subject == @subject_of_change
59
+ @statements.concat changeset_statement(stmt, :addition)
60
+ end
61
+ end
62
+
63
+ def changeset_statement(stmt, action)
64
+ s = RDF::Node.new
65
+ [RDF::Statement.new(@resource, RDF::Talis::Changeset.send(action), s),
66
+ RDF::Statement.new(s, RDF.type, RDF.to_rdf+"Statement"),
67
+ RDF::Statement.new(s, RDF.subject, stmt.subject),
68
+ RDF::Statement.new(s, RDF.predicate, stmt.predicate),
69
+ RDF::Statement.new(s, RDF.object, stmt.object)]
70
+ end
71
+
72
+ #Convert into an RDF::Graph object
73
+ def to_graph
74
+ graph = RDF::Graph.new()
75
+ @statements.each do |s|
76
+ graph << s
77
+ end
78
+ graph
79
+ end
80
+
81
+ #Update a predicate from one value to another
82
+ #Will only remove the specified old value, other values for predicate will remain unchanged
83
+ def Changeset.update_property(subject, predicate, old_value, new_value, change_reason=DEFAULT_REASON, creator_name=DEFAULT_CREATOR)
84
+ cs = Changeset.new(subject, change_reason, creator_name) do |cs|
85
+ cs.remove_statements( RDF::Statement.new( subject, predicate, old_value ) )
86
+ cs.add_statements( RDF::Statement.new( subject, predicate, new_value ) )
87
+ end
88
+ cs
89
+ end
90
+
91
+ #Remove a specific single property for a subject
92
+ def Changeset.remove_property(subject, predicate, value, change_reason=DEFAULT_REASON, creator_name=DEFAULT_CREATOR)
93
+ return Changeset.remove_statement( RDF::Statement.new( subject, predicate, value ) )
94
+ end
95
+
96
+ #Remove a statement
97
+ def Changeset.remove_statement( statement, change_reason=DEFAULT_REASON, creator_name=DEFAULT_CREATOR)
98
+ cs = Changeset.new(statement.subject, change_reason, creator_name) do |cs|
99
+ cs.remove_statements( statement )
100
+ end
101
+ cs
102
+ end
103
+
104
+ #Remove all statements with a specific predicate for a specific resource
105
+ def Changeset.remove_properties(subject, predicate, graph, change_reason=DEFAULT_REASON, creator_name=DEFAULT_CREATOR)
106
+ cs = Changeset.new(subject, change_reason, creator_name) do |cs|
107
+ graph.query( [subject, predicate, nil] ).each do |statement|
108
+ cs.remove_statements( statement )
109
+ end
110
+ end
111
+ cs
112
+ end
113
+
114
+ #Remove all statements where the indicated resource is the subject
115
+ def Changeset.remove_subject(subject, graph, change_reason=DEFAULT_REASON, creator_name=DEFAULT_CREATOR)
116
+ cs = Changeset.new(subject, change_reason, creator_name) do |cs|
117
+ graph.query( [subject, nil, nil] ).each do |statement|
118
+ cs.remove_statements( statement )
119
+ end
120
+ end
121
+ cs
122
+ end
123
+
124
+ #Apply an update by removing all statements for the indicated subject from the old graph, replacing it
125
+ #with statements in the new graph
126
+ def Changeset.update(subject, new, old, change_reason=DEFAULT_REASON, creator_name=DEFAULT_CREATOR)
127
+ cs = Changeset.new(subject, change_reason, creator_name) do |cs|
128
+ old.query( [subject, nil, nil] ).each do |statement|
129
+ cs.remove_statements( statement )
130
+ end
131
+ new.query( [subject, nil, nil] ).each do |statement|
132
+ cs.add_statements( statement )
133
+ end
134
+ end
135
+ cs
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,158 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
2
+ require 'changesets'
3
+ require 'test/unit'
4
+ require 'mocha'
5
+
6
+ class ChangesetTest < Test::Unit::TestCase
7
+
8
+ def test_init
9
+ cs = RDF::Changeset.new( RDF::URI.new("http://www.example.org") )
10
+ assert_equal( RDF::URI.new("http://www.example.org"), cs.subject_of_change )
11
+ assert_equal( false, cs.statements.empty? )
12
+
13
+ graph = cs.to_graph()
14
+ assert_not_nil( graph )
15
+ assert_equal( cs.statements.length, cs.to_graph.size )
16
+ end
17
+
18
+ def test_add_single_statement
19
+ uri = RDF::URI.new("http://www.example.org")
20
+ cs = RDF::Changeset.new( uri )
21
+ stmts = RDF::Statement.new( uri , RDF.type, RDF::RDFS.Class )
22
+ cs.add_statements( stmts )
23
+ test_for( RDF::Talis::Changeset.addition, cs, stmts )
24
+ end
25
+
26
+ def test_remove_single_statement
27
+ uri = RDF::URI.new("http://www.example.org")
28
+ cs = RDF::Changeset.new( uri )
29
+ stmts = RDF::Statement.new( uri , RDF.type, RDF::RDFS.Class )
30
+ cs.remove_statements( stmts )
31
+ test_for( RDF::Talis::Changeset.removal, cs, stmts )
32
+ end
33
+
34
+ def test_add_statements
35
+ uri = RDF::URI.new("http://www.example.org")
36
+ cs = RDF::Changeset.new( uri )
37
+ stmts = [ RDF::Statement.new( uri , RDF.type, RDF::RDFS.Class ) ]
38
+ cs.add_statements( stmts )
39
+ test_for( RDF::Talis::Changeset.addition, cs, stmts )
40
+ end
41
+
42
+ def test_remove_statements
43
+ uri = RDF::URI.new("http://www.example.org")
44
+ cs = RDF::Changeset.new( uri )
45
+ stmts = [ RDF::Statement.new( uri , RDF.type, RDF::RDFS.Class ) ]
46
+ cs.remove_statements( stmts )
47
+ test_for( RDF::Talis::Changeset.removal, cs, stmts )
48
+ end
49
+
50
+ def test_precondition
51
+ uri = RDF::URI.new("http://www.example.org")
52
+ other = RDF::URI.new("http://www.example.com")
53
+ cs = RDF::Changeset.new( uri )
54
+
55
+ assert_raise ArgumentError do
56
+ cs.add_statements( RDF::Statement.new( other , RDF.type, RDF::RDFS.Class ) )
57
+ end
58
+ assert_raise ArgumentError do
59
+ cs.remove_statements( RDF::Statement.new( other , RDF.type, RDF::RDFS.Class ) )
60
+ end
61
+
62
+ end
63
+
64
+ def test_update_property()
65
+ uri = RDF::URI.new("http://www.example.org")
66
+ cs = RDF::Changeset.update_property(uri, RDF::RDFS.label, RDF::Literal.new("Old"), RDF::Literal.new("New") )
67
+ removal = RDF::Statement.new( uri, RDF::RDFS.label, RDF::Literal.new("Old"))
68
+ addition = RDF::Statement.new( uri, RDF::RDFS.label, RDF::Literal.new("New"))
69
+ test_for( RDF::Talis::Changeset.removal, cs, removal )
70
+ test_for( RDF::Talis::Changeset.addition, cs, addition )
71
+ end
72
+
73
+ def test_remove_property()
74
+ uri = RDF::URI.new("http://www.example.org")
75
+ cs = RDF::Changeset.remove_property(uri, RDF::RDFS.label, RDF::Literal.new("Gone") )
76
+ removal = RDF::Statement.new( uri, RDF::RDFS.label, RDF::Literal.new("Gone"))
77
+ test_for( RDF::Talis::Changeset.removal, cs, removal )
78
+ end
79
+
80
+ def test_remove_statement()
81
+ uri = RDF::URI.new("http://www.example.org")
82
+ removal = RDF::Statement.new( uri, RDF::RDFS.label, RDF::Literal.new("Gone") )
83
+ cs = RDF::Changeset.remove_statement( removal )
84
+ test_for( RDF::Talis::Changeset.removal, cs, removal )
85
+ end
86
+
87
+ def test_remove_properties()
88
+ uri = RDF::URI.new("http://www.example.org")
89
+ graph = RDF::Graph.new()
90
+ label = RDF::Statement.new( uri, RDF::RDFS.label, RDF::Literal.new("Label") )
91
+ title = RDF::Statement.new( uri, RDF::RDFS.label, RDF::Literal.new("Title") )
92
+ graph << label
93
+ graph << title
94
+ cs = RDF::Changeset.remove_properties(uri, RDF::RDFS.label, graph )
95
+ #puts cs.statements
96
+ test_for( RDF::Talis::Changeset.removal, cs, label )
97
+ test_for( RDF::Talis::Changeset.removal, cs, title )
98
+ end
99
+
100
+ def test_remove_subject()
101
+ uri = RDF::URI.new("http://www.example.org")
102
+ graph = RDF::Graph.new()
103
+ label = RDF::Statement.new( uri, RDF::RDFS.label, "Label")
104
+ title = RDF::Statement.new( uri, RDF::DC.title, "Title")
105
+ graph << label
106
+ graph << title
107
+ cs = RDF::Changeset.remove_subject(uri, graph )
108
+ test_for( RDF::Talis::Changeset.removal, cs, [ label, title ] )
109
+ end
110
+
111
+ def test_update()
112
+ uri = RDF::URI.new("http://www.example.org")
113
+ graph = RDF::Graph.new()
114
+ label = RDF::Statement.new( uri, RDF::RDFS.label, "Label")
115
+ title = RDF::Statement.new( uri, RDF::DC.title, "Title")
116
+ graph << label
117
+ graph << title
118
+ cs = RDF::Changeset.update(uri, RDF::Graph.new(), graph )
119
+ test_for( RDF::Talis::Changeset.removal, cs, [ label, title ] )
120
+
121
+ cs = RDF::Changeset.update(uri, graph, RDF::Graph.new() )
122
+ test_for( RDF::Talis::Changeset.addition, cs, [ label, title ] )
123
+ end
124
+
125
+ # def test_apply_to()
126
+ # uri = RDF::URI.new("http://www.example.org")
127
+ # graph = RDF::Graph.new()
128
+ # label = RDF::Statement.new( uri, RDF::RDFS.label, RDF::Literal.new("Label") )
129
+ # title = RDF::Statement.new( uri, RDF::DC.title, RDF::Literal.new("Title") )
130
+ # graph << label
131
+ # graph << title
132
+ # cs = RDF::Changeset.remove_properties(uri, RDF::RDFS.label, graph )
133
+ #
134
+ # cs.apply_to(graph)
135
+ #
136
+ # label = graph.first_object( [uri, RDF::RDFS.label, nil ] )
137
+ # assert_nil(label)
138
+ # end
139
+
140
+
141
+ def test_for( predicate, cs, statements )
142
+ statements = [statements] if statements.is_a?(RDF::Statement)
143
+
144
+ statements.each do |s|
145
+ query = RDF::Query.new do
146
+ pattern [ :x, predicate, :stmt ]
147
+ pattern [ :stmt, RDF.subject, s.subject]
148
+ pattern [ :stmt, RDF.predicate, s.predicate]
149
+ pattern [ :stmt, RDF.object, s.object]
150
+ end
151
+ query.execute(cs.to_graph)
152
+ if query.failed?
153
+ raise "Unable to find #{predicate} for #{s.subject} #{s.predicate}, #{s.object}"
154
+ end
155
+ end
156
+ end
157
+
158
+ end
@@ -0,0 +1,2 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'test/unit'
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: changesets
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Leigh Dodds
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-10-12 00:00:00 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: rdf
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: mocha
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ hash: 49
42
+ segments:
43
+ - 0
44
+ - 9
45
+ - 5
46
+ version: 0.9.5
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: RDF Changesets API
50
+ email: ld@kasabi.com
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - README.md
59
+ - Rakefile
60
+ - tests/ts_changeset.rb
61
+ - tests/tc_rdf_changeset.rb
62
+ - lib/changesets.rb
63
+ - lib/changesets/rdf_changeset.rb
64
+ homepage: http://github.com/ldodds/changesets.rb
65
+ licenses: []
66
+
67
+ post_install_message:
68
+ rdoc_options:
69
+ - --quiet
70
+ - --title
71
+ - RDF Changesets API
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ hash: 57
80
+ segments:
81
+ - 1
82
+ - 8
83
+ - 7
84
+ version: 1.8.7
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ requirements: []
95
+
96
+ rubyforge_project:
97
+ rubygems_version: 1.8.9
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: RDF Changesets API
101
+ test_files:
102
+ - tests/ts_changeset.rb