grom 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '05864c0145ac584f326c708cd0b7c0331856a111'
4
- data.tar.gz: 3f3eeb74ad8b02887fc8365e3650ceba0a6134bd
3
+ metadata.gz: 34878dcc73dda4ed7d3145cc1bee503d8843d471
4
+ data.tar.gz: 91303eb86ec021e9ef6d4ab62edb271c1d8a4b89
5
5
  SHA512:
6
- metadata.gz: 5b8bc8f5cf98c9668df249284774ec47ee0ce06a4b75d87135eba191fa2f900504e728fd86d4ebecaf70058801d1cd8d193b278cb4a3ff1dedc71f73ca7dc0fc
7
- data.tar.gz: ea8afc191d34039ab45e1f3f51807f65b2125e70edd83ef20e2f928acec57e58bfe23d5d4d41bb25982439d06b96268221948cc9f5ccefc1fa76c4123368302b
6
+ metadata.gz: 362e2f954adc564f22875118b2ee2901a968183a72ba900da4d7469153b1198c2f2793efa22c88f0a88bd7b25aa22f91ef58f983124dd5ad6298429114e620b9
7
+ data.tar.gz: 74f6dd9f20628e1e3e766ca1874c487ea4368aabbf99c33bbe647f1ed488a379216368c75e364d995d4a7b13d4732ba701925ec3f5add0af94534367a977ef29
data/README.md CHANGED
@@ -1,15 +1,18 @@
1
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.
2
+ Grom is a custom ORM that takes graph data and deserializes it into objects. It uses the [RDF.rb](https://github.com/ruby-rdf/rdf) library. It was created in the context
3
+ of UK Parliament's new website.
4
4
 
5
5
  ## Installation
6
+ ```apple js
7
+ gem install grom
8
+ ```
6
9
  Add this line to your application's Gemfile:
7
10
 
8
11
  ```ruby
9
- gem 'grom', git: "https://github.com/ukpds/grom"
12
+ gem 'grom'
10
13
  ```
11
14
 
12
- And then execute:
15
+ Then execute:
13
16
  ```bash
14
17
  $ bundle
15
18
  ```
@@ -17,7 +20,7 @@ $ bundle
17
20
  ## Usage
18
21
 
19
22
  #### Set up
20
- You will need to set three constants in order for the package to work.
23
+ You will need to set three constants in order for the gem to work.
21
24
 
22
25
  ```
23
26
  API_ENDPOINT = 'http://example.com'
@@ -25,14 +28,14 @@ API_ENDPOINT_HOST = 'example.com'
25
28
  DATA_URI_PREFIX = 'http://id.example.com'
26
29
  ```
27
30
 
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.
31
+ The API Endpoint will be the endpoint where you are making the calls to get graph data. <br>
32
+ The Data URI prefix is the prefix that you use to define the entities in your triple store.
30
33
 
31
34
 
32
35
  #### 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.
36
+ Models inherit from `Grom::Base` and need a `self.property_translator` hash defined. The keys are predicates (without the prefix) and the values are the translated object properties.
34
37
 
35
- Here is an example of a Person class.
38
+ An example of a Person class.
36
39
 
37
40
  ```
38
41
  class Person < Grom::Base
@@ -49,10 +52,31 @@ class Person < Grom::Base
49
52
  end
50
53
  ```
51
54
 
55
+ #### Retrieving Objects
56
+
57
+ ##### #all
58
+ To return a collection of objects use the class method `all`.
59
+ ```apple js
60
+ people = Person.all #=> returns an array of People objects
61
+ ```
62
+
63
+ This method can take unlimited parameters. Including a parameter appends it to the endpoint url creating a new url. For example,
64
+
65
+ ```apple js
66
+ people = Person.all('current') #=> returns an array of current People
67
+ ```
68
+ This generates the endpoint url `http://#{API_ENDPOINT}/people/current.ttl` and returns a list of current people from there.
69
+
70
+ ##### #find
71
+ To return a single object use the class method `find` with an id.
72
+ ```apple js
73
+ person = Person.find('1') #=> returns a Person object
74
+ ```
75
+
52
76
  #### Associations
53
77
 
54
78
  ##### #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.
79
+ For a simple `has_many` association, declare the association at the top of the model. For example, if `Person` has many `contact_points`, create a `ContactPoint` class and declare the association at the top of the `Person` class.
56
80
 
57
81
  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
82
 
@@ -61,17 +85,28 @@ class Person < Grom::Base
61
85
  has_many :contact_points
62
86
  ```
63
87
 
88
+ It is then possible to retrieve the contact_points for a person in the following way:
89
+
90
+ ```apple js
91
+ person = Person.find('1')
92
+ person.contact_points #=> returns an array of contact_points related to person
93
+ ```
64
94
 
65
95
  ##### #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.
96
+ For a `has_many_through` association, declare the association at the top of the model. For example, if `Person` has many `parties` through `party_memberships`, create a `Party` class and a `PartyMembership` class, then declare the association at the top of the `Person` class.
67
97
 
68
98
  ```
69
99
  class Person < Grom::Base
70
100
  has_many_through :parties, via: :party_memberships
71
101
  ```
72
102
 
73
- ## Contributing
74
- Contribution directions go here.
103
+ It is then possible to retrieve the parties for a person in the following way:
104
+
105
+ ```apple js
106
+ person = Person.find('1')
107
+ person.parties #=> returns an array of parties with the party_memberships stored as a hash
108
+ ```
109
+
75
110
 
76
111
  ## License
77
112
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/lib/grom/base.rb CHANGED
@@ -8,14 +8,10 @@ module Grom
8
8
  extend Grom::Helpers
9
9
 
10
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
11
  attributes.each do |k, v|
16
12
  translated_key = self.class.property_translator[k]
17
13
  v = self.class.create_property_name(self.class.get_id(v)) if (v =~ URI::regexp) == 0
18
- unless (v.nil? || translated_key.nil?)
14
+ unless v.nil? || translated_key.nil?
19
15
  instance_variable_set("@#{translated_key}", v)
20
16
  self.class.send(:attr_reader, translated_key)
21
17
  end
@@ -24,54 +20,40 @@ module Grom
24
20
 
25
21
  def self.find(id)
26
22
  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)
23
+ ttl_data = get_ttl_data(endpoint_url)
24
+ self.object_single_maker(ttl_data)
29
25
  end
30
26
 
31
27
  def self.all(*options)
32
28
  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)
29
+ ttl_data = get_ttl_data(endpoint_url)
30
+
31
+ self.object_array_maker(ttl_data)
35
32
  end
36
33
 
37
34
  def self.has_many(association)
38
- self.class_eval("def #{association}(optional=nil); #{create_class_name(association)}.has_many_query(self, optional); end")
35
+ self.class_eval("def #{association}(*options); #{create_class_name(association)}.has_many_query(self, *options); end")
39
36
  end
40
37
 
41
38
  def self.has_many_through(association, through_association)
42
39
  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")
40
+ self.class_eval("def #{association}(*options); #{create_class_name(association)}.has_many_through_query(self, #{create_class_name(through_association[:via])}.new({}).class.name, *options); end")
44
41
  end
45
42
 
46
43
  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)
44
+ self.class_eval("def #{association}(*options); #{create_class_name(association)}.has_one_query(self, *options); end")
54
45
  end
55
46
 
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)
47
+ def self.has_many_query(owner_object, *options)
48
+ endpoint_url = associations_url_builder(owner_object, self.name, {optional: options })
49
+ ttl_data = get_ttl_data(endpoint_url)
50
+ self.object_array_maker(ttl_data)
60
51
  end
61
52
 
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
53
+ def self.has_one_query(owner_object, *options)
54
+ endpoint_url = associations_url_builder(owner_object, self.name, {optional: options, single: true })
55
+ ttl_data = get_ttl_data(endpoint_url)
56
+ self.object_single_maker(ttl_data)
75
57
  end
76
58
 
77
59
  def self.through_getter_setter(through_property_plural)
@@ -79,30 +61,34 @@ module Grom
79
61
  self.class_eval("def #{through_property_plural}; @#{through_property_plural}; end")
80
62
  end
81
63
 
82
- def self.object_array_maker(graph_data)
83
- self.statements_mapper_by_subject(graph_data).map do |data|
84
- self.new(data)
64
+ def self.object_array_maker(ttl_data)
65
+ create_hash_from_ttl(ttl_data).map do |hash|
66
+ self.new(hash)
85
67
  end
86
68
  end
87
69
 
88
- def self.object_single_maker(graph_data)
89
- self.object_array_maker(graph_data).first
70
+ def self.object_single_maker(ttl_data)
71
+ self.object_array_maker(ttl_data).first
72
+ end
73
+
74
+ def self.has_many_through_query(owner_object, through_class, *options)
75
+ through_property_plural = create_plural_property_name(through_class)
76
+ endpoint_url = associations_url_builder(owner_object, self.name, {optional: options })
77
+ self.through_getter_setter(through_property_plural)
78
+ ttl_data = get_ttl_data(endpoint_url)
79
+ self.map_hashes_to_objects(through_split_graph(ttl_data), through_property_plural)
90
80
  end
91
81
 
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)
82
+ def self.map_hashes_to_objects(hashes, through_property_plural)
83
+ through_array = hashes[:through_class_hash].values
84
+ hashes[:associated_class_hash].values.map do |hash|
85
+ associated_object = self.new(hash)
86
+ through_obj_array, through_array = through_array.partition do |t_hash|
87
+ t_hash[:associated_object_id] == associated_object.id
103
88
  end
89
+ associated_object.send((through_property_plural + '=').to_sym, through_obj_array)
90
+ associated_object
104
91
  end
105
- hash
106
92
  end
107
93
  end
108
94
  end
@@ -1,79 +1,56 @@
1
1
  require 'grom'
2
+ require 'rdf/turtle'
2
3
 
3
4
  module Grom
4
5
  module GraphMapper
5
6
 
6
- def get_graph_data(uri)
7
- ttl_data = Net::HTTP.get(URI(uri))
8
- create_graph_from_ttl(ttl_data)
7
+ def get_ttl_data(uri)
8
+ Net::HTTP.get(URI(uri))
9
9
  end
10
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
11
+ def get_id(uri)
12
+ uri == RDF.type.to_s ? 'type' : uri.to_s.split("/").last
18
13
  end
19
14
 
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|
15
+ def create_hash_from_ttl(ttl_data)
16
+ hash = {}
17
+ RDF::Turtle::Reader.new(ttl_data) do |reader|
26
18
  reader.each_statement do |statement|
27
- graph << statement
19
+ statement_mapper(statement, hash)
28
20
  end
29
21
  end
30
- graph
31
- end
32
-
33
- def get_id(uri)
34
- uri == RDF.type.to_s ? 'type' : uri.to_s.split("/").last
22
+ hash.values
35
23
  end
36
24
 
37
- def get_object_and_predicate(statement)
38
- predicate = get_id(statement.predicate)
39
- { predicate.to_sym => statement.object.to_s }
25
+ def statement_mapper(statement, hash)
26
+ subject = get_id(statement.subject)
27
+ hash[subject] ||= { :id => subject }
28
+ hash[subject][get_id(statement.predicate).to_sym] = statement.object.to_s
40
29
  end
41
30
 
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
31
+ def through_split_graph(ttl_data)
32
+ associated_hash, through_hash = {}, {}
33
+ RDF::Turtle::Reader.new(ttl_data) do |reader|
34
+ reader.each_statement do |s|
35
+ if (s.subject.to_s =~ URI::regexp) == 0
36
+ subject = get_id(s.subject)
37
+ associated_hash[subject] ||= { :id => subject }
38
+ associated_hash[subject][get_id(s.predicate).to_sym] = s.object.to_s
39
+ else
40
+ through_hash[s.subject.to_s] ||= {}
41
+ if get_id(s.predicate) == "connect"
42
+ through_hash[s.subject.to_s][:associated_object_id] = get_id(s.object)
43
+ elsif get_id(s.predicate) == "objectId"
44
+ through_hash[s.subject.to_s][:id] = get_id(s.object)
45
+ else
46
+ through_hash[s.subject.to_s][get_id(s.predicate).to_sym] = s.object.to_s
47
+ end
48
+ end
63
49
  end
64
- through_graph << graph
65
- through_graph.delete(associated_graph)
66
50
  end
67
- { through_graph: through_graph, associated_class_graph: associated_graph }
68
- end
69
51
 
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
52
+ { associated_class_hash: associated_hash, through_class_hash: through_hash }
53
+ end
77
54
 
78
55
  end
79
56
  end
data/lib/grom/helpers.rb CHANGED
@@ -7,7 +7,15 @@ module Grom
7
7
  id = owner_object.id
8
8
  associated_class_name = options[:single].nil? ? create_plural_property_name(associated_class_name) : create_property_name(associated_class_name)
9
9
  endpoint = "#{find_base_url_builder(owner_object.class.name, id)}/#{associated_class_name}"
10
- endpoint += options[:optional].nil? ? '.ttl' : "/#{options[:optional]}.ttl"
10
+ if options[:optional].nil?
11
+ endpoint += '.ttl'
12
+ else
13
+ options[:optional].each do |option|
14
+ endpoint += "/#{option}" unless option.nil?
15
+ end
16
+ endpoint += '.ttl'
17
+ end
18
+ endpoint
11
19
  end
12
20
 
13
21
  def find_base_url_builder(class_name, id)
@@ -34,27 +42,17 @@ module Grom
34
42
  ActiveSupport::Inflector.underscore(class_name).downcase
35
43
  end
36
44
 
37
- def collective_graph(objects)
38
- collective_graph = RDF::Graph.new
39
- objects.each do |o|
40
- collective_graph << o.graph
45
+ def order_list(arr, *parameters)
46
+ rejected = []
47
+ arr.delete_if{ |obj| rejected << obj if parameters.any?{ |param| obj.send(param).nil? } }
48
+ sorted_arr = arr.sort_by do |obj|
49
+ parameters.map{ |param| obj.send(param) }.select{ |p| not p.nil? }
41
50
  end
42
- collective_graph
51
+ rejected.concat(sorted_arr)
43
52
  end
44
53
 
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
54
+ def order_list_by_through(arr, through_association, property)
55
+ arr.map{ |obj| obj.send(through_association) }.flatten.sort{ |a, b| a[property] <=> b[property] }
58
56
  end
59
57
  end
60
58
  end
data/lib/grom.rb CHANGED
@@ -4,7 +4,7 @@ require_relative '../lib/grom/helpers'
4
4
  require 'active_support/core_ext/string/inflections'
5
5
 
6
6
  module Grom
7
- VERSION = '0.1.0'
7
+ VERSION = '0.1.1'
8
8
 
9
9
  def self.root
10
10
  File.dirname __dir__
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rebecca Appleyard
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-11-29 00:00:00.000000000 Z
12
+ date: 2016-12-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -46,13 +46,13 @@ dependencies:
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  - !ruby/object:Gem::Dependency
49
- name: rspec-rails
49
+ name: rdf-turtle
50
50
  requirement: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- type: :development
55
+ type: :runtime
56
56
  prerelease: false
57
57
  version_requirements: !ruby/object:Gem::Requirement
58
58
  requirements:
@@ -60,19 +60,19 @@ dependencies:
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  - !ruby/object:Gem::Dependency
63
- name: rdf
63
+ name: rspec-rails
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 2.1.0
68
+ version: '0'
69
69
  type: :development
70
70
  prerelease: false
71
71
  version_requirements: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: 2.1.0
75
+ version: '0'
76
76
  - !ruby/object:Gem::Dependency
77
77
  name: webmock
78
78
  requirement: !ruby/object:Gem::Requirement