Skip to content

Instantly share code, notes, and snippets.

@TGSmith
Last active December 19, 2015 14:29
Show Gist options
  • Save TGSmith/458f0e617427cdcd0f00 to your computer and use it in GitHub Desktop.
Save TGSmith/458f0e617427cdcd0f00 to your computer and use it in GitHub Desktop.
Solution for ActiveRecord, Jr. 1: A Basic ORM
class Cohort < Database::Model
# def self.all
# Database::Model.execute("SELECT * FROM cohorts").map do |row|
# Cohort.new(row)
# end
# end
def self.create(attributes)
record = self.new(attributes)
record.save
record
end
def self.where(query, *args)
Database::Model.execute("SELECT * FROM cohorts WHERE #{query}", *args).map do |row|
Cohort.new(row)
end
end
def self.find(pk)
self.where('id = ?', pk).first
end
self.attribute_names = [:id, :name, :created_at, :updated_at]
attr_reader :attributes, :old_attributes
# e.g., Cohort.new(:id => 1, :name => 'Alpha', :created_at => '2012-12-01 05:54:30')
def initialize(attributes = {})
attributes.symbolize_keys!
raise_error_if_invalid_attribute!(attributes.keys)
@attributes = {}
Cohort.attribute_names.each do |name|
@attributes[name] = attributes[name]
end
@old_attributes = @attributes.dup
end
def [](attribute)
raise_error_if_invalid_attribute!(attribute)
@attributes[attribute]
end
def []=(attribute, value)
raise_error_if_invalid_attribute!(attribute)
@attributes[attribute] = value
end
def students
Student.where('cohort_id = ?', self[:id])
end
def add_students(students)
students.each do |student|
student.cohort = self
end
students
end
def new_record?
self[:id].nil?
end
def save
if new_record?
results = insert!
else
results = update!
end
# When we save, remove changes between new and old attributes
@old_attributes = @attributes.dup
results
end
private
def insert!
self[:created_at] = DateTime.now
self[:updated_at] = DateTime.now
fields = self.attributes.keys
values = self.attributes.values
marks = Array.new(fields.length) { '?' }.join(',')
insert_sql = "INSERT INTO cohorts (#{fields.join(',')}) VALUES (#{marks})"
results = Database::Model.execute(insert_sql, *values)
# This fetches the new primary key and updates this instance
self[:id] = Database::Model.last_insert_row_id
results
end
def update!
self[:updated_at] = DateTime.now
fields = self.attributes.keys
values = self.attributes.values
update_clause = fields.map { |field| "#{field} = ?" }.join(',')
update_sql = "UPDATE cohorts SET #{update_clause} WHERE id = ?"
# We have to use the (potentially) old ID attributein case the user has re-set it.
Database::Model.execute(update_sql, *values, self.old_attributes[:id])
end
end
require 'sqlite3'
module Database
class InvalidAttributeError < StandardError;end
class NotConnectedError < StandardError;end
class Model
def self.all
Database::Model.execute("SELECT * FROM #{self.to_s.downcase}s").map do |row|
self.new(row)
end
end
def self.inherited(klass)
end
def self.connection
@connection
end
def self.filename
@filename
end
def self.database=(filename)
@filename = filename.to_s
@connection = SQLite3::Database.new(@filename)
# Return the results as a Hash of field/value pairs
# instead of an Array of values
@connection.results_as_hash = true
# Automatically translate data from database into
# reasonably appropriate Ruby objects
@connection.type_translation = true
end
def self.attribute_names
@attribute_names
end
def self.attribute_names=(attribute_names)
@attribute_names = attribute_names
end
# Input looks like, e.g.,
# execute("SELECT * FROM students WHERE id = ?", 1)
# Returns an Array of Hashes (key/value pairs)
def self.execute(query, *args)
raise NotConnectedError, "You are not connected to a database." unless connected?
prepared_args = args.map { |arg| prepare_value(arg) }
Database::Model.connection.execute(query, *prepared_args)
end
def self.last_insert_row_id
Database::Model.connection.last_insert_row_id
end
def self.connected?
!self.connection.nil?
end
def raise_error_if_invalid_attribute!(attributes)
# This guarantees that attributes is an array, so we can call both:
# raise_error_if_invalid_attribute!("id")
# and
# raise_error_if_invalid_attribute!(["id", "name"])
Array(attributes).each do |attribute|
unless valid_attribute?(attribute)
raise InvalidAttributeError, "Invalid attribute for #{self.class}: #{attribute}"
end
end
end
def to_s
attribute_str = self.attributes.map { |key, val| "#{key}: #{val.inspect}" }.join(', ')
"#<#{self.class} #{attribute_str}>"
end
def valid_attribute?(attribute)
self.class.attribute_names.include? attribute
end
private
def self.prepare_value(value)
case value
when Time, DateTime, Date
value.to_s
else
value
end
end
end
end
# Solution for Challenge: ActiveRecord, Jr. 1: A Basic ORM. Started 2013-07-10T19:32:15+00:00
class Student < Database::Model
# def self.all
# Database::Model.execute("SELECT * FROM students").map do |row|
# Student.new(row)
# end
# end
def self.create(attributes)
record = self.new(attributes)
record.save
record
end
def self.where(query, *args)
Database::Model.execute("SELECT * FROM students WHERE #{query}", *args).map do |row|
Student.new(row)
end
end
def self.find(pk)
self.where('id = ?', pk).first
end
self.attribute_names = [:id, :cohort_id, :first_name, :last_name, :email,
:gender, :birthdate, :created_at, :updated_at]
attr_reader :attributes, :old_attributes
# e.g., Student.new(:id => 1, :first_name => 'Steve', :last_name => 'Rogers', ...)
def initialize(attributes = {})
attributes.symbolize_keys!
raise_error_if_invalid_attribute!(attributes.keys)
# This defines the value even if it's not present in attributes
@attributes = {}
Student.attribute_names.each do |name|
@attributes[name] = attributes[name]
end
@old_attributes = @attributes.dup
end
def save
if new_record?
results = insert!
else
results = update!
end
# When we save, remove changes between new and old attributes
@old_attributes = @attributes.dup
results
end
# We say a record is "new" if it doesn't have a defined primary key in its
# attributes
def new_record?
self[:id].nil?
end
# e.g., student['first_name'] #=> 'Steve'
def [](attribute)
raise_error_if_invalid_attribute!(attribute)
@attributes[attribute]
end
# e.g., student['first_name'] = 'Steve'
def []=(attribute, value)
raise_error_if_invalid_attribute!(attribute)
@attributes[attribute] = value
end
def cohort
Cohort.where('id = ?', self[:cohort_id]).first
end
def cohort=(cohort)
self[:cohort_id] = cohort[:id]
self.save
cohort
end
private
def insert!
self[:created_at] = DateTime.now
self[:updated_at] = DateTime.now
fields = self.attributes.keys
values = self.attributes.values
marks = Array.new(fields.length) { '?' }.join(',')
insert_sql = "INSERT INTO students (#{fields.join(',')}) VALUES (#{marks})"
results = Database::Model.execute(insert_sql, *values)
# This fetches the new primary key and updates this instance
self[:id] = Database::Model.last_insert_row_id
results
end
def update!
self[:updated_at] = DateTime.now
fields = self.attributes.keys
values = self.attributes.values
update_clause = fields.map { |field| "#{field} = ?" }.join(',')
update_sql = "UPDATE students SET #{update_clause} WHERE id = ?"
# We have to use the (potentially) old ID attribute in case the user has re-set it.
Database::Model.execute(update_sql, *values, self.old_attributes[:id])
end
end
require_relative 'app'
require 'faker'
require 'colorize'
# Student Tests
# test Student#initialize
# should see object with values, but no id or timestamps
cohort_ids = [1,2,3]
puts "Creating new student".red
new_student = Student.new(:first_name => Faker::Name.first_name,
:last_name => Faker::Name.last_name,
:email => Faker::Internet.email,
:birthdate => Date.today - rand(15..40)*365,
:gender => ['m', 'f'].sample,
:cohort_id => cohort_ids.sample
)
puts "Inspecting new student".red
p new_student
puts "Count of all students".red
# p Student.all.count # should be some number
# Student#save
# should see same object with primary key id and timestamps
puts "Saving new student".red
p new_student.save
puts "Count of all students (should be +1)".red
p Student.all.count # should be some number + 1
# Student#[]
# [] for name should see same name as inspects above
puts "checking new student's name".red
p new_student.[](:first_name)
# Student#[]=
puts "Changing students name to Tristan".red
new_student.[]=(:first_name, "Tristan")
puts "Checking name is Tristan".red
p new_student.[](:first_name) # should see Tristan
# Cohort#initialize
puts "Number of Cohorts in the DB".red
p Cohort.all
puts "Creating new Cohort 'Super GrassHoppers Group'".red
p new_cohort = Cohort.new(:name => "Super GrassHoppers Group")
# Cohort#save
puts "Saving the new cohort".red
p new_cohort.save
puts "Checking number of Cohorts in DB (should be +1)".red
p Cohort.all.count
# Cohort#[]=
puts "Check new cohort name".red
p new_cohort.[](:name)
puts "Change cohort name to 'WazUP Group'".red
new_cohort.[]=(:name, "WazUP Group")
puts "Check that the name is 'WazUp Group".red
p new_cohort.[](:name)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment