gearbox 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.
Files changed (42) hide show
  1. data/.document +5 -0
  2. data/Gemfile +21 -0
  3. data/Gemfile.lock +138 -0
  4. data/Guardfile +9 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.html +89 -0
  7. data/README.md +87 -0
  8. data/Rakefile +46 -0
  9. data/VERSION +1 -0
  10. data/lib/examples/audience.rb +24 -0
  11. data/lib/examples/person.rb +29 -0
  12. data/lib/examples/reference.rb +38 -0
  13. data/lib/examples/theme.rb +8 -0
  14. data/lib/gearbox.rb +40 -0
  15. data/lib/gearbox/attribute_collection.rb +42 -0
  16. data/lib/gearbox/mixins/ad_hoc_properties.rb +41 -0
  17. data/lib/gearbox/mixins/resource.rb +17 -0
  18. data/lib/gearbox/mixins/semantic_accessors.rb +118 -0
  19. data/lib/gearbox/rdf_collection.rb +72 -0
  20. data/lib/gearbox/type.rb +85 -0
  21. data/lib/gearbox/types.rb +28 -0
  22. data/lib/gearbox/types/any.rb +24 -0
  23. data/lib/gearbox/types/boolean.rb +31 -0
  24. data/lib/gearbox/types/date.rb +27 -0
  25. data/lib/gearbox/types/decimal.rb +29 -0
  26. data/lib/gearbox/types/float.rb +28 -0
  27. data/lib/gearbox/types/integer.rb +27 -0
  28. data/lib/gearbox/types/native.rb +22 -0
  29. data/lib/gearbox/types/string.rb +27 -0
  30. data/lib/gearbox/types/uri.rb +25 -0
  31. data/spec/examples/audience_spec.rb +28 -0
  32. data/spec/examples/person_spec.rb +45 -0
  33. data/spec/examples/reference_spec.rb +43 -0
  34. data/spec/examples/theme_spec.rb +137 -0
  35. data/spec/gearbox/attribute_collection_spec.rb +33 -0
  36. data/spec/gearbox/mixins/ad_hoc_properties_spec.rb +52 -0
  37. data/spec/gearbox/mixins/resource_spec.rb +32 -0
  38. data/spec/gearbox/mixins/semantic_accessors_spec.rb +53 -0
  39. data/spec/gearbox/rdf_collection_spec.rb +52 -0
  40. data/spec/gearbox_spec.rb +13 -0
  41. data/spec/spec_helper.rb +16 -0
  42. metadata +235 -0
