Last active
December 19, 2015 14:29
-
-
Save TGSmith/458f0e617427cdcd0f00 to your computer and use it in GitHub Desktop.
Solution for ActiveRecord, Jr. 1: A Basic ORM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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