grom 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: '05864c0145ac584f326c708cd0b7c0331856a111'
4
+ data.tar.gz: 3f3eeb74ad8b02887fc8365e3650ceba0a6134bd
5
+ SHA512:
6
+ metadata.gz: 5b8bc8f5cf98c9668df249284774ec47ee0ce06a4b75d87135eba191fa2f900504e728fd86d4ebecaf70058801d1cd8d193b278cb4a3ff1dedc71f73ca7dc0fc
7
+ data.tar.gz: ea8afc191d34039ab45e1f3f51807f65b2125e70edd83ef20e2f928acec57e58bfe23d5d4d41bb25982439d06b96268221948cc9f5ccefc1fa76c4123368302b
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Rebecca Appleyard
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # Graph Object Mapper (Grom)
2
+ Grom is a custom ORM that takes graph data and deserializes it into objects. It uses the ruby-rdf library. We created it in the context
3
+ of UK Parliament new website.
4
+
5
+ ## Installation
6
+ Add this line to your application's Gemfile:
7
+
8
+ ```ruby
9
+ gem 'grom', git: "https://github.com/ukpds/grom"
10
+ ```
11
+
12
+ And then execute:
13
+ ```bash
14
+ $ bundle
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ #### Set up
20
+ You will need to set three constants in order for the package to work.
21
+
22
+ ```
23
+ API_ENDPOINT = 'http://example.com'
24
+ API_ENDPOINT_HOST = 'example.com'
25
+ DATA_URI_PREFIX = 'http://id.example.com'
26
+ ```
27
+
28
+ Your Api Endpoint will be where you are making the calls to get your graph data. <br>
29
+ Your Data URI prefix is just the prefix that you use to define the entities in your triple store.
30
+
31
+
32
+ #### Models
33
+ In order for the deserialization to work, your models will need to inherit from `Grom::Base` and you will need to define a `self.property_translator` hash where the key should be the predicate (without the prefix) and the value should be whatever you want to call the property of the object.
34
+
35
+ Here is an example of a Person class.
36
+
37
+ ```
38
+ class Person < Grom::Base
39
+
40
+ def self.property_translator
41
+ {
42
+ id: 'id',
43
+ forename: 'forename',
44
+ surname: 'surname',
45
+ middleName: 'middle_name',
46
+ dateOfBirth: 'date_of_birth'
47
+ }
48
+ end
49
+ end
50
+ ```
51
+
52
+ #### Associations
53
+
54
+ ##### #has_many
55
+ For a simple `has_many` association, you need to declare the association at the top of the model. For example, if `Person` has many `contact_points`, you need to have a `ContactPoint` class and you would declare the association at the top of the `Person` class.
56
+
57
+ The name of the association must match the name of the class but it needs to be underscored and pluralized just like in ActiveRecord.
58
+
59
+ ```
60
+ class Person < Grom::Base
61
+ has_many :contact_points
62
+ ```
63
+
64
+
65
+ ##### #has_many_through
66
+ For a `has_many_through` association, you need to declare the association at the top of the model. For example, if `Person` has many `parties` through `party_memberships`, you need to have a `Party` class and a `PartyMembership` and you would declare the association at the top of the `Person` class.
67
+
68
+ ```
69
+ class Person < Grom::Base
70
+ has_many_through :parties, via: :party_memberships
71
+ ```
72
+
73
+ ## Contributing
74
+ Contribution directions go here.
75
+
76
+ ## License
77
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Grom'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
data/lib/grom/base.rb ADDED
@@ -0,0 +1,108 @@
1
+ require 'grom'
2
+ require 'uri'
3
+ require_relative '../../lib/grom/helpers'
4
+
5
+ module Grom
6
+ class Base
7
+ extend Grom::GraphMapper
8
+ extend Grom::Helpers
9
+
10
+ def initialize(attributes)
11
+ unless attributes == {}
12
+ ttl_graph = self.class.convert_to_ttl(attributes[:graph]).gsub("'", "\\\\'")
13
+ self.instance_eval("def graph; self.class.create_graph_from_ttl('#{ttl_graph}') ; end")
14
+ end
15
+ attributes.each do |k, v|
16
+ translated_key = self.class.property_translator[k]
17
+ v = self.class.create_property_name(self.class.get_id(v)) if (v =~ URI::regexp) == 0
18
+ unless (v.nil? || translated_key.nil?)
19
+ instance_variable_set("@#{translated_key}", v)
20
+ self.class.send(:attr_reader, translated_key)
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.find(id)
26
+ endpoint_url = "#{find_base_url_builder(self.name, id)}.ttl"
27
+ graph_data = get_graph_data(endpoint_url)
28
+ self.object_single_maker(graph_data)
29
+ end
30
+
31
+ def self.all(*options)
32
+ endpoint_url = "#{all_base_url_builder(self.name, *options)}.ttl"
33
+ graph_data = get_graph_data(endpoint_url)
34
+ self.object_array_maker(graph_data)
35
+ end
36
+
37
+ def self.has_many(association)
38
+ self.class_eval("def #{association}(optional=nil); #{create_class_name(association)}.has_many_query(self, optional); end")
39
+ end
40
+
41
+ def self.has_many_through(association, through_association)
42
+ self.has_many(through_association[:via])
43
+ self.class_eval("def #{association}(optional=nil); #{create_class_name(association)}.has_many_through_query(self, #{create_class_name(through_association[:via])}.new({}).class.name, optional); end")
44
+ end
45
+
46
+ def self.has_one(association)
47
+ self.class_eval("def #{association}(optional=nil); #{create_class_name(association)}.has_one_query(self, optional); end")
48
+ end
49
+
50
+ def self.has_many_query(owner_object, optional=nil)
51
+ endpoint_url = associations_url_builder(owner_object, self.name, {optional: optional })
52
+ graph_data = get_graph_data(endpoint_url)
53
+ self.object_array_maker(graph_data)
54
+ end
55
+
56
+ def self.has_one_query(owner_object, optional=nil)
57
+ endpoint_url = associations_url_builder(owner_object, self.name, {optional: optional, single: true })
58
+ graph_data = get_graph_data(endpoint_url)
59
+ self.object_single_maker(graph_data)
60
+ end
61
+
62
+ def self.has_many_through_query(owner_object, through_class, optional=nil)
63
+ endpoint_url = associations_url_builder(owner_object, self.name, {optional: optional })
64
+ graph_data = get_graph_data(endpoint_url)
65
+ separated_graphs = split_by_subject(graph_data, self.name)
66
+ associated_objects_array = self.object_array_maker(separated_graphs[:associated_class_graph])
67
+ through_property_plural = create_plural_property_name(through_class)
68
+ self.through_getter_setter(through_property_plural)
69
+ associated_objects_array.each do |associated_object|
70
+ through_class_array = get_through_graphs(separated_graphs[:through_graph], associated_object.id).map do |graph|
71
+ ActiveSupport::Inflector.constantize(through_class).object_single_maker(graph)
72
+ end
73
+ associated_object.send((through_property_plural + '=').to_sym, through_class_array)
74
+ end
75
+ end
76
+
77
+ def self.through_getter_setter(through_property_plural)
78
+ self.class_eval("def #{through_property_plural}=(array); @#{through_property_plural} = array; end")
79
+ self.class_eval("def #{through_property_plural}; @#{through_property_plural}; end")
80
+ end
81
+
82
+ def self.object_array_maker(graph_data)
83
+ self.statements_mapper_by_subject(graph_data).map do |data|
84
+ self.new(data)
85
+ end
86
+ end
87
+
88
+ def self.object_single_maker(graph_data)
89
+ self.object_array_maker(graph_data).first
90
+ end
91
+
92
+ def serialize_associated_objects(*associations)
93
+ object_name = self.class.create_property_name(self.class.name).to_sym
94
+ hash = {
95
+ object_name => self
96
+ }
97
+ associations.each do |association|
98
+ if association.is_a?(Hash)
99
+ association_name = association.keys.first
100
+ hash[association_name] = self.send(association_name, association.values.first)
101
+ else
102
+ hash[association] = self.send(association)
103
+ end
104
+ end
105
+ hash
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,79 @@
1
+ require 'grom'
2
+
3
+ module Grom
4
+ module GraphMapper
5
+
6
+ def get_graph_data(uri)
7
+ ttl_data = Net::HTTP.get(URI(uri))
8
+ create_graph_from_ttl(ttl_data)
9
+ end
10
+
11
+ def convert_to_ttl(data)
12
+ # p "called convert_to_ttl"
13
+ result = ""
14
+ data.each_statement do |statement|
15
+ result << RDF::NTriples::Writer.serialize(statement)
16
+ end
17
+ result
18
+ end
19
+
20
+ def create_graph_from_ttl(ttl_data)
21
+ # p "called create_graph_from_ttl"
22
+ # p ttl_data
23
+ ttl_data
24
+ graph = RDF::Graph.new
25
+ RDF::NTriples::Reader.new(ttl_data) do |reader|
26
+ reader.each_statement do |statement|
27
+ graph << statement
28
+ end
29
+ end
30
+ graph
31
+ end
32
+
33
+ def get_id(uri)
34
+ uri == RDF.type.to_s ? 'type' : uri.to_s.split("/").last
35
+ end
36
+
37
+ def get_object_and_predicate(statement)
38
+ predicate = get_id(statement.predicate)
39
+ { predicate.to_sym => statement.object.to_s }
40
+ end
41
+
42
+ def statements_mapper_by_subject(graph)
43
+ graph.subjects.map do |subject|
44
+ individual_graph = RDF::Graph.new
45
+ pattern = RDF::Query::Pattern.new(subject, :predicate, :object)
46
+ attributes = graph.query(pattern).map do |statement|
47
+ individual_graph << statement
48
+ get_object_and_predicate(statement)
49
+ end.reduce({}, :merge)
50
+
51
+ attributes.merge({id: get_id(subject), graph: individual_graph })
52
+ end
53
+ end
54
+
55
+ def split_by_subject(graph, associated_class_name)
56
+ associated_class_type_pattern = RDF::Query::Pattern.new(:subject, RDF.type, RDF::URI.new("#{DATA_URI_PREFIX}/schema/#{associated_class_name}"))
57
+ associated_graph = RDF::Graph.new
58
+ through_graph = RDF::Graph.new
59
+ graph.query(associated_class_type_pattern).subjects.map do |subject|
60
+ subject_pattern = RDF::Query::Pattern.new(subject, :predicate, :object)
61
+ graph.query(subject_pattern).each do |statement|
62
+ associated_graph << statement
63
+ end
64
+ through_graph << graph
65
+ through_graph.delete(associated_graph)
66
+ end
67
+ { through_graph: through_graph, associated_class_graph: associated_graph }
68
+ end
69
+
70
+ def get_through_graphs(graph, id)
71
+ connection_pattern = RDF::Query::Pattern.new(:subject, :predicate, RDF::URI.new("#{DATA_URI_PREFIX}/#{id}"))
72
+ graph.query(connection_pattern).subjects.map do |subject|
73
+ subject_pattern = RDF::Query::Pattern.new(subject, :predicate, :object)
74
+ graph.query(subject_pattern)
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,60 @@
1
+ require 'grom'
2
+
3
+ module Grom
4
+ module Helpers
5
+
6
+ def associations_url_builder(owner_object, associated_class_name, options={})
7
+ id = owner_object.id
8
+ associated_class_name = options[:single].nil? ? create_plural_property_name(associated_class_name) : create_property_name(associated_class_name)
9
+ endpoint = "#{find_base_url_builder(owner_object.class.name, id)}/#{associated_class_name}"
10
+ endpoint += options[:optional].nil? ? '.ttl' : "/#{options[:optional]}.ttl"
11
+ end
12
+
13
+ def find_base_url_builder(class_name, id)
14
+ "#{API_ENDPOINT}/#{create_plural_property_name(class_name)}/#{id}"
15
+ end
16
+
17
+ def all_base_url_builder(class_name, *options)
18
+ endpoint = "#{API_ENDPOINT}/#{create_plural_property_name(class_name)}"
19
+ options.each do |option|
20
+ endpoint += "/#{option}" unless option.nil?
21
+ end
22
+ endpoint
23
+ end
24
+
25
+ def create_class_name(plural_name)
26
+ ActiveSupport::Inflector.camelize(ActiveSupport::Inflector.singularize(plural_name.to_s).capitalize)
27
+ end
28
+
29
+ def create_plural_property_name(class_name)
30
+ ActiveSupport::Inflector.pluralize(create_property_name(class_name))
31
+ end
32
+
33
+ def create_property_name(class_name)
34
+ ActiveSupport::Inflector.underscore(class_name).downcase
35
+ end
36
+
37
+ def collective_graph(objects)
38
+ collective_graph = RDF::Graph.new
39
+ objects.each do |o|
40
+ collective_graph << o.graph
41
+ end
42
+ collective_graph
43
+ end
44
+
45
+ def collective_has_many_graph(owner, association)
46
+ owner.graph << collective_graph(association)
47
+ end
48
+
49
+ def collective_through_graph(owner, association, through_property)
50
+ graph = owner.graph
51
+ association.each do |associated_object|
52
+ graph << associated_object.graph
53
+ associated_object.send(through_property).each do |through_object|
54
+ graph << through_object.graph
55
+ end
56
+ end
57
+ graph
58
+ end
59
+ end
60
+ end
data/lib/grom.rb ADDED
@@ -0,0 +1,12 @@
1
+ require_relative '../lib/grom/graph_mapper'
2
+ require_relative '../lib/grom/base'
3
+ require_relative '../lib/grom/helpers'
4
+ require 'active_support/core_ext/string/inflections'
5
+
6
+ module Grom
7
+ VERSION = '0.1.0'
8
+
9
+ def self.root
10
+ File.dirname __dir__
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :grom do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grom
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rebecca Appleyard
8
+ - Giuseppe De Santis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-11-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 5.0.0
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 5.0.0.1
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: 5.0.0
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.0.1
34
+ - !ruby/object:Gem::Dependency
35
+ name: activesupport
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec-rails
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rdf
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 2.1.0
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 2.1.0
76
+ - !ruby/object:Gem::Dependency
77
+ name: webmock
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ - !ruby/object:Gem::Dependency
91
+ name: simplecov
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ description: Grom is a Graph Object Mapper designed for UK Parliament
105
+ email:
106
+ - rklappleyard@gmail.com
107
+ - giusdesan@gmail.com
108
+ executables: []
109
+ extensions: []
110
+ extra_rdoc_files: []
111
+ files:
112
+ - MIT-LICENSE
113
+ - README.md
114
+ - Rakefile
115
+ - lib/grom.rb
116
+ - lib/grom/base.rb
117
+ - lib/grom/graph_mapper.rb
118
+ - lib/grom/helpers.rb
119
+ - lib/tasks/grom_tasks.rake
120
+ homepage: ''
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.6.7
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: ''
144
+ test_files: []