@@ -0,0 +1,29 @@
1
+ module Gearbox
2
+ class Person
3
+
4
+ attr_accessor :name
5
+ attr_accessor :twitter_account
6
+ attr_accessor :email
7
+ attr_accessor :website
8
+ attr_accessor :phone
9
+ attr_accessor :resource
10
+
11
+ end
12
+ end
13
+
14
+
15
+ # Person
16
+ # name
17
+ # tweet account | email | website | phone
18
+
19
+ # it "has a people association" do
20
+ # subject.person_source = OpenStruct.public_method(:new)
21
+ # name = "George Q. Cannon"
22
+ # twitter_account = "gcannon"
23
+ # person = subject.add_person(:name => name, :twitter_account => twitter_account)
24
+ # subject.people[0].must_equal person
25
+ # person.resource.must_equal subject
26
+ # person.name.must_equal name
27
+ # person.twitter_account.must_equal twitter_account
28
+ # end
29
+
@@ -0,0 +1,38 @@
1
+ module Gearbox
2
+ class Reference
3
+ attr_accessor :location
4
+ attr_accessor :audience
5
+
6
+ attr_writer :person_source
7
+ attr_writer :theme_source
8
+
9
+ def add_person(hash={})
10
+ new_person = person_source.call(hash.merge(:reference => self))
11
+ people << new_person
12
+ new_person
13
+ end
14
+
15
+ def people
16
+ @people ||= []
17
+ end
18
+
19
+ def add_theme(hash={})
20
+ new_theme = theme_source.call(hash.merge(:reference => self))
21
+ themes << new_theme
22
+ new_theme
23
+ end
24
+
25
+ def themes
26
+ @themes ||= []
27
+ end
28
+
29
+ private
30
+ def person_source
31
+ @person_source ||= Person.public_method(:new)
32
+ end
33
+
34
+ def theme_source
35
+ @theme_source ||= Theme.public_method(:new)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,8 @@
1
+ module Gearbox
2
+ class Theme
3
+
4
+ attr_accessor :name
5
+ attr_accessor :tally
6
+
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ # ================
2
+ # = Dependencies =
3
+ # ================
4
+ require 'uuid'
5
+ require 'linkeddata'
6
+ require 'ostruct'
7
+
8
+ module Gearbox
9
+
10
+ # ========================
11
+ # = Helper Utility: path =
12
+ # ========================
13
+ # @private
14
+ def path(path)
15
+ File.expand_path("../gearbox/#{path}", __FILE__)
16
+ end
17
+ private :path
18
+ module_function :path
19
+
20
+ # =======================
21
+ # = Loading the Library =
22
+ # =======================
23
+ autoload :AttributeCollection, path('attribute_collection')
24
+ autoload :RDFCollection, path('rdf_collection')
25
+
26
+ autoload :AdHocProperties, path('mixins/ad_hoc_properties')
27
+ autoload :Resource, path('mixins/resource')
28
+ autoload :SemanticAccessors, path('mixins/semantic_accessors')
29
+
30
+ # ============
31
+ # = Examples =
32
+ # ============
33
+
34
+ # Will separate these after things get to a solid 0.1 state.
35
+ autoload :Audience, path('../examples/audience')
36
+ autoload :Person, path('../examples/person')
37
+ autoload :Reference, path('../examples/reference')
38
+ autoload :Theme, path('../examples/theme')
39
+
40
+ end
@@ -0,0 +1,42 @@
1
+ module Gearbox
2
+
3
+ ##
4
+ # Collects attributes in a hash-like format. Serializes the attributes in an OpenStruct.
5
+ ##
6
+ class AttributeCollection
7
+
8
+ # Build a new AttributeCollection with an optional Hash.
9
+ # Note: this class normalizes the keys and creates OpenStructs for values.
10
+ # @param [Hash] default A hash with normalized keys and OpenStruct values
11
+ def initialize(default={})
12
+ raise ArgumentError, "Must provide a hash for the defaults" unless default.is_a?(Hash)
13
+ @default = default
14
+ @source = {}
15
+ end
16
+
17
+ # Set one attribute in the collection.
18
+ # @param [String, Symbol] key
19
+ # @param [Hash] hash
20
+ # @return [OpenStruct] The hash, converted into an OpenStruct
21
+ def []=(key, hash)
22
+ raise ArgumentError, "Must provide a hash for the value" unless hash.is_a?(Hash)
23
+ @source[normalize_key(key)] = OpenStruct.new(@default.merge(hash))
24
+ end
25
+
26
+ # Get one attribute from the collection.
27
+ # @param [String, Symbol] key
28
+ # @return [OpenStruct, nil] Returns the attribute OpenStruct, if found.
29
+ def [](key)
30
+ @source[normalize_key(key)]
31
+ end
32
+
33
+ private
34
+ # Normalizes the key. Converts to a lower case symbol with non-alpha-numerics
35
+ # replaced by underscores, removing trailing and preceding underscores.
36
+ # @private
37
+ def normalize_key(obj)
38
+ obj.to_s.downcase.gsub(/[^A-Za-z0-9_]+/, '_').gsub(/(_$)|(^_)/, '').to_sym
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ module Gearbox
2
+ ##
3
+ # Allows a model instance to add a new property at runtime.
4
+ # This is a (small) tip of the hat towards the flexibility
5
+ # offered by using a graph instead of a schema to govern
6
+ # the data store.
7
+ ##
8
+ module AdHocProperties
9
+
10
+ # Getter and setter for an id property.
11
+ # Will be adjusted when I decide whether to mixin ActiveModel
12
+ attr_accessor :id
13
+
14
+ # Stored attributes
15
+ # @return [RDFCollection]
16
+ def attributes_list
17
+ @attributes_list ||= RDFCollection.new
18
+ end
19
+
20
+ # Generates or gets a blank node, based on the id.
21
+ # Will be replaced by subject.
22
+ # @return [RDF::Node]
23
+ def bnode
24
+ return @bnode if @bnode
25
+ self.id ||= UUID.generate
26
+ safe_id = "#{self.class.name}_#{id}".gsub(/[^A-Za-z0-9\-_]/, '_')
27
+ @bnode = RDF::Node(safe_id)
28
+ end
29
+
30
+ # Add a property without defining it on the class.
31
+ # This will stay, will use the subject, and the regular infrastructure.
32
+ # @param [Symbol] accessor, the new field being created.
33
+ # @param [RDF::Statement] predicate, the predicate for the new field.
34
+ # @param [Any] The value to store
35
+ def add_property(accessor, predicate, object)
36
+ new_property = RDF::Statement.new(bnode, predicate, object)
37
+ attributes_list[accessor] = new_property
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ module Gearbox
2
+ ##
3
+ # The main mixin for any model.
4
+ # TODO: include an example file.
5
+ ##
6
+ module Resource
7
+
8
+ # ============
9
+ # = Behavior =
10
+ # ============
11
+ include AdHocProperties
12
+ include SemanticAccessors
13
+ include RDF::Mutable
14
+ include RDF::Queryable
15
+
16
+ end
17
+ end
@@ -0,0 +1,118 @@
1
+ module Gearbox
2
+
3
+ ##
4
+ # The attributes to add to a model.
5
+ # TODO: Add example from file.
6
+ ##
7
+ module SemanticAccessors
8
+
9
+ # Treat this as a bundle of class methods and instance methods.
10
+ # @private
11
+ def self.included(base)
12
+ base.extend ClassMethods
13
+ base.send :include, InstanceMethods
14
+ end
15
+
16
+ ##
17
+ # Class methods for the model.
18
+ ##
19
+ module ClassMethods
20
+
21
+ # Add an attribute or a field to a model. Takes a field name.
22
+ # Defines both a getter and a setter on the object.
23
+ # Requires a predicate option. Options are:
24
+ # * :predicate => RDF::URI
25
+ # * :reverse => Boolean store as value, predicate, subject
26
+ # * :index => Boolean maintain a full-text search index on this attribute
27
+ # @param [String, Symbol] getter_name, the field that is being created.
28
+ # @param [Hash] options
29
+ def attribute(getter_name, options={})
30
+
31
+ raise ArgumentError, "A predicate must be defined" unless options[:predicate]
32
+
33
+ send(attributes_source)[getter_name] = options
34
+
35
+ # Define a getter on the object
36
+ define_method(getter_name) do
37
+ self.class.yield_attr(getter_name, self)
38
+ end
39
+
40
+ # Define a setter on the object
41
+ define_method("#{getter_name}=") do |value|
42
+ self.class.store_attr(getter_name, self, value)
43
+ # attribute = send(self.class.attributes_source)[getter_name]
44
+ end
45
+
46
+ end
47
+
48
+ # Sets the attributes_source, where to store the attributes
49
+ attr_writer :attributes_source
50
+
51
+ # Gets the attributes_source...
52
+ def attributes_source
53
+ @attributes_source ||= :attribute_collection
54
+ end
55
+
56
+ def attribute_collection
57
+ @attribute_collection ||= AttributeCollection.new
58
+ end
59
+
60
+ def yield_attr(getter_name, instance)
61
+ if statement = instance.rdf_collection[getter_name]
62
+ # TODO: Deserialize object
63
+ return statement.object.to_s
64
+ else
65
+ attribute_options = send(attributes_source)[getter_name]
66
+ attribute_options ? attribute_options.default : nil
67
+ end
68
+ end
69
+
70
+ def store_attr(getter_name, instance, value)
71
+ attribute_options = send(attributes_source)[getter_name]
72
+ # TODO: serialize value
73
+ statement = RDF::Statement.new(instance.subject, attribute_options.predicate, value)
74
+ instance.rdf_collection[getter_name] = statement
75
+ end
76
+
77
+ end
78
+
79
+ module InstanceMethods
80
+ # We collect triples inside the models in order to query them, filter them
81
+ # and handle the bridge between domain models and the graph we're building.
82
+ def rdf_collection
83
+ @rdf_collection ||= RDFCollection.new
84
+ end
85
+
86
+ def subject
87
+ "1"
88
+ end
89
+
90
+ # An initialization strategy for all occasions.
91
+ def initialize(obj=nil)
92
+ case obj
93
+ when Hash
94
+ merge_hash_values(obj)
95
+ when RDFCollection
96
+ merge_rdf_collection(obj)
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def merge_hash_values(hash)
103
+ # TODO: work with associations here...
104
+ hash.each do |getter_name, value|
105
+ setter_name = "#{getter_name}="
106
+ send(setter_name, value) if respond_to?(setter_name)
107
+ # What to do with the others?
108
+ end
109
+ end
110
+
111
+ def merge_rdf_collection(collection)
112
+ rdf_collection.merge!(collection)
113
+ end
114
+
115
+ end # InstanceMethods
116
+
117
+ end # SemanticAccessors
118
+ end # Gearbox
@@ -0,0 +1,72 @@
1
+ module Gearbox
2
+
3
+ ##
4
+ # Collects model values as RDF. This is a key part of making SPARQL our primary
5
+ # filtering, finding, and extension language.
6
+ ##
7
+ class RDFCollection
8
+
9
+ # ============
10
+ # = Behavior =
11
+ # ============
12
+ include RDF::Enumerable
13
+
14
+ def initialize
15
+ @source = {}
16
+ end
17
+
18
+ # Enumerates on the RDF Statements. Necessary for RDF::Enumerable to
19
+ # add all of the internal and external iterator goodies available there
20
+ # (like each_subject and has_subject?).
21
+ # @param [Block] block Optional block. Creates an external iterator if omitted.
22
+ # @return [nil, Enumerator] Returns either nil, or an external iterator.
23
+ def each(&block)
24
+ if block_given?
25
+ @source.each(&block)
26
+ else
27
+ Enumerator.new(self, :each)
28
+ end
29
+ end
30
+
31
+ # Set RDF::Statements to the underlying collection. Normalizes the keys.
32
+ # @param [String, Symbol] key
33
+ # @param [RDF::Statement] obj. RDF::Statement that will be added.
34
+ def add_statement(key, obj)
35
+ @source[normalize_key(key)] = obj if obj.is_a?(RDF::Statement)
36
+ end
37
+ alias :[]= :add_statement
38
+
39
+ # Get RDF::Statement from the underlying collection. Normalizes the key.
40
+ # @param [String, Symbol] key. Normalized.
41
+ # @return [RDF::Statement, nil] Found statement, if it exists.
42
+ def [](key)
43
+ @source[normalize_key(key)]
44
+ end
45
+
46
+ # Lookup whether the key exists.
47
+ # @param [String, Symbol] key
48
+ # @param [Hash, nil] opts. :normalize => false will lookup the key as provided.
49
+ # @return [Boolean]
50
+ def has_key?(key, opts={})
51
+ key = normalize_key(key) if opts.fetch(:normalize, true)
52
+ @source.has_key?(key)
53
+ end
54
+
55
+ # Merges a hash of RDF::Statements into the underlying collection.
56
+ # Uses the add_statement to filter the values of the hash.
57
+ # @param [Hash] hash. Collection of statements.
58
+ # @return [nil]
59
+ def merge!(hash)
60
+ hash.each {|key, obj| add_statement(key, obj)}
61
+ end
62
+
63
+ private
64
+ # Normalizes the key. Converts to a lower case symbol with non-alpha-numerics
65
+ # replaced by underscores, removing trailing and preceding underscores.
66
+ # @private
67
+ def normalize_key(obj)
68
+ obj.to_s.downcase.gsub(/[^A-Za-z0-9_]+/, '_').gsub(/(_$)|(^_)/, '').to_sym
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,85 @@
1
+ module Gearbox
2
+
3
+ ##
4
+ # This was taken wholesale from Spira. I think Ben did a great job with that.
5
+ # I have nothing to add or detract from his good work.
6
+ #
7
+ # Gearbox::Type can be included by classes to create new property types for
8
+ # Gearbox. These types are responsible for serialization a Ruby value into an
9
+ # `RDF::Value`, and deserialization of an `RDF::Value` into a Ruby value.
10
+ #
11
+ # A simple example:
12
+ #
13
+ # class Integer
14
+ #
15
+ # include Gearbox::Type
16
+ #
17
+ # def self.unserialize(value)
18
+ # value.object
19
+ # end
20
+ #
21
+ # def self.serialize(value)
22
+ # RDF::Literal.new(value)
23
+ # end
24
+ #
25
+ # register_alias XSD.integer
26
+ # end
27
+ #
28
+ # This example will serialize and deserialize integers. It's included with
29
+ # Gearbox by default. It allows either of the following forms to declare an
30
+ # integer property on a Gearbox resource:
31
+ #
32
+ # property :age, :predicate => FOAF.age, :type => Integer
33
+ # property :age, :predicate => FOAF.age, :type => XSD.integer
34
+ #
35
+ # `Gearbox::Type`s include the RDF namespace and thus have all of the base RDF
36
+ # vocabularies available to them without the `RDF::` prefix.
37
+ #
38
+ # @see http://rdf.rubyforge.org/RDF/Value.html
39
+ # @see Gearbox::Resource
40
+ module Type
41
+
42
+ ##
43
+ # Make the DSL available to a child class.
44
+ #
45
+ # @private
46
+ def self.included(child)
47
+ child.extend(ClassMethods)
48
+ Gearbox.type_alias(child,child)
49
+ end
50
+
51
+ include RDF
52
+
53
+ module ClassMethods
54
+
55
+ ##
56
+ # Register an alias that this type can be referred to as, such as an RDF
57
+ # URI. The alias can be any object, symbol, or constant.
58
+ #
59
+ # @param [Any] identifier The new alias in property declarations for this class
60
+ # @return [Void]
61
+ def register_alias(any)
62
+ Gearbox.type_alias(any, self)
63
+ end
64
+
65
+ ##
66
+ # Serialize a given value to RDF.
67
+ #
68
+ # @param [Any] value The Ruby value to be serialized
69
+ # @return [RDF::Value] The RDF form of this value
70
+ def serialize(value)
71
+ value
72
+ end
73
+
74
+ ##
75
+ # Unserialize a given RDF value to Ruby
76
+ #
77
+ # @param [RDF::Value] value The RDF form of this value
78
+ # @return [Any] The Ruby form of this value
79
+ def unserialize(value)
80
+ value
81
+ end
82
+ end
83
+
84
+ end
85
+ end