Skip to content
Browse files

Replace the old attribute system with the attribute class

  • Loading branch information...
1 parent cef3bdb commit e8d5a5725be16f29dce7b391a458d8a08712691d @davidrichards committed Apr 3, 2012
Showing with 340 additions and 151 deletions.
  1. +260 −96 lib/gearbox/mixins/semantic_accessors.rb
  2. +80 −55 spec/gearbox/mixins/semantic_accessors_spec.rb
View
356 lib/gearbox/mixins/semantic_accessors.rb
@@ -1,127 +1,291 @@
module Gearbox
- ##
- # The attributes to add to a model.
- # TODO: Add example from file.
- ##
+ # These are the attributes and associations that the user adds to a model.
module SemanticAccessors
- # Treat this as a bundle of class methods and instance methods.
- # @private
def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods
end
- ##
- # Class methods for the model.
- ##
module ClassMethods
- # Add an attribute or a field to a model. Takes a field name.
- # Defines both a getter and a setter on the object.
- # Requires a predicate option. Options are:
- # * :predicate => RDF::URI
- # * :reverse => Boolean store as value, predicate, subject
- # * :index => Boolean maintain a full-text search index on this attribute
- # @param [String, Symbol] getter_name, the field that is being created.
- # @param [Hash] options
- def attribute(getter_name, options={})
-
- raise ArgumentError, "A predicate must be defined" unless options[:predicate]
-
- defaults = {:type => Gearbox::Types::Any}
-
- send(attributes_source)[getter_name] = defaults.merge(options)
-
- # Define a getter on the object
- define_method(getter_name) do
- self.class.yield_attr(getter_name, self)
- end
-
- # Define a setter on the object
- define_method("#{getter_name}=") do |value|
- self.class.store_attr(getter_name, self, value)
- # attribute = send(self.class.attributes_source)[getter_name]
- end
-
+ def attributes
+ @attributes ||= {}
end
- # Sets the attributes_source, where to store the attributes
- attr_writer :attributes_source
-
- # Gets the attributes_source...
- def attributes_source
- @attributes_source ||= :attribute_collection
- end
+ def attribute(name, opts={})
+ opts = opts.merge(:name => name)
- def attribute_collection
- @attribute_collection ||= AttributeCollection.new
- end
-
- def yield_attr(getter_name, instance)
- if statement = instance.rdf_collection[getter_name]
- attribute_options = instance.class.attribute_collection[getter_name]
- type = attribute_options ? attribute_options.type : nil
- type ? type.unserialize(statement.object) : statement.object.to_s
- else
- attribute_options = send(attributes_source)[getter_name]
- attribute_options ? attribute_options.default : nil
+ define_method(name) do
+ attribute_definitions[name] ||= Attribute.new(opts)
+ attribute_definitions[name].to_value
+ end
+
+ define_method("#{name}=") do |value|
+ attribute_definitions[name] ||= Attribute.new(opts)
+ attribute_definitions[name].set(value)
end
- end
-
- def store_attr(getter_name, instance, value)
- attribute_options = send(attributes_source)[getter_name]
- type = attribute_options.type
- value = type.serialize(value) if type.respond_to?(:serialize)
- statement = RDF::Statement.new(instance.subject, attribute_options.predicate, value)
- instance.rdf_collection[getter_name] = statement
- end
+ attributes[name] = opts
+ end
end
module InstanceMethods
- # We collect triples inside the models in order to query them, filter them
- # and handle the bridge between domain models and the graph we're building.
- def rdf_collection
- @rdf_collection ||= RDFCollection.new
+
+ def initialize(opts={})
+ super
+ assert_defaults
+ assert_options(opts)
end
- def subject
- # Will be updated...
- @subject ||= RDF::Node.new
- end
-
- # An initialization strategy for all occasions.
- def initialize(obj=nil)
- case obj
- when Hash
- merge_hash_values(obj)
- when RDFCollection
- merge_rdf_collection(obj)
- end
+ def id
+ @id ||= object_id
end
+ attr_writer :id
def attributes
- # Hash-like object ([] and []=)
+ self.class.attributes.inject({:id => id}) do |hash, (name, opts)|
+ hash[name] = send(name)
+ hash
+ end
end
private
- def merge_hash_values(hash)
- # TODO: work with associations here...
- hash.each do |getter_name, value|
- setter_name = "#{getter_name}="
- send(setter_name, value) if respond_to?(setter_name)
- # What to do with the others?
- end
+ def assert_defaults
+ self.class.attributes.each do |name, opts|
+ next unless opts.has_key?(:default)
+ setter = "#{name}="
+ send(setter, opts[:default])
end
-
- def merge_rdf_collection(collection)
- rdf_collection.merge!(collection)
+ end
+
+ def assert_options(opts)
+ opts.each do |name, value|
+ setter = "#{name}="
+ send(setter, value) if respond_to?(setter)
end
+ end
+
+ def attribute_definitions
+ @attribute_definitions ||= {}
+ end
- end # InstanceMethods
-
- end # SemanticAccessors
-end # Gearbox
+ end
+ end
+end
+
+
+
+# class Z
+#
+# # ============
+# # = Behavior =
+# # ============
+# include RDF::Queryable
+# include ActiveModel::Validations
+# include ActiveModel::Conversion
+# extend ActiveModel::Naming
+#
+# def self.attribute(name, opts={})
+#
+# opts = opts.merge(:name => name)
+#
+# define_method(name) do
+# attribute_definitions[name] ||= Attribute.new(opts)
+# attribute_definitions[name].to_value
+# end
+#
+# define_method("#{name}=") do |value|
+# attribute_definitions[name] ||= Attribute.new(opts)
+# attribute_definitions[name].set(value)
+# end
+#
+# attributes[name] = opts
+# end
+#
+# def self.attributes
+# @attributes ||= {}
+# end
+#
+# attribute :name, :predicate => RDF::FOAF.name
+# attribute :email, :predicate => RDF::FOAF.mbox
+#
+# def initialize(opts={})
+# assert_defaults
+# assert_options(opts)
+# end
+#
+# def id
+# @id ||= object_id
+# end
+# attr_writer :id
+#
+# def subject
+# "http://example.com/z/#{id}"
+# end
+#
+# def attributes
+# self.class.attributes.inject({:id => id}) do |hash, (name, opts)|
+# hash[name] = send(name)
+# hash
+# end
+# end
+#
+# def each(opts={}, &block)
+# attribute_definitions.map{|name, attribute| attribute.to_rdf(self, opts)}.each(&block)
+# end
+#
+# def inspect
+# "Z: #{name}"
+# end
+#
+# def persisted?
+# false
+# end
+#
+# private
+#
+# def assert_defaults
+# end
+#
+# def assert_options(opts)
+# opts.each do |accessor, value|
+# send("#{accessor}=", value) if respond_to?("#{accessor}=")
+# end
+# end
+#
+# def attribute_definitions
+# @attribute_definitions ||= {}
+# end
+# end
+
+
+# module Gearbox
+#
+# ##
+# # The attributes to add to a model.
+# # TODO: Add example from file.
+# ##
+# module SemanticAccessors
+#
+# # Treat this as a bundle of class methods and instance methods.
+# # @private
+# def self.included(base)
+# base.extend ClassMethods
+# base.send :include, InstanceMethods
+# end
+#
+# ##
+# # Class methods for the model.
+# ##
+# module ClassMethods
+#
+# # Add an attribute or a field to a model. Takes a field name.
+# # Defines both a getter and a setter on the object.
+# # Requires a predicate option. Options are:
+# # * :predicate => RDF::URI
+# # * :reverse => Boolean store as value, predicate, subject
+# # * :index => Boolean maintain a full-text search index on this attribute
+# # @param [String, Symbol] getter_name, the field that is being created.
+# # @param [Hash] options
+# def attribute(getter_name, options={})
+#
+# raise ArgumentError, "A predicate must be defined" unless options[:predicate]
+#
+# defaults = {:type => Gearbox::Types::Any}
+#
+# send(attributes_source)[getter_name] = defaults.merge(options)
+#
+# # Define a getter on the object
+# define_method(getter_name) do
+# self.class.yield_attr(getter_name, self)
+# end
+#
+# # Define a setter on the object
+# define_method("#{getter_name}=") do |value|
+# self.class.store_attr(getter_name, self, value)
+# # attribute = send(self.class.attributes_source)[getter_name]
+# end
+#
+# end
+#
+# # Sets the attributes_source, where to store the attributes
+# attr_writer :attributes_source
+#
+# # Gets the attributes_source...
+# def attributes_source
+# @attributes_source ||= :attribute_collection
+# end
+#
+# def attribute_collection
+# @attribute_collection ||= AttributeCollection.new
+# end
+#
+# def yield_attr(getter_name, instance)
+# if statement = instance.rdf_collection[getter_name]
+# attribute_options = instance.class.attribute_collection[getter_name]
+# type = attribute_options ? attribute_options.type : nil
+# type ? type.unserialize(statement.object) : statement.object.to_s
+# else
+# attribute_options = send(attributes_source)[getter_name]
+# attribute_options ? attribute_options.default : nil
+# end
+# end
+#
+# def store_attr(getter_name, instance, value)
+# attribute_options = send(attributes_source)[getter_name]
+# type = attribute_options.type
+# value = type.serialize(value) if type.respond_to?(:serialize)
+# statement = RDF::Statement.new(instance.subject, attribute_options.predicate, value)
+# instance.rdf_collection[getter_name] = statement
+# end
+#
+# end
+#
+# module InstanceMethods
+# # We collect triples inside the models in order to query them, filter them
+# # and handle the bridge between domain models and the graph we're building.
+# def rdf_collection
+# @rdf_collection ||= RDFCollection.new
+# end
+#
+# def subject
+# # Will be updated...
+# @subject ||= RDF::Node.new
+# end
+#
+# # An initialization strategy for all occasions.
+# def initialize(obj=nil)
+# case obj
+# when Hash
+# merge_hash_values(obj)
+# when RDFCollection
+# merge_rdf_collection(obj)
+# end
+# end
+#
+# def attributes
+# # Hash-like object ([] and []=)
+# end
+#
+# private
+#
+# def merge_hash_values(hash)
+# # TODO: work with associations here...
+# hash.each do |getter_name, value|
+# setter_name = "#{getter_name}="
+# send(setter_name, value) if respond_to?(setter_name)
+# # What to do with the others?
+# end
+# end
+#
+# def merge_rdf_collection(collection)
+# rdf_collection.merge!(collection)
+# end
+#
+# end # InstanceMethods
+#
+# end # SemanticAccessors
+# end # Gearbox
View
135 spec/gearbox/mixins/semantic_accessors_spec.rb
@@ -4,62 +4,87 @@
describe SemanticAccessors do
- before do
- @class = Class.new do
- def self.name; 'demo_class'; end
- include SemanticAccessors
- end
- end
-
- subject { @class.new }
-
- it "has an rdf_collection" do
- subject.must_respond_to :rdf_collection
- subject.rdf_collection.must_be_kind_of RDFCollection
- end
-
- it "has an attribute_collection on the class" do
- subject.class.must_respond_to :attribute_collection
- subject.class.attribute_collection.must_be_kind_of AttributeCollection
- end
-
- it "has an attribute macro for setting up new attributes" do
- @class.attribute :given_name, :predicate => RDF::FOAF.givenname
- subject.given_name = "Frank"
- subject.given_name.must_equal("Frank")
- end
- it "raises an error unless a predicate is defined" do
- lambda{@class.attribute :given_name}.must_raise(ArgumentError, /predicate/i)
- end
-
- it "takes a hash on initialization" do
- @class.attribute :given_name, :predicate => RDF::FOAF.givenname
- subject = @class.new :given_name => "Frank"
- subject.given_name.must_equal "Frank"
- end
-
- it "takes an RDFCollection on initialization" do
- @class.attribute :given_name, :predicate => RDF::FOAF.givenname
- collection = RDFCollection.new
- collection[:given_name] = RDF::Statement.new(:a, RDF::FOAF.givenname, 'Frank')
- collection[:family_name] = RDF::Statement.new(:a, RDF::FOAF.familyname, 'Wilde')
- subject = @class.new(collection)
- subject.given_name.must_equal "Frank"
- end
-
- it "defaults the type to Gearbox::Types::Any" do
- @class.attribute :given_name, :predicate => RDF::FOAF.givenname
- @class.attribute_collection[:given_name].type.must_equal Gearbox::Types::Any
- end
+ describe "Class methods" do
+ before do
+ @class = Class.new do
+ def self.name; 'demo_class'; end
+ include SemanticAccessors
+ end
+ end
+
+ it "uses an attributes hash" do
+ @class.attributes.must_be_kind_of Hash
+ end
+
+ describe "attribute" do
+ it "has an attribute method" do
+ @class.respond_to?(:attribute).must_equal true
+ end
+
+ it "defines a setter and getter" do
+ @class.attribute(:name, :predicate => RDF::FOAF.name)
+ subject = @class.new
+ subject.respond_to?(:name).must_equal true
+ subject.respond_to?(:name=).must_equal true
+ end
+
+ it "stores the options + {:name => name} in the attributes hash" do
+ @class.attribute(:name, :predicate => RDF::FOAF.name)
+ @class.attributes[:name].must_equal({:name => :name, :predicate => RDF::FOAF.name})
+ end
+ end
+ end # "Class methods"
- it "stores the literals with their type" do
- @class.attribute :file_release, :predicate => RDF::DOAP["file-release"], :type => Types::Date
- subject = @class.new(:file_release => Time.now)
- release = subject.rdf_collection.query("select ?release where {?s <#{RDF::DOAP["file-release"]}> ?release}")[0].release
- release.must_be_kind_of RDF::Literal::Date
- release.object.must_be_kind_of Date
- release.object.day.must_equal Time.now.day
- end
+ describe "Instance methods" do
+ before do
+ @class = Class.new do
+ include SemanticAccessors
+ attribute :name, :predicate => RDF::FOAF.name
+ end
+ end
+ subject { @class.new }
+
+ it "defaults the id to object_id" do
+ subject.id.must_equal subject.object_id
+ end
+
+ it "has an id setter" do
+ subject.id = :whale
+ subject.id.must_equal :whale
+ end
+
+ it "creates a setter and a getter for each attribute" do
+ subject.name = "George"
+ subject.name.must_equal "George"
+ end
+
+ it "supports the attribute data types" do
+ # In fact, it supports the full attribute system, this is just a quick check.
+ @class.attribute :birthday, :predicate => RDF::FOAF.birthday, :datatype => RDF::XSD.date
+ subject.birthday = Time.now
+ subject.birthday.must_equal Time.now.to_date
+ end
+
+ it "creates an attributes hash from attribute values" do
+ subject.name = "George"
+ subject.attributes.must_equal({:id => subject.id, :name => "George"})
+ end
+
+ it "uses the default attribute parameter to set defaults" do
+ @class.attribute :spanish_name, :predicate => RDF::FOAF.name, :language => :es, :default => "Jorge"
+ subject = @class.new
+ subject.spanish_name.must_equal "Jorge"
+ end
+
+ it "can take attribute values as an initialization hash" do
+ subject = @class.new(:name => "George")
+ subject.name.must_equal "George"
+ end
+
+ # TODO: Once I implement some finds via SPARQL, I should standardize the kind of data I'll be feeding
+ # the model initialization. At that point, either refactor the RDF::Statement feature in Attribute,
+ # or add it in the initialization process.
+ end # "Instance methods"
end

0 comments on commit e8d5a57

Please sign in to comment.
Something went wrong with that request. Please try again.