Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track persistence independent of presence of an identifier #339

Merged
merged 1 commit into from Jan 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 10 additions & 2 deletions lib/valkyrie/persistence/fedora/persister/model_converter.rb
Expand Up @@ -12,13 +12,21 @@ def initialize(resource:, adapter:, subject_uri: RDF::URI(""))

def convert
graph_resource.graph.delete([nil, nil, nil])
resource.attributes.each do |key, values|
output = property_converter.for(Property.new(subject_uri, key, values, adapter, resource)).result
properties.each do |property|
values = resource_attributes[property]

output = property_converter.for(Property.new(subject_uri, property, values, adapter, resource)).result
graph_resource.graph << output.to_graph
end
graph_resource
end

def properties
resource_attributes.keys - [:new_record]
end

delegate :attributes, to: :resource, prefix: true

def graph_resource
@graph_resource ||= ::Ldp::Container::Basic.new(connection, subject, nil, base_path)
end
Expand Down
5 changes: 3 additions & 2 deletions lib/valkyrie/persistence/fedora/persister/orm_converter.rb
Expand Up @@ -14,8 +14,9 @@ def convert
end

def attributes
GraphToAttributes.new(graph: graph, adapter: adapter).convert.merge(id:
id)
GraphToAttributes.new(graph: graph, adapter: adapter)
.convert
.merge(id: id, new_record: false)
end

def id
Expand Down
4 changes: 3 additions & 1 deletion lib/valkyrie/persistence/memory/persister.rb
Expand Up @@ -41,7 +41,9 @@ def wipe!
private

def generate_id(resource)
resource.new(id: SecureRandom.uuid, created_at: Time.current)
resource.new(id: SecureRandom.uuid,
created_at: Time.current,
new_record: false)
end

def normalize_dates!(resource)
Expand Down
164 changes: 90 additions & 74 deletions lib/valkyrie/persistence/postgres/orm_converter.rb
Expand Up @@ -6,113 +6,129 @@ def initialize(orm_object)
@orm_object = orm_object
end

# Create a new instance of the class described in attributes[:internal_resource]
# and send it all the attributes that @orm_object has
def convert!
@resource ||= attributes[:internal_resource].constantize.new(attributes)
@resource ||= resource
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this refactor to make the postgres/orm_converter more like the solr/orm_converter which has these additional methods

end

# @return [Hash] Valkyrie-style hash of attributes.
def attributes
@attributes ||= orm_object.attributes.merge(rdf_metadata).symbolize_keys
end
private

def rdf_metadata
@rdf_metadata ||= RDFMetadata.new(orm_object.metadata).result
end
def resource
resource_klass.new(attributes.merge(new_record: false))
end

class RDFMetadata
attr_reader :metadata
def initialize(metadata)
@metadata = metadata
def resource_klass
internal_resource.constantize
end

def result
Hash[
metadata.map do |key, value|
[key, PostgresValue.for(value).result]
end
]
def internal_resource
attributes[:internal_resource]
end

class PostgresValue < ::Valkyrie::ValueMapper
# @return [Hash] Valkyrie-style hash of attributes.
def attributes
@attributes ||= orm_object.attributes.merge(rdf_metadata).symbolize_keys
end
# Converts {RDF::Literal} typed-literals from JSON-LD stored into an
# {RDF::Literal}
class HashValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.is_a?(Hash) && value["@value"]
end

def result
RDF::Literal.new(value["@value"], language: value["@language"])
end
def rdf_metadata
@rdf_metadata ||= RDFMetadata.new(orm_object.metadata).result
end

# Converts stored IDs into {Valkyrie::ID}s
class IDValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.is_a?(Hash) && value["id"] && !value["internal_resource"]
class RDFMetadata
attr_reader :metadata
def initialize(metadata)
@metadata = metadata
end

def result
Valkyrie::ID.new(value["id"])
Hash[
metadata.map do |key, value|
[key, PostgresValue.for(value).result]
end
]
end
end

# Converts stored URIs into {RDF::URI}s
class URIValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.is_a?(Hash) && value["@id"]
class PostgresValue < ::Valkyrie::ValueMapper
end
# Converts {RDF::Literal} typed-literals from JSON-LD stored into an
# {RDF::Literal}
class HashValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.is_a?(Hash) && value["@value"]
end

def result
::RDF::URI.new(value["@id"])
def result
RDF::Literal.new(value["@value"], language: value["@language"])
end
end
end

# Converts nested records into {Valkyrie::Resource}s
class NestedRecord < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.is_a?(Hash) && value.keys.length > 1
end
# Converts stored IDs into {Valkyrie::ID}s
class IDValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.is_a?(Hash) && value["id"] && !value["internal_resource"]
end

