如何制作通用的 SQL heredocs?
How to make generic SQL heredocs?
我正在 Ruby 中制作模拟 ORM,存储 'forum' 数据。
我有以下 tables:
users (id, fname, lname)
和 questions (id, title, body, author_id)
.
a_user.create
和a_question.create
大致相同:
# aUser#create
def create
# adds a user to the database
raise "user already in db" if self.id
begin
QuestionsDB.instance.execute(<<-SQL, self.fname, self.lname)
insert into users (fname, lname)
values (?, ?);
SQL
self.id = QuestionsDB.instance.last_insert_row_id
return self.id
rescue => exception
raise "failed to create user: #{exception}"
end
end
# aQuestion#create
def create
# add question to db
raise "already asked" if self.id
raise "title already exists" unless Question.find_by_title(self.title).empty?
begin
QuestionsDB.instance.execute(<<-SQL, self.title, self.body, self.author_id)
insert into questions (title, body, author_id)
values (?, ?, ?);
SQL
self.id = QuestionsDB.instance.last_insert_row_id
return self.id
rescue => exception
raise "failed to create question: #{exception}"
end
end
我正在写另一个 class,ModelBase,我希望能够说出类似
的内容
ModelBase.create(aUser)
或
aMB = ModelBase.new(~characteristics_from_user_or_question_object~)
aMB.create
。用户和问题在添加到数据库之前没有 ID。
两个创建的 SQL 基本上采用所有预创建属性并将它们放入相应 table 的所有列中,不包括第一个 'id' 列。有没有一种方法可以概括指令“将此对象的所有属性插入指定的 table 值 ('all but the first')”?
我们可以通过一些 Ruby 元编程实现非常基本的 ORM,但请记住,出于安全原因,您不应该在生产中使用它。实现一个完全可用的 ORM 需要大量工作。
我们可以用instance_variables
.
得到一个class的实例变量
class User
attr_reader :id, :first_name, :last_name
def initialize(first_name:, last_name:)
@first_name = first_name
@last_name = last_name
end
end
puts User.new(first_name: "Elvis", last_name: "Presley").instance_variables
# @first_name @last_name
请注意开头的 @
,我们可以使用简单的 gsub
将其删除。我们还需要拒绝 @id
属性。最后使用 instance_variable_get
我们可以获得实际值。
根据这些信息,我们现在可以实施 columns
和 values
方法。
class OrmBase
def columns
instance_variables_without_id.map do |var_name|
var_name.to_s.gsub /^@/, ''
end
end
def values
instance_variables_without_id.map do |var_name|
instance_variable_get var_name
end
end
def instance_variables_without_id
instance_variables.reject { |var_name| var_name == "@id" }
end
end
现在我们需要计算 table 名称,我们可以从具有 self.class.name
的对象的 class 名称派生该名称。如果我们把所有东西放在一起,它看起来像这样:
class OrmBase
def create
return if @id
QuestionsDB.instance.execute(to_sql)
@id = QuestionsDB.instance.last_insert_row_id
self
end
def to_sql
"INSERT INTO #{table_name} (#{columns.join(', ')}) values (#{values.join(', ')});"
end
private
def table_name
"#{self.class.name}s".downcase
end
def columns
instance_variables_without_id.map do |var_name|
var_name.to_s.gsub(/^@/, '')
end
end
def values
instance_variables_without_id.map do |var_name|
instance_variable_get(var_name)
end
end
def instance_variables_without_id
instance_variables.reject { |var_name| var_name == "@id" }
end
end
class User < OrmBase
attr_reader :id, :first_name, :last_name
def initialize(first_name:, last_name:)
@first_name = first_name
@last_name = last_name
end
end
class Question < OrmBase
attr_reader :id, :title
def initialize(title:)
@title = title
end
end
puts User.new(first_name: "Elvis", last_name: "Presley").to_sql
# INSERT INTO users (first_name, last_name) values (Elvis, Presley);
puts Question.new(title: "What is your favorite song?").to_sql
# INSERT INTO questions (title) values (What is your favorite song?);
如开头所述,请只将其用作练习的一部分,因为它不安全(SQL 注入),有错误(例如 table 名称的复数化和转义值)等
另一种实现方法是首先查询数据库 table 具有哪些列名称,然后从中派生 class 属性。例如 ActiveRecord 就是这样做的。
我正在 Ruby 中制作模拟 ORM,存储 'forum' 数据。
我有以下 tables:
users (id, fname, lname)
和 questions (id, title, body, author_id)
.
a_user.create
和a_question.create
大致相同:
# aUser#create
def create
# adds a user to the database
raise "user already in db" if self.id
begin
QuestionsDB.instance.execute(<<-SQL, self.fname, self.lname)
insert into users (fname, lname)
values (?, ?);
SQL
self.id = QuestionsDB.instance.last_insert_row_id
return self.id
rescue => exception
raise "failed to create user: #{exception}"
end
end
# aQuestion#create
def create
# add question to db
raise "already asked" if self.id
raise "title already exists" unless Question.find_by_title(self.title).empty?
begin
QuestionsDB.instance.execute(<<-SQL, self.title, self.body, self.author_id)
insert into questions (title, body, author_id)
values (?, ?, ?);
SQL
self.id = QuestionsDB.instance.last_insert_row_id
return self.id
rescue => exception
raise "failed to create question: #{exception}"
end
end
我正在写另一个 class,ModelBase,我希望能够说出类似
的内容ModelBase.create(aUser)
或
aMB = ModelBase.new(~characteristics_from_user_or_question_object~)
aMB.create
。用户和问题在添加到数据库之前没有 ID。
两个创建的 SQL 基本上采用所有预创建属性并将它们放入相应 table 的所有列中,不包括第一个 'id' 列。有没有一种方法可以概括指令“将此对象的所有属性插入指定的 table 值 ('all but the first')”?
我们可以通过一些 Ruby 元编程实现非常基本的 ORM,但请记住,出于安全原因,您不应该在生产中使用它。实现一个完全可用的 ORM 需要大量工作。
我们可以用instance_variables
.
class User
attr_reader :id, :first_name, :last_name
def initialize(first_name:, last_name:)
@first_name = first_name
@last_name = last_name
end
end
puts User.new(first_name: "Elvis", last_name: "Presley").instance_variables
# @first_name @last_name
请注意开头的 @
,我们可以使用简单的 gsub
将其删除。我们还需要拒绝 @id
属性。最后使用 instance_variable_get
我们可以获得实际值。
根据这些信息,我们现在可以实施 columns
和 values
方法。
class OrmBase
def columns
instance_variables_without_id.map do |var_name|
var_name.to_s.gsub /^@/, ''
end
end
def values
instance_variables_without_id.map do |var_name|
instance_variable_get var_name
end
end
def instance_variables_without_id
instance_variables.reject { |var_name| var_name == "@id" }
end
end
现在我们需要计算 table 名称,我们可以从具有 self.class.name
的对象的 class 名称派生该名称。如果我们把所有东西放在一起,它看起来像这样:
class OrmBase
def create
return if @id
QuestionsDB.instance.execute(to_sql)
@id = QuestionsDB.instance.last_insert_row_id
self
end
def to_sql
"INSERT INTO #{table_name} (#{columns.join(', ')}) values (#{values.join(', ')});"
end
private
def table_name
"#{self.class.name}s".downcase
end
def columns
instance_variables_without_id.map do |var_name|
var_name.to_s.gsub(/^@/, '')
end
end
def values
instance_variables_without_id.map do |var_name|
instance_variable_get(var_name)
end
end
def instance_variables_without_id
instance_variables.reject { |var_name| var_name == "@id" }
end
end
class User < OrmBase
attr_reader :id, :first_name, :last_name
def initialize(first_name:, last_name:)
@first_name = first_name
@last_name = last_name
end
end
class Question < OrmBase
attr_reader :id, :title
def initialize(title:)
@title = title
end
end
puts User.new(first_name: "Elvis", last_name: "Presley").to_sql
# INSERT INTO users (first_name, last_name) values (Elvis, Presley);
puts Question.new(title: "What is your favorite song?").to_sql
# INSERT INTO questions (title) values (What is your favorite song?);
如开头所述,请只将其用作练习的一部分,因为它不安全(SQL 注入),有错误(例如 table 名称的复数化和转义值)等
另一种实现方法是首先查询数据库 table 具有哪些列名称,然后从中派生 class 属性。例如 ActiveRecord 就是这样做的。