Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
radiospiel committed Nov 27, 2018
1 parent 0978f25 commit 09d8423
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 50 deletions.
35 changes: 35 additions & 0 deletions config/console-init.rb
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
# This file is loaded when running bin/console.

Simple::SQL.exec <<~SQL
CREATE SCHEMA IF NOT EXISTS store;
CREATE TABLE IF NOT EXISTS store.organizations (
id SERIAL PRIMARY KEY,
name VARCHAR
);
CREATE TABLE IF NOT EXISTS store.users (
id SERIAL PRIMARY KEY,
organization_id INTEGER REFERENCES store.organizations (id),
role_id INTEGER,
first_name VARCHAR,
last_name VARCHAR,
metadata JSONB,
created_at timestamp,
updated_at timestamp
);
SQL

Simple::Store.ask "DELETE FROM store.users"
Simple::Store.ask "DELETE FROM store.organizations"

Simple::Store::Metamodel.register "User", table_name: "store.users"
# do |registration|
# attribute :last_name
# end

Simple::Store::Metamodel.register "Organization", table_name: "store.organizations" do
attribute :city, writable: false
end

user = Simple::Store.create! "User", first_name: "foo"
p user
2 changes: 1 addition & 1 deletion lib/simple/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def save!(models)

models.map do |model|
if model.new_record?
Create.create_model(model)
Create.create_model(model)
else
Update.update_model(model)
end
Expand Down
22 changes: 12 additions & 10 deletions lib/simple/store/metamodel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def self.resolve(name_or_metamodel)
def attribute(name, options)
name = name.to_s

options = OPTIONS_DEFAULTS.merge(options)
current_options = @attributes[name] || OPTIONS_DEFAULTS
options = current_options.merge(options)
@attributes[name] ||= {}
@attributes[name].merge!(options)

Expand Down Expand Up @@ -102,9 +103,11 @@ def initialize(attrs, &block)

name, table_name = attrs.values_at :name, :table_name

@attributes = {}
@name = name || table_name.split(".").last.singularize
@table_name = table_name
@attributes = read_attributes_from_table

read_attributes_from_table

instance_eval(&block) if block
end
Expand All @@ -113,7 +116,7 @@ def initialize(attrs, &block)

def column_info
column_info = Simple::SQL::Reflection.column_info(table_name)
raise ArgumentError, "Invalid table name #{table_name.inspect}" if column_info.empty?
raise ArgumentError, "No such table #{table_name.inspect}" if column_info.empty?
column_info
end

Expand All @@ -134,17 +137,16 @@ def column?(name)
READONLY_ATTRIBUTES = %(created_at updated_at id type)

def read_attributes_from_table
column_info.each_with_object({}) do |(name, ostruct), hsh|
column_info.each do |name, ostruct|
next if name == "metadata"

data_type = ostruct.data_type

hsh[name] = {
type: (TYPE_BY_PG_DATA_TYPE[data_type] || data_type.to_sym),
writable: !READONLY_ATTRIBUTES.include?(name),
readable: true,
kind: :static
}
attribute name,
type: (TYPE_BY_PG_DATA_TYPE[data_type] || data_type.to_sym),
writable: !READONLY_ATTRIBUTES.include?(name),
readable: true,
kind: :static
end
end
end
7 changes: 3 additions & 4 deletions lib/simple/store/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
require "active_support/core_ext/hash/keys"

class Simple::Store::Model

def new_record?
self.id.nil?
id.nil?
end

attr_reader :metamodel
Expand Down Expand Up @@ -47,7 +46,7 @@ def initialize(metamodel, trusted_data: {})
@metamodel = metamodel
@to_hash = trusted_data.stringify_keys
@to_hash["type"] = metamodel.name
@to_hash["id"] ||= nil
@to_hash["id"] ||= nil
end

def set_id_by_trusted_caller(id)
Expand Down Expand Up @@ -121,7 +120,7 @@ def inspect
v.nil? && metamodel.attributes[k] && metamodel.attributes[k][:kind] == :dynamic
end

