To store data, inside of Sketchup, or elsewhere, a few steps have to be taken. First, you should be aware of the difference between your business objects that have all the functionality and references to other objects, and their purest state representation, which is what you want to make persistent. Now, the example I am going to write here is far from complete, as it lacks business objects, but it will give you an example of how to make data persistent in sketchup. In this case, by storing ruby Hash objects.
First, a generic repository is created to handle basic CRUD operations against a store. In this case, the store must be a key-value-store. Since it is generic, all dependencies should be injected. The repository should not be aware that it is running inside of sketchup, it should not be aware what kind of key-value-store it uses (AttributeDictionary, or a regular ruby Hash, or…), it should not be aware which key is used as an ID and it should not be aware of how it is serialized exactly.
module Developer
module Product
# this class should not be aware that it is running inside of SketchUp
# inject dependencies
class GenericHashRepositoryKeyValueStore
def initialize(id_key, key_value_store, key_value_store_id_provider, hash_serializer)
# TODO: check if passed dependencies implement the needed methods
@id_key = id_key
@key_value_store = key_value_store
@key_value_store_id_provider = key_value_store_id_provider
@hash_serializer = hash_serializer
end #def
def add(hash)
# get a new id for thekey value store we have here
id = @key_value_store_id_provider.next_id(@key_value_store)
hash[@id_key] = id
# serialize it
serialized = @hash_serializer.serialize(hash)
# and store it
@key_value_store[id] = serialized
# and finally, return it
return hash
end #def
def get(id)
# get the entry at the given key
serialized = @key_value_store[id]
return false if not serialized
# deserialize it
hash = @hash_serializer.deserialize(serialized)
return hash
end #def
def all()
hashes = []
@key_value_store.values do |serialized|
hash = @hash_serializer.deserialize(serialized)
hashes << hash
end #def
return hashes
end #def
def update(hash)
# get the id
id = hash[@id_key]
# check if the given id exists, if not return false
return false if not @key_value_store[id]
# serialize
serialized = @hash_serializer.serialize(hash)
# store
@key_value_store[id] = serialized
return true
end #def
def delete(id)
# check if the given id exists, if not return false
return false if not @key_value_store[id]
# delete the entry
value = @key_value_store.delete(id)
return value ? true : false
end #def
end #class
end #module
end #module
Because dependencies are injected, different implementations can be interchanged. for example, the serializer could be a JSON serializer, or YAML, or XML, or binary, or whatever you like… I have created 2 small implementations:
require 'json'
module Developer
module Product
class HashSerializerJson
def serialize(hash)
return JSON.dump(hash)
end #def
def deserialize(json)
return JSON.parse(json)
end #def
end #class
end #module
end #module
and
require 'yaml'
module Developer
module Product
class HashSerializerYaml
def serialize(hash)
return YAML::dump(hash)
end #def
def deserialize(yaml)
return YAML::load(yaml)
end #def
end #class
end #module
end #module
Because I don’t know if I want the ID’s to be numbers, or strings, or guids, or colors, or… I have made the id provider also an injected dependecy. For now I only have created the implementation that gives a new number as an id. The id provider also has to store its state in the key-value-store, for that reason, the key where the state is stored is also injected.
module Developer
module Product
class KeyValueStoreIdProviderIncrementalNumber
def initialize(key)
@key = key
end #def
def next_id(key_value_store)
# get the last id
last_id = key_value_store[@key] || 0
# get next
next_id = last_id + 1
# store it
key_value_store[@key] = next_id
# return nex id
return next_id
end #def
end #class
end #module
end #module
Finally, the key-value-store itself is free to select. Only want to make a repository in-memory? Fine, pass a regular Hash to it and it will work. Want to make it persistent inside an AttributeDictionary? Fine, you can pass an AttributeDictionary as well, except, it lacks a delete
method which a regular Hash has. For that reason, I made a wrapper:
require 'sketchup'
module Developer
module Product
class KeyValueStoreSketchupAttributeDictionaryBased
def initialize(attribute_dictionary)
# TODO: check if passed dependencies implement the needed methods, or, in this case, I'd check if it truely is an AttributeDictionary
@attribute_dictionary = attribute_dictionary
end #def
def delete(key)
return @attribute_dictionary.delete_key(key)
end #def
def method_missing(method, *args)
if @attribute_dictionary.respond_to?(method)
@attribute_dictionary.send(method, *args)
else
super
end
end
end #class
end #module
end #module
With all these classes, not aware of what their dependencies are, one can create multiple, slightly different implementations. For example:
# create a repository that:
# - stores hashes in a Hash
# - serializes them to json
# - uses a Number as a key
# - uses "id" to store the id
# create dependencies first
serializer = Developer::Product::HashSerializerJson.new()
id_provider_key = "LAST_ID"
id_provider = Developer::Product::KeyValueStoreIdProviderIncrementalNumber.new(id_provider_key)
store = {} # simple hash for now
id_key = "id"
# now the repository
repository = Developer::Product::GenericHashRepositoryKeyValueStore.new(id_key, store, id_provider, serializer)
Another implementation:
# create a repository that:
# - stores hashes in an AttributeDictionary
# - serializes them to yaml
# - uses a Number as a key
# - uses "the-cool-id" to store the id
# create dependencies first
serializer = Developer::Product::HashSerializerYaml.new()
id_provider_key = "LAST_ID"
id_provider = Developer::Product::KeyValueStoreIdProviderIncrementalNumber.new(id_provider_key)
attribute_dictionary = Sketchup.active_model.attribute_dictionary('name', true)
store = Developer::Product::KeyValueStoreSketchupAttributeDictionaryBased.new(attribute_dictionary)
id_key = "the-cool-id"
# now the repository
repository = Developer::Product::GenericHashRepositoryKeyValueStore.new(id_key, store, id_provider, serializer)
Regardles of the implementations, both example repositories` input and output should be the same (except for the id key). to use the repository:
# add a new entry
entry= repository.add({"name" => "Kenny", "country" => "Belgium"})
# get an existing entry
entry = repository.get(id)
# update an entry
entry["country"] = "Scotland"
repository.update(entry)
# delete an entry
repository.delete(id)
This code is partially written during the creation of this post. I’m sure there will be typos. But it should give you an idea of how to make data persistent and at the same time an idea of dependecy injection.
The next step should be to create Controllers, that create business objects based on one or mulitple Hash repositories. But, that is for a next time I have some spare time…
In the meantime, this code is also on github: https://github.com/kengey/sketchup-dev-tools-and-examples