pragmatic_context 0.0.1

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: 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: