pragmatic_context 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4bc6e4f0095dfc065bdc39301e9199ccf86dc34a
4
+ data.tar.gz: 1befe8820a2a6540868a07773b255c88e28fd9d2
5
+ SHA512:
6
+ metadata.gz: 0369df6d7cd4d81bcbd7d7581dab2f282245f51266c2ceb1f71199e97975baa7c3042e03a08c9328a2e3688360cbd113c8c526aedb439679afee2e1213abd8c5
7
+ data.tar.gz: 049c0729133a59bb9ff14398ae0826614b1a46c68ffb9b9a79bd7edc818b2106715a239daaea79fe4cdc639801b7ec3261638e82e52a638446c7cacc2d5003b1
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pragmatic_context.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Mat Trudel
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # PragmaticContext
2
+
3
+ PragmaticContext lets declaratively contextualize your
4
+ ActiveModel objects in terms of Linked Data concepts, giving you an easy way to
5
+ get your models talking JSON-LD.
6
+
7
+ ## What's JSON-LD?
8
+
9
+ I'm glad you asked. [JSON-LD](http://json-ld.org/) is a lightweight JSON format
10
+ for expressing data within a context (also known as 'Linked Data'). Essentially
11
+ what this means is that it lets you say that the thing your data model calls
12
+ a 'last_name' is an embodiment of the abstract idea of a [Family
13
+ Name](http://xmlns.com/foaf/spec/#term_familyName). JSON-LD is similar in spirit
14
+ (though [vastly different in
15
+ execution](http://manu.sporny.org/2014/json-ld-origins-2/)) to
16
+ [RDF](http://en.wikipedia.org/wiki/Resource_Description_Framework). It allows
17
+ users of your application to unambiguously establish the place of your application's data
18
+ within a larger universe by placing it in context.
19
+
20
+ If you're just getting started with JSON-LD, I highly recommend the [JSON-LD 1.0 W3C
21
+ Recommendation](http://www.w3.org/TR/json-ld/) (really. Start at section 5).
22
+ It's a straightforward description of JSON-LD's purpose and structure, and
23
+ should be an easy read for anyone who already uses JSON. I wish more standards
24
+ documents were written this way. Seriously, it's great.
25
+
26
+ ## An example
27
+
28
+ As an example, let's consider the following Mongoid document:
29
+
30
+ class Person
31
+ include Mongoid::Document
32
+
33
+ field :first_name
34
+ field :last_name
35
+ field :email
36
+ end
37
+
38
+ As implemented above, a Person object has a first name, a last name, an email
39
+ address, and would serialize to JSON as something like:
40
+
41
+ {
42
+ "first_name": "Mat",
43
+ "last_name": "Trudel",
44
+ "email": "mat@geeky.net"
45
+ }
46
+
47
+ While that's usually clear enough to a human interacting with your API, the
48
+ field names chosen are basically arbitrary, and are difficult to process in any
49
+ automated way. Ideally, your field names should be able to unambiguously
50
+ identify a field as representing the concept of a person's first
51
+ name, whether the field was named 'first_name', 'FirstName', 'given_name', or
52
+ even 'field1234'. By allowing you to contextualize your data, JSON-LD lets you
53
+ essentially say "when I say 'last_name', I really mean
54
+ `http://xmlns.com/foaf/0.1/familyName`". In so doing, it becomes possible to
55
+ associate the last name fields of any JSON-LD API, regardless of what they name
56
+ their fields.
57
+
58
+ ## Isn't this just a re-invention of RDF?
59
+
60
+ Yes and no. While it's true that JSON-LD is a valid RDF serialization format,
61
+ this [wasn't one of its original design
62
+ goals](http://manu.sporny.org/2014/json-ld-origins-2/). My intent with
63
+ PragmaticContext is to allow developers who already work with JSON data to be
64
+ able to easily turn it into Linked Data without having to know or care about RDF
65
+ at all. At a basic level, the concepts of Linked Data should be grokkable by
66
+ anyone who already understands the basic concepts of APIs in general. JSON-LD's
67
+ creator Manu Sporny says it best:
68
+
69
+ > I’ve heard many people say that JSON-LD is primarily about the Semantic Web,
70
+ > but I disagree, it’s not about that at all. JSON-LD was created for Web
71
+ > Developers that are working with data that is important to other people and
72
+ > must interoperate across the Web. The Semantic Web was near the bottom of my
73
+ > list of “things to care about” when working on JSON-LD, and anyone that tells
74
+ > you otherwise is wrong
75
+
76
+ In terms of my personal approach to developing PragmanticContext, I'm
77
+ consciously not considering RDF as entering into the equation at all. With
78
+ PragmaticContext, I'm playing the role of an API developer already steeped in
79
+ JSON who wants to make their data more expressive without having to slow down
80
+ or learn a new stack. While I personally understand RDF's role and potential,
81
+ I'm willfully ignoring it with the goal of producing a library relevant to JSON
82
+ API developers. My thesis is that if JSON-LD is to be relevant to existing JSON
83
+ API developers, it needs to do so as a natural outgrowth of their existing
84
+ environment.
85
+
86
+ ## Installation
87
+
88
+ Add this line to your application's Gemfile:
89
+
90
+ gem 'pragmatic_context'
91
+
92
+ And then execute:
93
+
94
+ $ bundle
95
+
96
+ Or install it yourself as:
97
+
98
+ $ gem install pragmatic_context
99
+
100
+ ## Usage
101
+
102
+ PragmaticContext is implemented as a module that you mix into your existing
103
+ ActiveModel objects. Taking the `Person` model above, you would add context to
104
+ it like so:
105
+
106
+ class Person
107
+ include Mongoid::Document
108
+ include PragmaticContext::Contextualizable
109
+
110
+ field :first_name
111
+ field :last_name
112
+ field :email
113
+
114
+ contextualize :first_name, :as => 'http://xmlns.com/foaf/0.1/givenName'
115
+ contextualize :last_name, :as => 'http://xmlns.com/foaf/0.1/familyName'
116
+ contextualize :email, :as => 'http://xmlns.com/foaf/0.1/mbox'
117
+ end
118
+
119
+ The `Contextualizable` mixin adds a `context` method to the Person object which
120
+ returns a Hash object ready to be serialized into the output object. By wiring
121
+ this up in whatever serializer your application uses, you would end up with
122
+ the following JSON(-LD!) serialization:
123
+
124
+
125
+ {
126
+ "@context": {
127
+ "first_name": { "@id", "http://xmlns.com/foaf/0.1/givenName" },
128
+ "last_name": { "@id", "http://xmlns.com/foaf/0.1/familyName" },
129
+ "email": { "@id", "http://xmlns.com/foaf/0.1/mbox" },
130
+ },
131
+ "first_name": "Mat",
132
+ "last_name": "Trudel",
133
+ "email": "mat@geeky.net"
134
+ }
135
+
136
+ ### Dynamic documents & more complicated cases
137
+
138
+ There are cases (especially using Monogid's dynamic fields feature) where the
139
+ list of an object's fields is not known at class load time. In these cases, it's
140
+ possible to defer contextualization to a custom `Contextualizer` class
141
+ configured like so:
142
+
143
+ class Person
144
+ include Mongoid::Document
145
+ include PragmaticContext::Contextualizable
146
+
147
+ #
148
+ # Machinery for defining fields, either static or dynamic
149
+ #
150
+
151
+ contextualize_with CustomContextualizer
152
+ end
153
+
154
+ class CustomContextualizer
155
+ def definitions_for_terms(terms)
156
+ # Returns a hash of terms => term definitions
157
+ end
158
+ end
159
+
160
+ Examples of this are forthcoming.
161
+
162
+ ## Known Issues
163
+
164
+ * `@type` values aren't yet handled in any real way. This is next up on my queue
165
+ and should be done very soon.
166
+ * The above example is purposely short on serialization specifics. I'm trying to
167
+ be accommodating of various serializers and I'm still figuring out the best
168
+ way to do this. Hopefully we'll be able to make the process of adding
169
+ a `@context` automatic (or close to it) for common cases. Suggestions for this
170
+ point are very welcome.
171
+ * Further to the above, there are many (most, even) use cases where the included
172
+ `@context` field should simply be a URI pointing to a stand-alone
173
+ represntation of the object's context. I'm not sure how to flexibly realize
174
+ this yet. Again, suggestions are very welcome.
175
+
176
+ ## Contributing
177
+
178
+ 1. Fork it ( http://github.com/mtrudel/pragmatic_context/fork )
179
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
180
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
181
+ 4. Push to the branch (`git push origin my-new-feature`)
182
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,47 @@
1
+ require 'pragmatic_context/default_contextualizer'
2
+
3
+ module PragmaticContext
4
+ module Contextualizable
5
+ def Contextualizable.included(base)
6
+ base.class_eval do
7
+ end
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ attr_accessor :contextualizer
13
+
14
+ def contextualize_with(klass)
15
+ self.contextualizer = klass.new
16
+ end
17
+
18
+ def contextualize(field, params)
19
+ setup_default_contextualizer
20
+ self.contextualizer.add_term(field, params)
21
+ end
22
+
23
+ private
24
+
25
+ def setup_default_contextualizer
26
+ already_setup = !self.contextualizer.nil? && !self.contextualizer.is_a?(PragmaticContext::DefaultContextualizer)
27
+ raise "Cannot call contextualize if contextualize_with has already been called" if already_setup
28
+ self.contextualizer ||= PragmaticContext::DefaultContextualizer.new
29
+ end
30
+ end
31
+
32
+ def context
33
+ self.class.contextualizer.definitions_for_terms(terms)
34
+ end
35
+
36
+ def uncontextualized_terms
37
+ terms_with_context = self.class.contextualizer.definitions_for_terms(terms).keys
38
+ terms - terms_with_context
39
+ end
40
+
41
+ private
42
+
43
+ def terms
44
+ attributes.keys - ['_id', '_type']
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support'
2
+ require 'active_support/hash_with_indifferent_access'
3
+
4
+ module PragmaticContext
5
+ class DefaultContextualizer
6
+ def add_term(term, params)
7
+ @properties ||= ActiveSupport::HashWithIndifferentAccess.new
8
+ @properties[term] = ActiveSupport::HashWithIndifferentAccess.new params
9
+ end
10
+
11
+ def definitions_for_terms(terms)
12
+ Hash[@properties.slice(*terms).map { |term, params| [term, definition_from_params(params)] }]
13
+ end
14
+
15
+ private
16
+
17
+ def definition_from_params(params)
18
+ result = {}
19
+ result['@id'] = params[:as] if params[:as]
20
+ result['@type'] = params[:type] if params[:type]
21
+ result
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module PragmaticContext
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "pragmatic_context/version"
2
+ require "pragmatic_context/contextualizable"
3
+
4
+ module PragmaticContext
5
+
6
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pragmatic_context/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pragmatic_context"
8
+ spec.version = PragmaticContext::VERSION
9
+ spec.authors = ["Mat Trudel"]
10
+ spec.email = ["mat@geeky.net"]
11
+ spec.summary = %q{JSON-LD from a JSON perspective}
12
+ spec.description = %q{A library to declaratively add JSON-LD support to your ActiveModel models}
13
+ spec.homepage = "http://github.com/mtrudel/pragmatic_context"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 2.6"
24
+
25
+ spec.add_dependency "activesupport"
26
+ end
@@ -0,0 +1,75 @@
1
+ require 'pragmatic_context'
2
+
3
+ class Stub
4
+ include PragmaticContext::Contextualizable
5
+
6
+ attr_accessor :bacon
7
+
8
+ def attributes
9
+ { "bacon" => bacon }
10
+ end
11
+ end
12
+
13
+ describe PragmaticContext::Contextualizable do
14
+ describe 'included class methods (configuration DSL)' do
15
+ subject { Stub }
16
+
17
+ before :each do
18
+ subject.contextualizer = nil
19
+ end
20
+
21
+ describe 'contextualize_with' do
22
+ it 'should set the contextualizer on the class' do
23
+ contextualizer = double('contextualizer')
24
+ contextualizer_class = double('contextualizer class')
25
+ contextualizer_class.stub(:new) { contextualizer }
26
+ subject.contextualize_with contextualizer_class
27
+ subject.contextualizer.should eq contextualizer
28
+ end
29
+ end
30
+
31
+ describe 'contextualize' do
32
+ it 'should raise error if contextualize_with has already been called' do
33
+ subject.contextualize_with Object
34
+ expect { subject.contextualize }.to raise_error
35
+ end
36
+
37
+ it 'should create properties for each named field' do
38
+ contextualizer = double('contextualizer')
39
+ contextualizer.should_receive(:add_term).with(:bacon, { :as => 'http://bacon.yum' })
40
+ PragmaticContext::DefaultContextualizer.stub(:new) { contextualizer }
41
+
42
+ subject.contextualize :bacon, :as => 'http://bacon.yum'
43
+ end
44
+ end
45
+ end
46
+
47
+ describe 'included instance methods' do
48
+ subject { Stub.new }
49
+
50
+ before :each do
51
+ @contextualizer = double('contextualizer')
52
+ contextualizer_class = double('contextualizer class')
53
+ contextualizer_class.stub(:new) { @contextualizer }
54
+ subject.class.contextualize_with contextualizer_class
55
+ end
56
+
57
+ describe 'context' do
58
+ it 'should properly marshall together term definitions with the appropriate terms' do
59
+ @contextualizer.stub(:definitions_for_terms) do |terms|
60
+ { 'bacon' => { "@id" => "http://bacon.yum" } }.slice(*terms)
61
+ end
62
+ subject.bacon = 'crispy'
63
+ subject.context['bacon']['@id'].should == 'http://bacon.yum'
64
+ end
65
+ end
66
+
67
+ describe 'uncontextualized_terms' do
68
+ it 'should include terms which are not contextualized by the configured Contextualizer' do
69
+ @contextualizer.stub(:definitions_for_terms) { {} }
70
+ subject.uncontextualized_terms.should == ['bacon']
71
+ end
72
+ end
73
+ end
74
+ end
75
+
@@ -0,0 +1,14 @@
1
+ require 'pragmatic_context/default_contextualizer'
2
+
3
+ describe PragmaticContext::DefaultContextualizer do
4
+ describe 'adding terms' do
5
+ it 'should add terms and be able to retrive them' do
6
+ subject.add_term('bacon', :as => 'http://bacon.com')
7
+ subject.add_term('ham', :as => 'http://ham.com')
8
+ subject.definitions_for_terms(%w(bacon ham)).should eq({
9
+ "bacon" => { "@id" => "http://bacon.com" },
10
+ "ham" => { "@id" => "http://ham.com" }
11
+ })
12
+ end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pragmatic_context
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mat Trudel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A library to declaratively add JSON-LD support to your ActiveModel models
70
+ email:
71
+ - mat@geeky.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/pragmatic_context.rb
83
+ - lib/pragmatic_context/contextualizable.rb
84
+ - lib/pragmatic_context/default_contextualizer.rb
85
+ - lib/pragmatic_context/version.rb
86
+ - pragmatic_context.gemspec
87
+ - spec/lib/pragmatic_context/contextualizable_spec.rb
88
+ - spec/lib/pragmatic_context/default_contextualizer_spec.rb
89
+ homepage: http://github.com/mtrudel/pragmatic_context
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.2.2
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: JSON-LD from a JSON perspective
113
+ test_files:
114
+ - spec/lib/pragmatic_context/contextualizable_spec.rb
115
+ - spec/lib/pragmatic_context/default_contextualizer_spec.rb
116
+ has_rdoc: