trails-mvc 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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.gitmodules +0 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +74 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/server.rb +31 -0
- data/bin/setup +8 -0
- data/bin/trails +4 -0
- data/dynamic-archive/.rspec +2 -0
- data/dynamic-archive/.ruby-version +1 -0
- data/dynamic-archive/Gemfile +6 -0
- data/dynamic-archive/Gemfile.lock +38 -0
- data/dynamic-archive/README.md +40 -0
- data/dynamic-archive/lib/associatable.rb +145 -0
- data/dynamic-archive/lib/base.rb +143 -0
- data/dynamic-archive/lib/db_connection.rb +151 -0
- data/dynamic-archive/lib/searchable.rb +39 -0
- data/dynamic-archive/lib/validatable.rb +71 -0
- data/dynamic-archive/spec/associatable_spec.rb +294 -0
- data/dynamic-archive/spec/base_spec.rb +252 -0
- data/dynamic-archive/spec/searchable_spec.rb +44 -0
- data/dynamic-archive/spec/validatable_spec.rb +81 -0
- data/dynamic-archive/testing_database/database.db +0 -0
- data/dynamic-archive/testing_database/database.rb +6 -0
- data/dynamic-archive/testing_database/migrations/bats_migration.sql +7 -0
- data/dynamic-archive/testing_database/migrations/cats_migration.sql +7 -0
- data/dynamic-archive/testing_database/migrations/houses_migration.sql +4 -0
- data/dynamic-archive/testing_database/migrations/humans_migration.sql +8 -0
- data/dynamic-archive/testing_database/migrations/play_times_migration.sql +8 -0
- data/dynamic-archive/testing_database/migrations/toys_migration.sql +4 -0
- data/dynamic-archive/testing_database/schema.sql +44 -0
- data/dynamic-archive/testing_database/seed.sql +44 -0
- data/lib/asset_server.rb +28 -0
- data/lib/cli.rb +109 -0
- data/lib/controller_base.rb +78 -0
- data/lib/exception_handler.rb +14 -0
- data/lib/flash.rb +24 -0
- data/lib/router.rb +61 -0
- data/lib/session.rb +28 -0
- data/lib/trails.rb +30 -0
- data/lib/version.rb +3 -0
- data/public/assets/application.css +231 -0
- data/public/assets/application.js +9 -0
- data/template/Gemfile +1 -0
- data/template/README.md +0 -0
- data/template/app/controllers/application_controller.rb +2 -0
- data/template/app/controllers/static_controller.rb +4 -0
- data/template/app/views/layouts/application.html.erb +15 -0
- data/template/app/views/static/root.html.erb +73 -0
- data/template/config/database.rb +6 -0
- data/template/config/routes.rb +17 -0
- data/template/db/database.db +0 -0
- data/template/db/database.sql +0 -0
- data/template/db/schema.sql +0 -0
- data/template/db/seed.sql +0 -0
- data/template/public/assets/application.css +1 -0
- data/template/public/assets/application.js +1 -0
- data/trails-mvc.gemspec +42 -0
- metadata +257 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1244b83c70f9e9af3cfe1e9a7c44131e47ba9844
|
4
|
+
data.tar.gz: b8c8cd33e4451828db4f3440f5c61bb37ac3b2de
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 35083d83c3cc7b54efdce5205384e61d2fe354c8c0e59e2a4c1e28f754a4c3fdb2268ffed0842f66ea3d71238ff6b8b029fbd3ca35f60c775e5bfbd86aedf872
|
7
|
+
data.tar.gz: 9873a3e4b9e4bdc9f3964a7a83e65515a6691e9c5669a25d83762e255c1f2d53f6b51dc03d9685fcbc210820df0e7892052ae84844dfb3dba4d74aac937ce6c8
|
data/.gitignore
ADDED
data/.gitmodules
ADDED
File without changes
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 William McMeans
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Ruby on Trails
|
2
|
+
## What is Ruby on Trails?
|
3
|
+
**[View the Sample App Live](http://www.ruby-on-trails.com/)**
|
4
|
+
|
5
|
+
**[Trails Sample App Repository](https://github.com/wmcmeans/trails-sample)
|
6
|
+
to see how to use Trails**
|
7
|
+
|
8
|
+
Trails is a lightweight MVC framework built in Ruby by
|
9
|
+
[Will McMeans](https://github.com/wmcmeans). It is similar in functionality to Ruby on
|
10
|
+
Rails, but flavored the way I like it. It features a custom ORM called [Dynamic
|
11
|
+
Archive](https://github.com/wmcmeans/dynamic-archive) (doesn't that sound more
|
12
|
+
exciting than Active Record?) and everything else you need to build a simple web
|
13
|
+
app. We're not riding rails...we're hiking trails!
|
14
|
+
|
15
|
+
## How to Get Started
|
16
|
+
### Install the Trails Gem
|
17
|
+
`gem install trails-mvc`
|
18
|
+
|
19
|
+
### Create Your First App
|
20
|
+
`trails new <name>` — begins a new Trails project
|
21
|
+
|
22
|
+
### Start the Server
|
23
|
+
`trails server [--host] [--port]` — starts the Trails server; default host: localhost,
|
24
|
+
default port: 3000
|
25
|
+
|
26
|
+
### Generate Models and Controllers
|
27
|
+
`trails generate model <model_name>` — creates a new model file in `app/models` and a new migration in `db/migrations`
|
28
|
+
|
29
|
+
`trails generate controller <controller_name>` — creates a new controller file in
|
30
|
+
`app/controllers` and a corresponding view folder in `app/views`
|
31
|
+
|
32
|
+
`trails generate migration <migration_name>` — creates a blank migration file in
|
33
|
+
`db/migrations` (but no model file)
|
34
|
+
|
35
|
+
### Change the Database
|
36
|
+
`trails db reset` — clears the database, runs all migrations, and re-seeds
|
37
|
+
|
38
|
+
`trails db migrate` — runs all pending database migrations
|
39
|
+
|
40
|
+
`trails db seed` — seeds the database
|
41
|
+
|
42
|
+
### Add Routes
|
43
|
+
Follow the example in `config/routes.rb`
|
44
|
+
|
45
|
+
### Add CSS and JavaScript
|
46
|
+
It's recommended to add styling to `public/assets/application.css` and any JavaScript
|
47
|
+
to `public/assets/application.js`. You may also include other scripts and stylesheets
|
48
|
+
in your views as long as you store them in `public/assets`.
|
49
|
+
|
50
|
+
### Not Sure What to do Next?
|
51
|
+
**Check out the [Trails Sample App Repository](https://github.com/wmcmeans/trails-sample)
|
52
|
+
to get a better idea of what you can do with Ruby on Trails**
|
53
|
+
|
54
|
+
## Features
|
55
|
+
* [Dynamic Archive (Custom ORM)](https://github.com/wmcmeans/dynamic-archive)
|
56
|
+
creates rich relationships between objects.
|
57
|
+
* [`Server`](bin/server.rb) handles requests and creates responses, nesting the main
|
58
|
+
app within a `Rack::Builder` instance that contains middleware (e.g. a StackAssetsServer
|
59
|
+
that serves stylesheets).
|
60
|
+
* [`Router`](lib/router.rb) uses routes to direct requests to controllers.
|
61
|
+
* [`TrailsController::Base`](lib/controller_base.rb) builds responses based on
|
62
|
+
parameters and renders content using `ERB` views, including partials.
|
63
|
+
* [`Session`](lib/session.rb) sets and reads cookies to persist data between
|
64
|
+
requests
|
65
|
+
* [`Flash`](lib/flash.rb) a special type of information that lives for only one
|
66
|
+
subsequent request (or just the current one, if using `Flash#now`).
|
67
|
+
|
68
|
+
## Technologies
|
69
|
+
* Ruby
|
70
|
+
* Rack
|
71
|
+
* ERB
|
72
|
+
* SQLite3
|
73
|
+
* Thor
|
74
|
+
* JSON
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "trails"
|
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
|
data/bin/server.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
class TrailsServer
|
4
|
+
def initialize(port, host)
|
5
|
+
@port = port
|
6
|
+
@host = host
|
7
|
+
router = Router.new
|
8
|
+
router.draw(&ROUTES)
|
9
|
+
|
10
|
+
trails_app = Proc.new do |env|
|
11
|
+
req = Rack::Request.new(env)
|
12
|
+
res = Rack::Response.new
|
13
|
+
router.run(req, res)
|
14
|
+
res.finish
|
15
|
+
end
|
16
|
+
|
17
|
+
@app = Rack::Builder.new do
|
18
|
+
use StaticAssetsServer
|
19
|
+
use Rack::MethodOverride
|
20
|
+
run trails_app
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
Rack::Server.start(
|
26
|
+
app: @app,
|
27
|
+
Port: @port,
|
28
|
+
Host: @host
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
data/bin/setup
ADDED
data/bin/trails
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.0
|
@@ -0,0 +1,38 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (4.0.3)
|
5
|
+
i18n (~> 0.6, >= 0.6.4)
|
6
|
+
minitest (~> 4.2)
|
7
|
+
multi_json (~> 1.3)
|
8
|
+
thread_safe (~> 0.1)
|
9
|
+
tzinfo (~> 0.3.37)
|
10
|
+
byebug (8.2.1)
|
11
|
+
diff-lcs (1.2.5)
|
12
|
+
i18n (0.6.9)
|
13
|
+
minitest (4.7.5)
|
14
|
+
multi_json (1.10.1)
|
15
|
+
rspec (3.1.0)
|
16
|
+
rspec-core (~> 3.1.0)
|
17
|
+
rspec-expectations (~> 3.1.0)
|
18
|
+
rspec-mocks (~> 3.1.0)
|
19
|
+
rspec-core (3.1.7)
|
20
|
+
rspec-support (~> 3.1.0)
|
21
|
+
rspec-expectations (3.1.2)
|
22
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
23
|
+
rspec-support (~> 3.1.0)
|
24
|
+
rspec-mocks (3.1.3)
|
25
|
+
rspec-support (~> 3.1.0)
|
26
|
+
rspec-support (3.1.2)
|
27
|
+
sqlite3 (1.3.9)
|
28
|
+
thread_safe (0.3.4)
|
29
|
+
tzinfo (0.3.39)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
activesupport
|
36
|
+
byebug
|
37
|
+
rspec (~> 3.1.0)
|
38
|
+
sqlite3
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Dynamic Archive
|
2
|
+
Dynamic Archive is a Object-Relational Mapping system written in Ruby. It facilitates
|
3
|
+
storage of complex programming objects in databases. Information about objects are
|
4
|
+
stored in tables and retrieved from the database as needed and re-instantiated as
|
5
|
+
Ruby objects. Custom SQL queries allows for elaborate relationships between objects
|
6
|
+
to easily be formed.
|
7
|
+
|
8
|
+
The core of Dynamic Archive lives in [lib](./lib). Dynamic Archive plays a major role
|
9
|
+
as part of [Ruby on Trails](https://github.com/wmcmeans/trails).
|
10
|
+
|
11
|
+
## [DynamicArchive::Base](./lib/base.rb)
|
12
|
+
|
13
|
+
The `Base` class is the core of Dynamic Archive. The class creates attribute methods based
|
14
|
+
on column names in the `::finalize` method. This way, all classes that inherit will have
|
15
|
+
instance methods for properties, e.g. how you can call something like `User#name`
|
16
|
+
(and also `User#name=`).
|
17
|
+
|
18
|
+
Base also has some SQL query methods, like `::find` that take an ID and return the first
|
19
|
+
result from the database (or nil). The `#save` method nicely packages up the `update`
|
20
|
+
and `insert` methods as one, even though the two require two different query strings.
|
21
|
+
|
22
|
+
## Associatable, Searchable, Validatable
|
23
|
+
|
24
|
+
These modules are extended to the `Base` class and make it more powerful.
|
25
|
+
|
26
|
+
### [DynamicArchive::Associatable](./lib/associatable.rb)
|
27
|
+
`Associatable` allows rich relationships to be formed between objects of different classes.
|
28
|
+
Its `::has_many`,`::belongs_to`, `::has_one_through`, and `::has_many_through` methods
|
29
|
+
can generate a complex mapping of interelation.
|
30
|
+
|
31
|
+
### [DynamicArchive::Searchable](./lib/searchable.rb)
|
32
|
+
As the name suggests, `Searchable` gives extra SQL searching capabilities most notably
|
33
|
+
through its `::where` method that allows querying using an interpolated query string or
|
34
|
+
with a hash of params.
|
35
|
+
|
36
|
+
### [DynamicArchive::Validatable](./lib/validatable.rb)
|
37
|
+
`Validatable` gives the option of adding validations to class attributes.
|
38
|
+
`::validates` adds a list of attributes to be validated followed by a hash of validations
|
39
|
+
to check for each. Basic validations such as `length`, `presence`, and `uniqueness`
|
40
|
+
are included.
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module DynamicArchive
|
2
|
+
class AssocOptions
|
3
|
+
attr_accessor(
|
4
|
+
:foreign_key,
|
5
|
+
:class_name,
|
6
|
+
:primary_key
|
7
|
+
)
|
8
|
+
|
9
|
+
def model_class
|
10
|
+
class_name.constantize
|
11
|
+
end
|
12
|
+
|
13
|
+
def table_name
|
14
|
+
model_class.table_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class BelongsToOptions < DynamicArchive::AssocOptions
|
19
|
+
def initialize(name, options = {})
|
20
|
+
defaults = {
|
21
|
+
foreign_key: "#{name.to_s.underscore}_id".to_sym,
|
22
|
+
primary_key: :id,
|
23
|
+
class_name: name.to_s.camelcase
|
24
|
+
}
|
25
|
+
options = defaults.merge(options)
|
26
|
+
|
27
|
+
@foreign_key = options[:foreign_key]
|
28
|
+
@primary_key = options[:primary_key]
|
29
|
+
@class_name = options[:class_name]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class HasManyOptions < DynamicArchive::AssocOptions
|
34
|
+
def initialize(name, self_class_name, options = {})
|
35
|
+
defaults = {
|
36
|
+
foreign_key: "#{self_class_name.to_s.underscore}_id".to_sym,
|
37
|
+
primary_key: :id,
|
38
|
+
class_name: name.to_s.singularize.camelcase
|
39
|
+
}
|
40
|
+
options = defaults.merge(options)
|
41
|
+
|
42
|
+
@foreign_key = options[:foreign_key]
|
43
|
+
@primary_key = options[:primary_key]
|
44
|
+
@class_name = options[:class_name]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module Associatable
|
49
|
+
def assoc_options
|
50
|
+
@assoc_options ||= {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def belongs_to(name, options = {})
|
54
|
+
options = BelongsToOptions.new(name, options)
|
55
|
+
assoc_options[name] = options
|
56
|
+
|
57
|
+
define_method(name) do
|
58
|
+
foreign_key_val = self.send(options.foreign_key)
|
59
|
+
|
60
|
+
options.model_class.where(options.primary_key => foreign_key_val).first
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
def has_many(name, options = {})
|
66
|
+
options = HasManyOptions.new(name, self.to_s, options)
|
67
|
+
assoc_options[name] = options
|
68
|
+
|
69
|
+
define_method(name) do
|
70
|
+
foreign_key_val = self.send(options.primary_key)
|
71
|
+
|
72
|
+
options.model_class.where(options.foreign_key => foreign_key_val)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def has_one_through(name, through_name, source_name)
|
77
|
+
through_options = self.assoc_options[through_name]
|
78
|
+
source_options = through_options.model_class.assoc_options[source_name]
|
79
|
+
|
80
|
+
define_method(name) do
|
81
|
+
key = send(through_options.foreign_key)
|
82
|
+
# use `LIKE` to workaround SQLite bug
|
83
|
+
results = DBConnection.execute(<<-SQL, key)
|
84
|
+
SELECT
|
85
|
+
#{source_options.table_name}.*
|
86
|
+
FROM
|
87
|
+
#{through_options.table_name}
|
88
|
+
JOIN
|
89
|
+
#{source_options.table_name}
|
90
|
+
ON
|
91
|
+
#{through_options.table_name}.#{source_options.foreign_key} LIKE
|
92
|
+
#{source_options.table_name}.#{source_options.primary_key}
|
93
|
+
WHERE
|
94
|
+
#{through_options.table_name}.#{through_options.primary_key} LIKE ?
|
95
|
+
SQL
|
96
|
+
|
97
|
+
source_options.model_class.parse_all(results).first
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def has_many_through(name, through_name, source_name)
|
102
|
+
through_options = self.assoc_options[through_name]
|
103
|
+
source_options = through_options.model_class.assoc_options[source_name]
|
104
|
+
|
105
|
+
define_method(name) do
|
106
|
+
if source_options.is_a?(HasManyOptions)
|
107
|
+
key = send(through_options.primary_key)
|
108
|
+
# use `LIKE` to workaround SQLite bug
|
109
|
+
results = DBConnection.execute(<<-SQL, key)
|
110
|
+
SELECT
|
111
|
+
#{source_options.table_name}.*
|
112
|
+
FROM
|
113
|
+
#{through_options.table_name}
|
114
|
+
JOIN
|
115
|
+
#{source_options.table_name}
|
116
|
+
ON
|
117
|
+
#{through_options.table_name}.#{source_options.primary_key} LIKE
|
118
|
+
#{source_options.table_name}.#{source_options.foreign_key}
|
119
|
+
WHERE
|
120
|
+
#{through_options.table_name}.#{through_options.foreign_key} LIKE ?
|
121
|
+
SQL
|
122
|
+
|
123
|
+
else
|
124
|
+
key = send(through_options.primary_key)
|
125
|
+
# use `LIKE` to workaround SQLite bug
|
126
|
+
results = DBConnection.execute(<<-SQL, key)
|
127
|
+
SELECT
|
128
|
+
#{source_options.table_name}.*
|
129
|
+
FROM
|
130
|
+
#{through_options.table_name}
|
131
|
+
JOIN
|
132
|
+
#{source_options.table_name}
|
133
|
+
ON
|
134
|
+
#{through_options.table_name}.#{source_options.foreign_key} LIKE
|
135
|
+
#{source_options.table_name}.#{source_options.primary_key}
|
136
|
+
WHERE
|
137
|
+
#{through_options.table_name}.#{through_options.foreign_key} LIKE ?
|
138
|
+
SQL
|
139
|
+
end
|
140
|
+
|
141
|
+
source_options.model_class.parse_all(results)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require_relative 'db_connection'
|
2
|
+
require_relative 'associatable'
|
3
|
+
require_relative 'searchable'
|
4
|
+
require_relative 'validatable'
|
5
|
+
require 'active_support/inflector'
|
6
|
+
|
7
|
+
module DynamicArchive
|
8
|
+
class Base
|
9
|
+
extend DynamicArchive::Searchable
|
10
|
+
extend DynamicArchive::Associatable
|
11
|
+
extend DynamicArchive::Validatable
|
12
|
+
|
13
|
+
def self.columns
|
14
|
+
@columns ||= DBConnection.execute2(<<-SQL).first.map(&:to_sym)
|
15
|
+
SELECT
|
16
|
+
*
|
17
|
+
FROM
|
18
|
+
#{table_name}
|
19
|
+
SQL
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.finalize!
|
23
|
+
columns.each do |column|
|
24
|
+
define_method("#{column}=") do |message|
|
25
|
+
attributes[column] = message
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method("#{column}") do
|
29
|
+
attributes[column]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.table_name=(table_name)
|
35
|
+
@table_name = table_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.table_name
|
39
|
+
@table_name ||= self.to_s.tableize
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.all
|
43
|
+
results = DBConnection.execute(<<-SQL)
|
44
|
+
SELECT
|
45
|
+
*
|
46
|
+
FROM
|
47
|
+
#{table_name}
|
48
|
+
SQL
|
49
|
+
parse_all(results)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.parse_all(results)
|
53
|
+
results.map do |result|
|
54
|
+
params = {}
|
55
|
+
result.each do |key, value|
|
56
|
+
params[key.to_sym] = value
|
57
|
+
end
|
58
|
+
self.new(params)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.find(id)
|
63
|
+
results = DBConnection.execute(<<-SQL, id.to_i)
|
64
|
+
SELECT
|
65
|
+
*
|
66
|
+
FROM
|
67
|
+
#{table_name}
|
68
|
+
WHERE
|
69
|
+
id = (?)
|
70
|
+
SQL
|
71
|
+
parse_all(results).first
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(params = {})
|
75
|
+
@validations = Hash.new { |h, k| h[k] = {} }
|
76
|
+
|
77
|
+
params.each do |key, val|
|
78
|
+
attr_name = key.to_sym
|
79
|
+
unless self.class.columns.include?(key) || self.class.method_defined?("#{attr_name}=")
|
80
|
+
raise "unknown attribute '#{attr_name}'"
|
81
|
+
end
|
82
|
+
self.send("#{attr_name}=", val)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes
|
87
|
+
@attributes ||= {}
|
88
|
+
end
|
89
|
+
|
90
|
+
def attribute_values
|
91
|
+
self.class.columns.map do |attr|
|
92
|
+
self.send(attr)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def insert
|
97
|
+
col_names = self.class.columns.join(", ")
|
98
|
+
question_marks = (["?"] * self.class.columns.length).join(", ")
|
99
|
+
DBConnection.execute(<<-SQL, attribute_values)
|
100
|
+
INSERT INTO
|
101
|
+
#{self.class.table_name} (#{col_names})
|
102
|
+
VALUES
|
103
|
+
(#{question_marks})
|
104
|
+
SQL
|
105
|
+
self.id = DBConnection.last_insert_row_id
|
106
|
+
end
|
107
|
+
|
108
|
+
def update
|
109
|
+
set_line = self.class.columns.map { |col| "#{col} = (?)"}.join(", ")
|
110
|
+
DBConnection.execute(<<-SQL, attribute_values)
|
111
|
+
UPDATE
|
112
|
+
#{self.class.table_name}
|
113
|
+
SET
|
114
|
+
#{set_line}
|
115
|
+
WHERE
|
116
|
+
id = #{id}
|
117
|
+
SQL
|
118
|
+
end
|
119
|
+
|
120
|
+
def save
|
121
|
+
if self.class.is_valid?(self)
|
122
|
+
id.nil? ? insert : update
|
123
|
+
else
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def destroy
|
129
|
+
DBConnection.execute(<<-SQL, self.id)
|
130
|
+
DELETE FROM
|
131
|
+
#{self.class.table_name}
|
132
|
+
WHERE
|
133
|
+
id = (?)
|
134
|
+
SQL
|
135
|
+
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
def errors
|
140
|
+
@errors ||= []
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|