grom 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []