grom 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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