Skip to content

Commit

Permalink
Support nested attributes for RDF properties. Fixes #682
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed Jan 13, 2015
1 parent bee58b8 commit 632aabf
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 173 deletions.
1 change: 0 additions & 1 deletion lib/active_fedora/attributes.rb
Expand Up @@ -175,7 +175,6 @@ def property name, properties={}, &block
find_or_create_defined_attribute(name, nil, properties)
raise ArgumentError, "#{name} is a keyword and not an acceptable property name." if protected_property_name? name
reflection = ActiveFedora::Attributes::PropertyBuilder.build(self, name, properties, &block)
# reflection = ActiveTriple::PropertyBuilder.build(self, name, properties, &block)
ActiveTriples::Reflection.add_reflection self, name, reflection
end

Expand Down
11 changes: 11 additions & 0 deletions lib/active_fedora/nested_attributes.rb
Expand Up @@ -83,6 +83,17 @@ def #{association_name}_attributes=(attributes)
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
end
eoruby
elsif reflection = reflect_on_property(association_name)
resource_class.accepts_nested_attributes_for(association_name)

# Delegate the setter to the resource.
class_eval <<-eoruby, __FILE__, __LINE__ + 1
remove_possible_method(:#{association_name}_attributes=)
def #{association_name}_attributes=(attributes)
resource.#{association_name}_attributes=(attributes)
end
eoruby
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
end
Expand Down
185 changes: 185 additions & 0 deletions spec/integration/datastream_rdf_nested_attributes_spec.rb
@@ -0,0 +1,185 @@
require 'spec_helper'

describe "Nesting attribute behavior of RDFDatastream" do
describe ".attributes=" do

context "complex properties in a datastream" do
before do
class DummyMADS < RDF::Vocabulary("http://www.loc.gov/mads/rdf/v1#")
# TODO this test is order dependent. It expects to use the object created in the previous test
# componentList and Types of components
property :componentList
property :Topic
property :Temporal
property :PersonalName
property :CorporateName
property :ComplexSubject


# elementList and elementList values
property :elementList
property :elementValue
property :TopicElement
property :TemporalElement
property :NameElement
property :FullNameElement
property :DateNameElement
end

class ComplexRDFDatastream < ActiveFedora::NtriplesRDFDatastream
property :topic, predicate: DummyMADS.Topic, class_name: "Topic"
property :personalName, predicate: DummyMADS.PersonalName, class_name: "PersonalName"
property :title, predicate: ::RDF::DC.title


accepts_nested_attributes_for :topic, :personalName

class Topic < ActiveTriples::Resource
property :elementList, predicate: DummyMADS.elementList, class_name: "ComplexRDFDatastream::ElementList"
accepts_nested_attributes_for :elementList
end
class PersonalName < ActiveTriples::Resource
property :elementList, predicate: DummyMADS.elementList, class_name: "ComplexRDFDatastream::ElementList"
property :extraProperty, predicate: DummyMADS.elementValue, class_name: "ComplexRDFDatastream::Topic"
accepts_nested_attributes_for :elementList, :extraProperty
end
class ElementList < ActiveTriples::List
configure type: DummyMADS.elementList
property :topicElement, predicate: DummyMADS.TopicElement, class_name: "ComplexRDFDatastream::MadsTopicElement"
property :temporalElement, predicate: DummyMADS.TemporalElement
property :fullNameElement, predicate: DummyMADS.FullNameElement
property :dateNameElement, predicate: DummyMADS.DateNameElement
property :nameElement, predicate: DummyMADS.NameElement
property :elementValue, predicate: DummyMADS.elementValue
accepts_nested_attributes_for :topicElement
end
class MadsTopicElement < ActiveTriples::Resource
configure :type => DummyMADS.TopicElement
property :elementValue, predicate: DummyMADS.elementValue
end
end
end
after do
Object.send(:remove_const, :ComplexRDFDatastream)
Object.send(:remove_const, :DummyMADS)
end
subject { ComplexRDFDatastream.new }
let(:params) do
{ myResource:
{
topic_attributes: {
'0' =>
{
elementList_attributes: [{
topicElement_attributes: [{
id: 'http://library.ucsd.edu/ark:/20775/bb3333333x',
elementValue:"Cosmology"
}]
}]
},
'1' =>
{
elementList_attributes: [{
topicElement_attributes: {'0' => {elementValue:"Quantum Behavior"}}
}]
}
},
personalName_attributes: [
{
id: 'http://library.ucsd.edu/ark:20775/jefferson',
elementList_attributes: [{
fullNameElement: "Jefferson, Thomas",
dateNameElement: "1743-1826"
}]
}
#, "Hemings, Sally"
],
}
}
end

describe "on lists" do
subject { ComplexRDFDatastream::PersonalName.new(RDF::Graph.new) }
it "should accept a hash" do
subject.elementList_attributes = [{ topicElement_attributes: {'0' => { elementValue:"Quantum Behavior" }, '1' => { elementValue:"Wave Function" }}}]
expect(subject.elementList.first[0].elementValue).to eq ["Quantum Behavior"]
expect(subject.elementList.first[1].elementValue).to eq ["Wave Function"]

end
it "should accept an array" do
subject.elementList_attributes = [{ topicElement_attributes: [{ elementValue:"Quantum Behavior" }, { elementValue:"Wave Function" }]}]
expect(subject.elementList.first[0].elementValue).to eq ["Quantum Behavior"]
expect(subject.elementList.first[1].elementValue).to eq ["Wave Function"]
end
end

context "from nested objects" do
before do
# Replace the graph's contents with the Hash
subject.attributes = params[:myResource]
end

it 'should have attributes' do
expect(subject.topic[0].elementList.first[0].elementValue).to eq ["Cosmology"]
expect(subject.topic[1].elementList.first[0].elementValue).to eq ["Quantum Behavior"]
expect(subject.personalName.first.elementList.first.fullNameElement).to eq ["Jefferson, Thomas"]
expect(subject.personalName.first.elementList.first.dateNameElement).to eq ["1743-1826"]
end

it 'should build nodes with ids' do
expect(subject.topic[0].elementList.first[0].rdf_subject).to eq 'http://library.ucsd.edu/ark:/20775/bb3333333x'
expect(subject.personalName.first.rdf_subject).to eq 'http://library.ucsd.edu/ark:20775/jefferson'
end

it 'should fail when writing to a non-predicate' do
attributes = { topic_attributes: { '0' => { elementList_attributes: [{ topicElement_attributes: [{ fake_predicate:"Cosmology" }] }]}}}
expect{ subject.attributes = attributes }.to raise_error ArgumentError
end

it 'should fail when writing to a non-predicate with a setter method' do
attributes = { topic_attributes: { '0' => { elementList_attributes: [{ topicElement_attributes: [{ name:"Cosmology" }] }]}}}
expect{ subject.attributes = attributes }.to raise_error ArgumentError
end
end
end

describe "with an existing object" do
before(:each) do
class SpecDatastream < ActiveFedora::NtriplesRDFDatastream
property :parts, predicate: ::RDF::DC.hasPart, :class_name=>'Component'
accepts_nested_attributes_for :parts, allow_destroy: true

class Component < ActiveTriples::Resource
property :label, predicate: ::RDF::DC.title
end
end

end

after(:each) do
Object.send(:remove_const, :SpecDatastream)
end
subject { SpecDatastream.new }
before do
subject.attributes = { parts_attributes: [
{label: 'Alternator'},
{label: 'Distributor'},
{label: 'Transmission'},
{label: 'Fuel Filter'}]}
end
let (:replace_object_id) { subject.parts[1].rdf_subject.to_s }
let (:remove_object_id) { subject.parts[3].rdf_subject.to_s }

it "should update nested objects" do
subject.parts_attributes= [{id: replace_object_id, label: "Universal Joint"}, {label:"Oil Pump"}, {id: remove_object_id, _destroy: '1', label: "bar1 uno"}]

expect(subject.parts.map{|p| p.label.first}).to eq ['Alternator', 'Universal Joint', 'Transmission', 'Oil Pump']

end
it "create a new object when the id is provided" do
subject.parts_attributes= [{id: 'http://example.com/part#1', label: "Universal Joint"}]
expect(subject.parts.last.rdf_subject).to eq RDF::URI('http://example.com/part#1')
end
end
end
end

0 comments on commit 632aabf

Please sign in to comment.