inspected_values = hsh.map { |k, v| "#{k}: #{v.inspect}" }#.sort
inspected_values = hsh.map { |k, v| "#{k}: #{v.inspect}" }.sort

identifier = "#{metamodel.name}##{id.inspect}"
inspected_values.empty? ? "<#{identifier}>" : "<#{identifier}: #{inspected_values.join(', ')}>"
Expand Down
36 changes: 22 additions & 14 deletions lib/simple/store/storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,18 @@ def convert_one_to_storage_representation(metamodel, model)
convert_to_storage_representation(metamodel, [model]).first
end

def load_virtual_attributes_into_hash(model, virtual_attributes, hsh)
model.metamodel.virtual_attributes.each do |k,v|
hsh[k] = model.send(v)
end

hsh
end

def convert_to_storage_representation(metamodel, models)
attributes = metamodel.attributes
virtual_attributes = metamodel.attributes(kind: :virtual)
dynamic_attributes = metamodel.attributes(kind: :virtual)

models.map do |model|
record = model.to_hash
record = model.to_hash.dup

# load virtual attributes
load_virtual_attributes_into_hash model, record
virtual_attributes.each do |k, v|
hsh[k] = model.send(v)
end

metadata = {}

Expand All @@ -43,22 +38,35 @@ def convert_to_storage_representation(metamodel, models)
# this is not a defined attribute (but maybe something internal, like "id" or "type")
true
else
expect! attribute => { kind: [:static, :dynamic, :virtual]}
expect! attribute => { kind: [:static, :dynamic, :virtual] }
true
end
end

record["metadata"] = metadata unless metadata.empty?
unless metadata.empty?
unless dynamic_attributes.empty?
raise "metadata on table without metadata column #{metadata.keys}"
end
record["metadata"] = metadata
end
record.delete "type" unless metamodel.column?("type")
record.delete "id"
record
end
end

#
Immutable = ::Simple::SQL::Helpers::Immutable

def new_from_row(hsh, fq_table_name:)
metamodel = determine_metamodel type: hsh[:type], fq_table_name: fq_table_name
Model.new(metamodel, trusted_data: hsh)
if metamodel
Model.new(metamodel, trusted_data: hsh)
elsif hsh.count == 1
hsh.values.first
else
Immutable.create(hsh)
end
end

private
Expand All @@ -67,7 +75,7 @@ def new_from_row(hsh, fq_table_name:)

def determine_metamodel(type:, fq_table_name:)
metamodels_by_table_name = Registry.grouped_by_table_name[fq_table_name]
raise "No types defined in table name #{fq_table_name.inspect}" if metamodels_by_table_name.nil?
return nil if metamodels_by_table_name.nil?

unless type
metamodels = metamodels_by_table_name.values
Expand Down
5 changes: 3 additions & 2 deletions lib/simple/store/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ module Simple::Store::Update

def update_model(model)
expect! model.new_record? => false

metamodel = model.metamodel
record = Storage.convert_one_to_storage_representation(metamodel, model)

keys = record.keys
values = record.values_at *keys

keys_w_placeholders = keys.each_with_index.map do |key, idx|
"#{key}=$#{idx+2}"
"#{key}=$#{idx + 2}"
end

Store.ask "UPDATE #{metamodel.table_name} SET #{keys_w_placeholders.join(", ")} WHERE id=$1 RETURNING *", model.id, *values
Store.ask "UPDATE #{metamodel.table_name} SET #{keys_w_placeholders.join(', ')} WHERE id=$1 RETURNING *", model.id, *values
end
end
22 changes: 11 additions & 11 deletions scripts/performance_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,17 @@ def clear
N = 1000

