• Register
31,120 points
10 6 4

Solution:

We can execute  a very basic ORM with some Ruby meta programming however please keep in mind that this is nothing you must use in production for security purposes. Executing a fully performing ORM is A LOT of work.

We can obtain the instance variables of a class with 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

 Note that the @ in the starting which we can remove with a easy gsub. We also require to reject the @id attribute. Lastly with instance_variable_get we can obtain the actual values.

With this information we can now execute a columns and values method.

Code:

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

Now we require to compute the table name which we can get from the class name of the object with self.class.name. If we set everything together it would look like this:

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?);

Point to be noted: as mentioned in the starting, please only execute this as part of an exercise as it is not safe (SQL injections), has bugs (for example pluralisation of the table name and escaping of values) etc.

Another method how this could get executed is to query the database first which column names the table has and then get the class attributes from it. ActiveRecord for instance does it this system.

31,120 points
10 6 4