def result
RDFMetadata.new(value).result.symbolize_keys
def result
Valkyrie::ID.new(value["id"])
end
end
end

class DateValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
return false unless value.is_a?(String)
return false unless value[4] == "-"
year = value.to_s[0..3]
return false unless year.length == 4 && year.to_i.to_s == year
DateTime.iso8601(value)
rescue
false
# Converts stored URIs into {RDF::URI}s
class URIValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.is_a?(Hash) && value["@id"]
end

def result
::RDF::URI.new(value["@id"])
end
end

def result
DateTime.iso8601(value).utc
# Converts nested records into {Valkyrie::Resource}s
class NestedRecord < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.is_a?(Hash) && value.keys.length > 1
end

def result
RDFMetadata.new(value).result.symbolize_keys
end
end
end

class EnumeratorValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.respond_to?(:each)
class DateValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
return false unless value.is_a?(String)
return false unless value[4] == "-"
year = value.to_s[0..3]
return false unless year.length == 4 && year.to_i.to_s == year
DateTime.iso8601(value)
rescue
false
end

def result
DateTime.iso8601(value).utc
end
end

def result
value.map do |value|
calling_mapper.for(value).result
class EnumeratorValue < ::Valkyrie::ValueMapper
PostgresValue.register(self)
def self.handles?(value)
value.respond_to?(:each)
end

def result
value.map do |value|
calling_mapper.for(value).result
end
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/valkyrie/persistence/solr/model_converter.rb
Expand Up @@ -56,7 +56,7 @@ def attribute_hash
end

def properties
resource_attributes.keys - [:id, :created_at, :updated_at]
resource_attributes.keys - [:id, :created_at, :updated_at, :new_record]
end

def resource_attributes
Expand Down
2 changes: 1 addition & 1 deletion lib/valkyrie/persistence/solr/orm_converter.rb
Expand Up @@ -13,7 +13,7 @@ def convert!
end

def resource
resource_klass.new(attributes.symbolize_keys)
resource_klass.new(attributes.symbolize_keys.merge(new_record: false))
end

def resource_klass
Expand Down
5 changes: 3 additions & 2 deletions lib/valkyrie/resource.rb
Expand Up @@ -20,11 +20,12 @@ def self.inherited(subclass)
subclass.attribute :internal_resource, Valkyrie::Types::Any.default(subclass.to_s)
subclass.attribute :created_at, Valkyrie::Types::DateTime.optional
subclass.attribute :updated_at, Valkyrie::Types::DateTime.optional
subclass.attribute :new_record, Types::Bool.default(true)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not have made this a schema attribute, but due to the use of constructor_type :schema I don't see any way around it. Suggestions?

end

# @return [Array<Symbol>] Array of fields defined for this class.
def self.fields
schema.keys
schema.keys.without(:new_record)
end

# Define an attribute.
Expand Down Expand Up @@ -75,7 +76,7 @@ def column_for_attribute(name)

# @return [Boolean]
def persisted?
to_param.present?
@new_record == false
end

def to_key
Expand Down
5 changes: 4 additions & 1 deletion lib/valkyrie/specs/shared_specs/persister.rb
Expand Up @@ -24,7 +24,10 @@ class CustomResource < Valkyrie::Resource
it { is_expected.to respond_to(:delete).with_keywords(:resource) }

it "can save a resource" do
expect(persister.save(resource: resource).id).not_to be_blank
expect(resource).not_to be_persisted
saved = persister.save(resource: resource)
expect(saved).to be_persisted
expect(saved.id).not_to be_blank
end

it "can save multiple resources at once" do
Expand Down
4 changes: 3 additions & 1 deletion lib/valkyrie/specs/shared_specs/queries.rb
Expand Up @@ -55,7 +55,9 @@ class SecondResource < Valkyrie::Resource
it "returns a resource by id" do
resource = persister.save(resource: resource_class.new)

expect(query_service.find_by(id: resource.id).id).to eq resource.id
found = query_service.find_by(id: resource.id)
expect(found.id).to eq resource.id
expect(found).to be_persisted
end

it "returns a Valkyrie::Persistence::ObjectNotFoundError for a non-found ID" do
Expand Down
12 changes: 7 additions & 5 deletions spec/valkyrie/resource_spec.rb
Expand Up @@ -32,12 +32,14 @@ class Resource < Valkyrie::Resource
end

describe "#persisted?" do
it "returns false if the ID is gone" do
expect(resource).not_to be_persisted
context 'when nothing is passed to the constructor' do
it { is_expected.not_to be_persisted }
end
it "returns true if the ID exists" do
resource.id = "test"
expect(resource).to be_persisted

context 'when new_record: false is passed to the constructor' do
subject(:resource) { Resource.new(new_record: false) }

it { is_expected.to be_persisted }
end
end

Expand Down