peel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3cc4a3fcd5bdf85bd8a05fff0bcb3e2f60b15729
4
+ data.tar.gz: 047c72e43c0f25a1d75f0bdba97ba4b824f14aef
5
+ SHA512:
6
+ metadata.gz: 321f4a382242123c38982978c9630f421851362c906bb2fc47141e889c7aeb5e3635166fb9adce48dd2d315f3a88711a7a8ff9003ebbe33ddcdc4ef32464be02
7
+ data.tar.gz: 789903c08e883c70dd5d8e419aca20b8e736233f0bc9134160a85a9c40702c40400851e40d2c0644e562cc85844b10597eaa03e01667318b23579a65f09777bd
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ # IDE
14
+ .vscode
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.3.3
7
+ before_install: gem install bundler -v 1.16.6
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at thomascountz@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in peel.gemspec
6
+ gemspec
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ peel (0.1.0)
5
+ sqlite3
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.3)
11
+ rake (10.4.2)
12
+ rspec (3.7.0)
13
+ rspec-core (~> 3.7.0)
14
+ rspec-expectations (~> 3.7.0)
15
+ rspec-mocks (~> 3.7.0)
16
+ rspec-core (3.7.1)
17
+ rspec-support (~> 3.7.0)
18
+ rspec-expectations (3.7.0)
19
+ diff-lcs (>= 1.2.0, < 2.0)
20
+ rspec-support (~> 3.7.0)
21
+ rspec-mocks (3.7.0)
22
+ diff-lcs (>= 1.2.0, < 2.0)
23
+ rspec-support (~> 3.7.0)
24
+ rspec-support (3.7.1)
25
+ sqlite3 (1.3.13)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ bundler (~> 1.16)
32
+ peel!
33
+ rake (~> 10.0)
34
+ rspec (~> 3.0)
35
+
36
+ BUNDLED WITH
37
+ 1.16.6
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Thomas Countz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,74 @@
1
+ # Peel
2
+
3
+ Non-production Ruby implementation of the Active Record Pattern, inspired by Gregory Brown's Broken Record project.
4
+
5
+ ## Development Journal
6
+
7
+ A contemporaneous journal of the development of Peel can be found under [/dev_journal](dev_journal)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'peel'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install peel
24
+
25
+ ## Usage
26
+
27
+ Configure Peel to use an _existing_ SQLite databse file, by calling `Peel.configure()` and passing in a block:
28
+
29
+ ```ruby
30
+ # config.rb
31
+ require 'peel'
32
+ Peel.configure do |config|
33
+ config.database_file = "books_app"
34
+ end
35
+ ```
36
+
37
+ Include `Peel::Modelable` in your class and call `peel_off`, passing in the table name as a symbol or string:
38
+
39
+ ```ruby
40
+ # book.rb
41
+ require 'config'
42
+ class Book
43
+ include Peel::Modelable
44
+ peel_off(:books)
45
+ end
46
+ ```
47
+
48
+ Call `.find()` on your model. A new instance will be returned with getters/setters based on the row set:
49
+
50
+ ```ruby
51
+ book = Book.find(1)
52
+ #=> #<Book:0x007f803ea9e938 @author="Metz, Sandi", @id=1, @isbn="0311237841549", @title="Practical Object-Oriented Design in Ruby">
53
+
54
+ book.title
55
+ #=> "Practical Object-Oriented Design in Ruby"
56
+ ```
57
+
58
+ ## Development
59
+
60
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
61
+
62
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
63
+
64
+ ## Contributing
65
+
66
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/peel. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
67
+
68
+ ## License
69
+
70
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
71
+
72
+ ## Code of Conduct
73
+
74
+ Everyone interacting in the Peel project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/peel/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "peel"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,109 @@
1
+ # Day 0
2
+
3
+ ## The Project
4
+
5
+ As one of the final projects during my apprenticeship at 8th Light, I've been tasked with implementing a feature-light ORM in Ruby, modeled after the Active Record _pattern_ and inspired by Gregory Brown's [Broken Record project](https://practicingruby.com/articles/implementing-the-active-record-pattern-1).
6
+
7
+ "Pattern?" Up until this point, I knew about the `ActiveRecord` ORM that ships with Ruby on Rails, but I wasn't aware that this was an implementation of the _active record pattern_...
8
+
9
+ ## Init Research
10
+
11
+ In _Patterns of Enterprise Application Architecture_, Martin Fowler introduces the Active Record pattern as:
12
+
13
+ > _...an object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data..._
14
+
15
+ This sounds like how one might describe Ruby on Rails' `ActiveRecord`, and that's no mistake. In the Ruby on Rails guides' [_Active Record Basics_](https://guides.rubyonrails.org/active_record_basics.html#the-active-record-pattern), they describe it like so:
16
+
17
+ > It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system... In Active Record, objects carry both persistent data and behavior which operates on that data.
18
+
19
+ Let's say I have a database table called `users`, which has three columns, `id`, `firstname`, and `lastname`. I could represent this table, and some data in it, like this:
20
+
21
+ ```
22
+ | id | firstname | lastname |
23
+ |----|-----------|----------|
24
+ | 0 | Simon | Parker |
25
+ | 1 | Mary | Souza |
26
+ | 2 | Tristen | Lu-Chen |
27
+ ```
28
+
29
+ If I want to get the data for `Mary Souza`, I might write an SQL query like this:
30
+
31
+ ```sql
32
+ SELECT * FROM users WHERE id = 1;
33
+ ```
34
+
35
+ And I could expect to get some data back like this:
36
+
37
+ ```
38
+ | id | firstname | lastname |
39
+ |----|-----------|----------|
40
+ | 1 | Mary | Souza |
41
+ ```
42
+
43
+ However, from my _application_, I might instead want to write something like this:
44
+
45
+ ```
46
+ user = User.select_where(id: 1)
47
+ ```
48
+
49
+ And get back and object like this:
50
+
51
+ ```
52
+ #{User "id": 1, "firstname": "Mary", "lastname":"Souza"}
53
+ ```
54
+
55
+ And supply behavior to that data, such as with a method called `fullname`:
56
+
57
+ ```
58
+ user.fullname
59
+ #=> "Mary Souza"
60
+ ```
61
+
62
+ This object is an Active Record object!
63
+
64
+ It 1) wraps data from a row in the database, 2) encapsulates the retrieval of that data with the `.select_where` method, and 3) adds domain-specific behavior, with the `.fullname` method.
65
+
66
+ Martin Fowler extends this descrption with the following:
67
+
68
+ >The Active Record class typically has methods that do the following:
69
+ >
70
+ >- Construct an instance of the Active Record from a SQL result set row
71
+ >- Construct a new instance for later insertion into the table
72
+ >- Static finder methods to wrap commonly used SQL queries and return Active Record objects
73
+ >- Update the database and insert into it the data in the Active Record
74
+ >- Get and set the fields
75
+ >- Implement some pieces of business logic
76
+
77
+ ___
78
+
79
+ This sounds pretty good, and more often than not, if you've used Ruby on Rails, you are familiar with the core feature-set of the active record implementation. However, throughout the apprenticeship, I've learned to always take a moment ask if this is the most cost-effective solution.
80
+
81
+ **Side Note**: What defines "cost" will always vary widely from project to project, feature to feature. Some general things to keep in mind are how easy a solution is to implement/maintain, how much time will the implementation take, does it directly solve our problem, is the expected return greater than the cost of delivery, and does it matter? The definition of cost must be defined for every feature, so for our purposes, I'll define cost as "does it directly solve our problem," and with that, we must first identify the problem!
82
+
83
+ For this project, the problem is, "Thomas needs a fun and exciting project to work on, he could benefit with some more time working with databases, and he loves working with Ruby, what should he build?" But, pushing that meta-problem aside, what problem does the active record pattern solve/when would we generally aim to implement it?
84
+
85
+ > Active Record is a good choice for domain logic that isn’t too complex, such as creates, reads, updates, and deletes. Derivations and validations based on a single record work well in this structure... Their primary problem is that they work well only if the Active Record objects correspond directly to the database tables... Another argument against Active Record is the fact that it couples the object design to the database design.
86
+ >
87
+ > Martin Fowler, _Patterns of Enterprise Application Architecture_
88
+
89
+ ## What's The Problem
90
+
91
+ Let's invent a little MVP scenario to get shake things loose and get us thinking creatively.
92
+
93
+ > We want to build a simple CRUD app to manage a global list of books in our library. We should be able to record a book's title, author, and ISBN number.
94
+
95
+ The Active Record pattern solves our scenario. Let's also say that we've weighed all of the costs and the decision to implement Active Record has gotten the green light!
96
+
97
+ Where do we go from here?
98
+
99
+ ## Initial Thoughts on Implementing Active Record
100
+
101
+ Without doing any more research at this point, I'll begin by reiterating what I understand as the three major features that need to be implemented in order to deliver an Active Record object:
102
+
103
+ 1. Abstract database connections/queries
104
+ 2. Encapsulate data into an "Active Record" object
105
+ 3. Provide an interface to add domain-specific behavior
106
+
107
+ In the example presented in _Patterns of Enterprise Application Architecture_, Martin Fowler's houses all of this within a single object, and I think I'll begin with that approach.
108
+
109
+ If we can implement a single Active Record object that connects to the database, wraps data, and provides an interface to that data, then maybe we can iterate until we end up with a reusable abstracted interfaces for composing new domain-specific Active Record objects.
@@ -0,0 +1,181 @@
1
+ # Day 1
2
+
3
+ ## Where to Begin
4
+
5
+ First, let's quickly revisit our problem domain:
6
+
7
+ > We want to build a simple CRUD app to manage a global list of books in our library. We should be able to record a book's title, author, and ISBN number.
8
+
9
+ Now, let's start with some initial, sometimes arbitrary, design decisions so that I can zero-in on the meat of the problem. First, like Gregory Brown's [Broken Record project](https://practicingruby.com/articles/implementing-the-active-record-pattern-1), I think I'll begin with an SQLite database. SQLite is the default Ruby on Rails development database, and its light footprint means I should be able to get up and running quickly. We'll utilize the `SQLite3` gem, which you can read up on [here](https://github.com/sparklemotion/sqlite3-ruby/).
10
+
11
+ I'm also going to assume that the database table already exists. Later in the project, I hope to take a look at managing schema migrations, but for now, the creation and alteration of database tables/columns will happen outside of the active record object. This is a good example of how the `ActiveRecord` implementation from Ruby on Rails goes above and beyond the basic definition of the Active Record pattern.
12
+
13
+ The database is build like this:
14
+
15
+ ```sqlite
16
+ CREATE TABLE IF NOT EXISTS books (
17
+ id INTEGER PRIMARY KEY,
18
+ title VARCHAR(255),
19
+ author VARCHAR(50),
20
+ isbn VARCHAR(13)
21
+ );
22
+ ```
23
+
24
+ The line `CREATE TABLE IF NOT EXISTS` does exactly what it sounds like, it will either create the table based on the schema provided, or, if a `books` table exits, it will do nothing.
25
+
26
+ This is important to note because a change to this SQL statement will not _update_ the `books` table. For example, if we wanted to add another column `pages`, to record the number of pages in our books, we couldn't simply add another line to our statement and run it again.
27
+
28
+ ```sqlite
29
+ /* This new statement will not update an existing table */
30
+ CREATE TABLE IF NOT EXISTS books (
31
+ id INTEGER PRIMARY KEY,
32
+ title VARCHAR(255),
33
+ author VARCHAR(50),
34
+ isbn VARCHAR(13),
35
+ pages INTEGER
36
+ );
37
+ ```
38
+
39
+ The next few lines create the columns of our table. `id`, `title`, `author`, and `isbn`, each hold the the data belonging to each book. The interesting one to note is `id`, which is declared as `INTEGER PRIMARY KEY`
40
+
41
+ > If you declare a column of a table to be [INTEGER PRIMARY KEY](https://www.sqlite.org/lang_createtable.html#rowid), then whenever you insert a `NULL` into that column of the table, the `NULL` is automatically converted into an integer which is one greater than the largest value of that column over all other rows in the table, or `1` if the table is empty.
42
+ >
43
+ > -[SQLite FAQ](https://www.sqlite.org/faq.html#q1)
44
+
45
+ ## SQLite3 Gem
46
+
47
+ In order to build an Active Record object, we need a database, and in order to use a database, we need an adapter. Fortunately for us, it is not the responsibility of an Active Record object to facility the infrastructure of communication between itself and the database. For our purposes, we'll begin with the [SQLite3 gem](https://github.com/sparklemotion/sqlite3-ruby) that abstracts the database away from our object.
48
+
49
+ Later we'll see how we can wrap this gem in an object we own, so that we can move towards becoming database agnostic.
50
+
51
+ ## Spiking an Active Record Object
52
+
53
+ ```ruby
54
+ require 'sqlite3'
55
+
56
+ db = SQLite3::Database.new("books.db").execute <<-SQL
57
+ CREATE TABLE IF NOT EXISTS books (
58
+ id INTEGER PRIMARY KEY,
59
+ title VARCHAR(255),
60
+ author VARCHAR(50),
61
+ isbn VARCHAR(13)
62
+ );
63
+ SQL
64
+
65
+ class Book
66
+ class << self
67
+ def create(title:, author:, isbn:)
68
+ db = SQLite3::Database.new("books.db")
69
+ db.execute(
70
+ "INSERT INTO books (title, author, isbn) VALUES (?, ?, ?)",
71
+ [title, author, isbn]
72
+ )
73
+ id = db.last_insert_row_id
74
+ Book.new(id: id, title: title, author: author, isbn: isbn)
75
+ end
76
+
77
+ def find(id)
78
+ result = SQLite3::Database.new("books.db").execute(
79
+ "SELECT * FROM books WHERE id = ? LIMIT 1", id
80
+ )
81
+ result.empty? ? result : Book.new(id: result[0][0],
82
+ title: result[0][1],
83
+ author: result[0][2],
84
+ isbn: result[0][3]
85
+ )
86
+ end
87
+ end
88
+
89
+ def initialize(id: nil, title:, author:, isbn:)
90
+ @id = id
91
+ @title = title
92
+ @author = author
93
+ @isbn = isbn
94
+ end
95
+
96
+ def update(title: nil, author: nil, isbn: nil)
97
+ title = title || @title
98
+ author = author || @author
99
+ isbn = isbn || @isbn
100
+
101
+ SQLite3::Database.new("books.db").execute <<-SQL
102
+ UPDATE books
103
+ SET title = '#{title}', author = '#{author}', isbn = '#{isbn}'
104
+ WHERE id = #{@id};
105
+ SQL
106
+ Book.new(id: @id, title: title, author: author, isbn: isbn)
107
+ end
108
+
109
+ def destroy
110
+ SQLite3::Database.new("books.db").execute("DELETE FROM books WHERE id = ?", @id)
111
+ end
112
+ end
113
+ ```
114
+
115
+ ## Example Usage ##
116
+
117
+ ```ruby
118
+ Book.create(title: "Practical Object Oriented Design in Ruby", author: "Metz, Sandi", isbn: "9780321721334")
119
+ # => #<Book:0x007fa904193e80 @author="Metz, Sandi", @id=1, @isbn="9780321721334", @title="Practical Object Oriented Design in Ruby">
120
+
121
+ Book.create(title: "Patterns of Enterprise Apllication Architecture", author: "Fowler, Martin", isbn: "9780321127426")
122
+ # => #<Book:0x007fa9039290b0 @author="Fowler, Martin", @id=2, @isbn="9780321127426", @title="Patterns of Enterprise Apllication Architecture">
123
+
124
+ book = Book.find(1)
125
+ # => #<Book:0x007fa903965e98 @author="Metz, Sandi", @id=1, @isbn="9780321721334", @title="Practical Object Oriented Design in Ruby">
126
+
127
+ book.title
128
+ # => "Practical Object Oriented Design in Ruby"
129
+
130
+ new_book = book.update(title: "POODR")
131
+ # => #<Book:0x007fa90291f638 @author="Metz, Sandi", @id=1, @isbn="9780321721334", @title="POODR">
132
+
133
+ Book.find(2)
134
+ # => <Book:0x007fa9028a72c8 @author="Fowler, Martin", @id=2, @isbn="9780321127426", @title="Patterns of Enterprise Apllication Architecture">
135
+
136
+ Book.find(2).destroy
137
+ # => []
138
+
139
+ Book.find(2)
140
+ # => []
141
+ ```
142
+
143
+ This is an Active Record object. It's not pretty, but it illustrates the core features of the pattern.
144
+
145
+ From the client's perspective, they're only dealing with a POOR, _plain old Ruby object_. Without debating the pros and cons of obfuscating database interactions, I think our `Book` class does a pretty good job!
146
+
147
+ Here is a list that Martin Fowler describes as typical Active Record object behaviors and a mapping to what our object does:
148
+
149
+ 1. Construct an instance of the Active Record from a SQL result set row *(e.g. `Book.find()`)*
150
+ 2. Static finder methods to wrap commonly used SQL queries and return Active Record objects *(e.g. `Book.find()` & `Book.create()`)*
151
+ 3. Update the database and insert into it the data in the Active Record *(e.g. `Book#update()`)*
152
+ 4. Get and set the fields *(e.g. `Book#id()`, `Book#title()`, etc)*
153
+
154
+ Two other behaviors are:
155
+
156
+ 1. Implement some pieces of business logic
157
+ 2. Construct a new instance for later insertion into the table
158
+
159
+ Which aren't currently implemented, however, we could follow the pattern above and implement these features pretty quickly.
160
+
161
+ ## What I Learned
162
+
163
+ The spiked implementation highlights a few things for me.
164
+
165
+ 1. The `ActiveRecord` implementation provided by Ruby on Rails does more than just implement the Active Record pattern.
166
+
167
+ `ActiveRecord` isn't just an *implementation* of a pattern, it's an _abstraction_ of the pattern. `ActiveRecord` allows its clients to _create_ Active Record objects, rather than being one. It uses object-orientation to produce interfaces which allow you or I to more easily create a `Book` class that interacts with a database. Using the Active Record pattern to build an ORM is not the same as using Active Record pattern to build an object.
168
+
169
+ 2. Testing is tricky.
170
+
171
+ The clearest way of testing the spiked version of `Book` is to write end-to-end tests that actually touch the database. This isn't ideal, so it's worth taking a step back and considering #1, above. The goal is to build an ORM abstraction that other classes can use, not just build an Active Record class, so there's some pieces missing in the current iteration.
172
+
173
+ 3. It's all about generalization.
174
+
175
+ The spike has left me with ugly code, but "working" code. I say, "working," because I've only got manual tests to verify what "working" means. However, with working code in hand, now I can begin to see abstractions that I could only guess about before.
176
+
177
+ ## Next Steps
178
+
179
+ How can I make SQL queries dynamic? How can I generalize a class' attributes, based on database columns? How can I remove hardcoded dependencies on the `SQLite3` gem?
180
+
181
+ Tomorrow, it's into the fire with these questions in hand. Thankfully, Ruby allows us to answer these questions in many different ways. Hopefully, tests can drive us to the answers!