if false
Benchmark.bm(30) do |x|
x.report("build #{N} organizations") { clear; N.times do Simple::Store.build "Organization", organization; end }
x.report("build #{N} users") { clear; N.times do Simple::Store.build "User", user; end }
x.report("create #{N} organizations") { clear; N.times do Simple::Store.create! "Organization", organization; end }
x.report("create #{N} users") { clear; N.times do Simple::Store.create! "User", user; end }
x.report("create #{N} orgs/w transaction") { clear; Simple::SQL.transaction do N.times do Simple::Store.create! "Organization", organization; end; end }
x.report("create #{N} users/w transaction") { clear; Simple::SQL.transaction do N.times do Simple::Store.create! "User", user; end; end }
x.report("mass-create #{N} orgs") { clear; Simple::Store.create! "Organization", organizations(N) }
x.report("mass-create #{N} users") { clear; Simple::Store.create! "User", users(N) }
end

Benchmark.bm(30) do |x|
x.report("build #{N} organizations") { clear; N.times { Simple::Store.build "Organization", organization; } }
x.report("build #{N} users") { clear; N.times { Simple::Store.build "User", user; } }
x.report("create #{N} organizations") { clear; N.times { Simple::Store.create! "Organization", organization; } }
x.report("create #{N} users") { clear; N.times { Simple::Store.create! "User", user; } }
x.report("create #{N} orgs/w transaction") { clear; Simple::SQL.transaction { N.times { Simple::Store.create! "Organization", organization; }; } }
x.report("create #{N} users/w transaction") { clear; Simple::SQL.transaction { N.times { Simple::Store.create! "User", user; }; } }
x.report("mass-create #{N} orgs") { clear; Simple::Store.create! "Organization", organizations(N) }
x.report("mass-create #{N} users") { clear; Simple::Store.create! "User", users(N) }
end

end

Expand Down
6 changes: 3 additions & 3 deletions scripts/performance_test2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ class XX

N = 1_000_000
Benchmark.bm(30) do |bm|
bm.report("extend") { N.times do x = Object.new; x.extend(X); x.s; end }
bm.report("custom class") { N.times do x = XX.new; x.s; end }
end
bm.report("extend") { N.times { x = Object.new; x.extend(X); x.s; } }
bm.report("custom class") { N.times { x = XX.new; x.s; } }
end
7 changes: 6 additions & 1 deletion spec/simple/store/model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@
end

describe "#inspect" do
let(:saved_user) { Simple::Store.create! "Organization", { name: "foo" } }
let(:saved_user) { Simple::Store.create! "Organization", { name: "foo" } }
let(:return_value) { saved_user.inspect }

it "returns a string" do
expect(user.inspect).to eq('<Organization#nil>')
end

it "contains attributes" do
expect(user.inspect).to eq('<Organization#nil>')
expect(saved_user.inspect).to eq('<Organization#1: city: nil, name: "foo">')
end
end
Expand Down
11 changes: 7 additions & 4 deletions spec/simple/store/save_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,22 @@

context "with a saved model" do
let!(:saved_model) { Simple::Store.create! "Organization", name: "orgname" }
let!(:returned_model) { Simple::Store.save! saved_model }
let!(:returned_model) {
saved_model.name = "changed"
Simple::Store.save! saved_model
}

it "saves into the database" do
reloaded = Simple::Store.ask("SELECT * FROM simple_store.organizations WHERE id=4")
expected = {
id: 4, type: "Organization", name: "orgname", city: nil
id: 4, type: "Organization", name: "changed", city: nil
}
expect(reloaded).to eq(expected)
end

it "returns a saved model" do
expected = {
id: 4, type: "Organization", name: "orgname", city: nil
id: 4, type: "Organization", name: "changed", city: nil
}
expect(returned_model).to eq(expected)
end
Expand All @@ -63,7 +66,7 @@

it "does not touch other objects" do
names = SQL.all "SELECT name FROM simple_store.organizations"
expect(names).to contain_exactly("orgname1", "orgname2", "orgname3", "orgname")
expect(names).to contain_exactly("orgname1", "orgname2", "orgname3", "changed")
end
end
end

0 comments on commit 09d8423

Please sign in to comment.