grails-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 +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +61 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/grails +5 -0
- data/bin/server.rb +37 -0
- data/bin/setup +8 -0
- data/grailedorm/.rspec +2 -0
- data/grailedorm/Gemfile +5 -0
- data/grailedorm/Gemfile.lock +39 -0
- data/grailedorm/associatable.rb +146 -0
- data/grailedorm/attr_accessor_object.rb +19 -0
- data/grailedorm/base.rb +121 -0
- data/grailedorm/db_connection.rb +60 -0
- data/grailedorm/searchable.rb +27 -0
- data/grailedorm/spec/associatable2_spec.rb +79 -0
- data/grailedorm/spec/associatable_spec.rb +165 -0
- data/grailedorm/spec/attr_accessor_object_spec.rb +47 -0
- data/grailedorm/spec/searchable_spec.rb +44 -0
- data/grailedorm/spec/sql_object_spec.rb +240 -0
- data/grails-mvc.gemspec +40 -0
- data/lib/commands.rb +112 -0
- data/lib/controller_base.rb +52 -0
- data/lib/flash.rb +22 -0
- data/lib/router.rb +60 -0
- data/lib/session.rb +32 -0
- data/lib/show_exceptions.rb +20 -0
- data/lib/static.rb +61 -0
- data/lib/version.rb +3 -0
- metadata +170 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
require '00_attr_accessor_object'
|
2
|
+
|
3
|
+
describe AttrAccessorObject do
|
4
|
+
before(:all) do
|
5
|
+
class MyAttrAccessorObject < AttrAccessorObject
|
6
|
+
my_attr_accessor :x, :y
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:all) do
|
11
|
+
Object.send(:remove_const, :MyAttrAccessorObject)
|
12
|
+
end
|
13
|
+
|
14
|
+
subject(:obj) { MyAttrAccessorObject.new }
|
15
|
+
|
16
|
+
describe '#my_attr_accessor' do
|
17
|
+
it 'defines getter methods' do
|
18
|
+
expect(obj).to respond_to(:x)
|
19
|
+
expect(obj).to respond_to(:y)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'defines setter methods' do
|
23
|
+
expect(obj).to respond_to(:x=)
|
24
|
+
expect(obj).to respond_to(:y=)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'getter methods get from associated ivars' do
|
28
|
+
x_val = 'value of @x'
|
29
|
+
y_val = 'value of @y'
|
30
|
+
obj.instance_variable_set('@x', x_val)
|
31
|
+
obj.instance_variable_set('@y', y_val)
|
32
|
+
|
33
|
+
expect(obj.x).to eq(x_val)
|
34
|
+
expect(obj.y).to eq(y_val)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'setter methods set associated ivars' do
|
38
|
+
x_val = 'value of @x'
|
39
|
+
y_val = 'value of @y'
|
40
|
+
obj.x = x_val
|
41
|
+
obj.y = y_val
|
42
|
+
|
43
|
+
expect(obj.instance_variable_get('@x')).to eq(x_val)
|
44
|
+
expect(obj.instance_variable_get('@y')).to eq(y_val)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require '02_searchable'
|
2
|
+
|
3
|
+
describe 'Searchable' do
|
4
|
+
before(:each) { DBConnection.reset }
|
5
|
+
after(:each) { DBConnection.reset }
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
class Cat < SQLObject
|
9
|
+
finalize!
|
10
|
+
end
|
11
|
+
|
12
|
+
class Human < SQLObject
|
13
|
+
self.table_name = 'humans'
|
14
|
+
|
15
|
+
finalize!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it '#where searches with single criterion' do
|
20
|
+
cats = Cat.where(name: 'Breakfast')
|
21
|
+
cat = cats.first
|
22
|
+
|
23
|
+
expect(cats.length).to eq(1)
|
24
|
+
expect(cat.name).to eq('Breakfast')
|
25
|
+
end
|
26
|
+
|
27
|
+
it '#where can return multiple objects' do
|
28
|
+
humans = Human.where(house_id: 1)
|
29
|
+
expect(humans.length).to eq(2)
|
30
|
+
end
|
31
|
+
|
32
|
+
it '#where searches with multiple criteria' do
|
33
|
+
humans = Human.where(fname: 'Matt', house_id: 1)
|
34
|
+
expect(humans.length).to eq(1)
|
35
|
+
|
36
|
+
human = humans[0]
|
37
|
+
expect(human.fname).to eq('Matt')
|
38
|
+
expect(human.house_id).to eq(1)
|
39
|
+
end
|
40
|
+
|
41
|
+
it '#where returns [] if nothing matches the criteria' do
|
42
|
+
expect(Human.where(fname: 'Nowhere', lname: 'Man')).to eq([])
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require '01_sql_object'
|
2
|
+
require 'db_connection'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
describe SQLObject do
|
6
|
+
before(:each) { DBConnection.reset }
|
7
|
+
after(:each) { DBConnection.reset }
|
8
|
+
|
9
|
+
context 'before ::finalize!' do
|
10
|
+
before(:each) do
|
11
|
+
class Cat < SQLObject
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
after(:each) do
|
16
|
+
Object.send(:remove_const, :Cat)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '::table_name' do
|
20
|
+
it 'generates default name' do
|
21
|
+
expect(Cat.table_name).to eq('cats')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '::table_name=' do
|
26
|
+
it 'sets table name' do
|
27
|
+
class Human < SQLObject
|
28
|
+
self.table_name = 'humans'
|
29
|
+
end
|
30
|
+
|
31
|
+
expect(Human.table_name).to eq('humans')
|
32
|
+
|
33
|
+
Object.send(:remove_const, :Human)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '::columns' do
|
38
|
+
it 'returns a list of all column names as symbols' do
|
39
|
+
expect(Cat.columns).to eq([:id, :name, :owner_id])
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'only queries the DB once' do
|
43
|
+
expect(DBConnection).to(
|
44
|
+
receive(:execute2).exactly(1).times.and_call_original)
|
45
|
+
3.times { Cat.columns }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#attributes' do
|
50
|
+
it 'returns @attributes hash byref' do
|
51
|
+
cat_attributes = {name: 'Gizmo'}
|
52
|
+
c = Cat.new
|
53
|
+
c.instance_variable_set('@attributes', cat_attributes)
|
54
|
+
|
55
|
+
expect(c.attributes).to equal(cat_attributes)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'lazily initializes @attributes to an empty hash' do
|
59
|
+
c = Cat.new
|
60
|
+
|
61
|
+
expect(c.instance_variables).not_to include(:@attributes)
|
62
|
+
expect(c.attributes).to eq({})
|
63
|
+
expect(c.instance_variables).to include(:@attributes)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'after ::finalize!' do
|
69
|
+
before(:all) do
|
70
|
+
class Cat < SQLObject
|
71
|
+
self.finalize!
|
72
|
+
end
|
73
|
+
|
74
|
+
class Human < SQLObject
|
75
|
+
self.table_name = 'humans'
|
76
|
+
|
77
|
+
self.finalize!
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
after(:all) do
|
82
|
+
Object.send(:remove_const, :Cat)
|
83
|
+
Object.send(:remove_const, :Human)
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '::finalize!' do
|
87
|
+
it 'creates getter methods for each column' do
|
88
|
+
c = Cat.new
|
89
|
+
expect(c.respond_to? :something).to be false
|
90
|
+
expect(c.respond_to? :name).to be true
|
91
|
+
expect(c.respond_to? :id).to be true
|
92
|
+
expect(c.respond_to? :owner_id).to be true
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'creates setter methods for each column' do
|
96
|
+
c = Cat.new
|
97
|
+
c.name = "Nick Diaz"
|
98
|
+
c.id = 209
|
99
|
+
c.owner_id = 2
|
100
|
+
expect(c.name).to eq 'Nick Diaz'
|
101
|
+
expect(c.id).to eq 209
|
102
|
+
expect(c.owner_id).to eq 2
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'created getter methods read from attributes hash' do
|
106
|
+
c = Cat.new
|
107
|
+
c.instance_variable_set(:@attributes, {name: "Nick Diaz"})
|
108
|
+
expect(c.name).to eq 'Nick Diaz'
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'created setter methods use attributes hash to store data' do
|
112
|
+
c = Cat.new
|
113
|
+
c.name = "Nick Diaz"
|
114
|
+
|
115
|
+
expect(c.instance_variables).to include(:@attributes)
|
116
|
+
expect(c.instance_variables).not_to include(:@name)
|
117
|
+
expect(c.attributes[:name]).to eq 'Nick Diaz'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#initialize' do
|
122
|
+
it 'calls appropriate setter method for each item in params' do
|
123
|
+
# We have to set method expectations on the cat object *before*
|
124
|
+
# #initialize gets called, so we use ::allocate to create a
|
125
|
+
# blank Cat object first and then call #initialize manually.
|
126
|
+
c = Cat.allocate
|
127
|
+
|
128
|
+
expect(c).to receive(:name=).with('Don Frye')
|
129
|
+
expect(c).to receive(:id=).with(100)
|
130
|
+
expect(c).to receive(:owner_id=).with(4)
|
131
|
+
|
132
|
+
c.send(:initialize, {name: 'Don Frye', id: 100, owner_id: 4})
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'throws an error when given an unknown attribute' do
|
136
|
+
expect do
|
137
|
+
Cat.new(favorite_band: 'Anybody but The Eagles')
|
138
|
+
end.to raise_error "unknown attribute 'favorite_band'"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '::all, ::parse_all' do
|
143
|
+
it '::all returns all the rows' do
|
144
|
+
cats = Cat.all
|
145
|
+
expect(cats.count).to eq(5)
|
146
|
+
end
|
147
|
+
|
148
|
+
it '::parse_all turns an array of hashes into objects' do
|
149
|
+
hashes = [
|
150
|
+
{ name: 'cat1', owner_id: 1 },
|
151
|
+
{ name: 'cat2', owner_id: 2 }
|
152
|
+
]
|
153
|
+
|
154
|
+
cats = Cat.parse_all(hashes)
|
155
|
+
expect(cats.length).to eq(2)
|
156
|
+
hashes.each_index do |i|
|
157
|
+
expect(cats[i].name).to eq(hashes[i][:name])
|
158
|
+
expect(cats[i].owner_id).to eq(hashes[i][:owner_id])
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it '::all returns a list of objects, not hashes' do
|
163
|
+
cats = Cat.all
|
164
|
+
cats.each { |cat| expect(cat).to be_instance_of(Cat) }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '::find' do
|
169
|
+
it 'fetches single objects by id' do
|
170
|
+
c = Cat.find(1)
|
171
|
+
|
172
|
+
expect(c).to be_instance_of(Cat)
|
173
|
+
expect(c.id).to eq(1)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'returns nil if no object has the given id' do
|
177
|
+
expect(Cat.find(123)).to be_nil
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe '#attribute_values' do
|
182
|
+
it 'returns array of values' do
|
183
|
+
cat = Cat.new(id: 123, name: 'cat1', owner_id: 1)
|
184
|
+
|
185
|
+
expect(cat.attribute_values).to eq([123, 'cat1', 1])
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '#insert' do
|
190
|
+
let(:cat) { Cat.new(name: 'Gizmo', owner_id: 1) }
|
191
|
+
|
192
|
+
before(:each) { cat.insert }
|
193
|
+
|
194
|
+
it 'inserts a new record' do
|
195
|
+
expect(Cat.all.count).to eq(6)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'sets the id once the new record is saved' do
|
199
|
+
expect(cat.id).to eq(DBConnection.last_insert_row_id)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'creates a new record with the correct values' do
|
203
|
+
# pull the cat again
|
204
|
+
cat2 = Cat.find(cat.id)
|
205
|
+
|
206
|
+
expect(cat2.name).to eq('Gizmo')
|
207
|
+
expect(cat2.owner_id).to eq(1)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe '#update' do
|
212
|
+
it 'saves updated attributes to the DB' do
|
213
|
+
human = Human.find(2)
|
214
|
+
|
215
|
+
human.fname = 'Matthew'
|
216
|
+
human.lname = 'von Rubens'
|
217
|
+
human.update
|
218
|
+
|
219
|
+
# pull the human again
|
220
|
+
human = Human.find(2)
|
221
|
+
expect(human.fname).to eq('Matthew')
|
222
|
+
expect(human.lname).to eq('von Rubens')
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe '#save' do
|
227
|
+
it 'calls #insert when record does not exist' do
|
228
|
+
human = Human.new
|
229
|
+
expect(human).to receive(:insert)
|
230
|
+
human.save
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'calls #update when record already exists' do
|
234
|
+
human = Human.find(1)
|
235
|
+
expect(human).to receive(:update)
|
236
|
+
human.save
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
data/grails-mvc.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "grails-mvc"
|
8
|
+
spec.version = Grails::VERSION
|
9
|
+
spec.authors = ["Victor Wu"]
|
10
|
+
spec.email = ["victor.wu.1@vanderbilt.edu"]
|
11
|
+
|
12
|
+
spec.summary = %q{Grails is a lightweight MVC framework based on Rails!}
|
13
|
+
spec.description = %q{Build web applications easily with Grails!}
|
14
|
+
spec.homepage = "https://github.com/victorwu3/grails/tree/master/bin"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
"public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_dependency "thor", "~> 0.20"
|
34
|
+
|
35
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
36
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
37
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
38
|
+
spec.add_development_dependency "pry", "~> 0.10", ">= 0.10.3"
|
39
|
+
spec.add_development_dependency "activesupport", "< 5.0"
|
40
|
+
end
|
data/lib/commands.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'pathname'
|
3
|
+
require 'fileutils'
|
4
|
+
require_relative 'version'
|
5
|
+
|
6
|
+
module Grails
|
7
|
+
|
8
|
+
def self.project_root
|
9
|
+
current_dir = Pathname.new(Dir.pwd)
|
10
|
+
current_dir.ascend do |dir|
|
11
|
+
gemfile = File.exist?(File.join(dir, 'Gemfile'))
|
12
|
+
app_folder = Dir.exist?(File.join(dir, 'app'))
|
13
|
+
return dir if gemfile && app_folder
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Generate < Thor
|
18
|
+
desc "migration NAME", "creates a migration file with name"
|
19
|
+
def migration(name)
|
20
|
+
file_name = name
|
21
|
+
timestamp = Time.now.to_i
|
22
|
+
path = File.join(project_root, "db/migrations/#{timestamp}_#{name}.sql")
|
23
|
+
File.new(path, "w")
|
24
|
+
|
25
|
+
puts "New migration file #{name} can be found at #{path}"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "model NAME", "creates model and migration files"
|
29
|
+
def model(name)
|
30
|
+
path = File.join(project_root, "app/models/#{name.underscore}.rb")
|
31
|
+
raise "Model already exists" if File.exist?(path)
|
32
|
+
|
33
|
+
File.open(path, "w") do |file|
|
34
|
+
file.write("class #{name.camelcase}" < GrailedORM::Base\n)
|
35
|
+
file.write(" self.finalize!\n")
|
36
|
+
file.write("end\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
migration(name)
|
40
|
+
puts "New migration file #{name} can be found at #{path}"
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "controller NAME", "creates a controller file and view folder"
|
44
|
+
def controller(name)
|
45
|
+
controller_path = File.join(project_root, "app/controllers/#{file.pluralize.underscore}_controlller.rb"
|
46
|
+
controller_name = "#{name.pluralize.camelcase}Controller"
|
47
|
+
view_dir = File.join(project_root, "app/views/#{file.pluralize.underscore}")
|
48
|
+
raise "Controller already exists" if File.exist?(controller_path)
|
49
|
+
|
50
|
+
File.open(controller_path, "w") do |file|
|
51
|
+
file.write("class #{controller_name} < ApplicationController\n")
|
52
|
+
file.write("end")
|
53
|
+
end
|
54
|
+
Dir.mkdir(view_dir) unless Dir.exist?(view_dir)
|
55
|
+
puts "New controller file #{name} can be found at #{controller_path}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class DB < Thor
|
60
|
+
desc "reset", "resets the database and remigrates and reseeds"
|
61
|
+
def reset
|
62
|
+
DBConnection.reset
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "seed", "seeds the database with seed file"
|
66
|
+
def seed
|
67
|
+
DBConnection.seed
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "migrate", "runs all pending migrations if any"
|
71
|
+
def migrate
|
72
|
+
DBConnection.migrate
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class CLI < Thor
|
77
|
+
desc "new", "'new' will generate a new Grails application in the working directory"
|
78
|
+
|
79
|
+
def new(app_name)
|
80
|
+
app_dir = File.join(Dir.pwd, name)
|
81
|
+
Raise "Directory of #{app_name} already exists" if Dir.exist?(app_dir)
|
82
|
+
|
83
|
+
Dir.mkdir(File.join(app_dir, "app/models"))
|
84
|
+
Dir.mkdir(File.join(app_dir, "app/controllers"))
|
85
|
+
Dir.mkdir(File.join(app_dir, "app/views"))
|
86
|
+
Dir.mkdir(File.join(app_dir, "db/migrations"))
|
87
|
+
File.new(File.join(app_dir, "config/routes.rb"))
|
88
|
+
File.new(File.join(app_dir, "Gemfile"))
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
desc "generate COMMAND ...args", "Generates migration, model, or controller"
|
94
|
+
subcommand "g", Generate
|
95
|
+
|
96
|
+
desc "generate COMMAND ...args", "Generates migration, model, or controller"
|
97
|
+
subcommand "generate", Generate
|
98
|
+
|
99
|
+
desc "db COMMAND", "execute an action with the database"
|
100
|
+
subcommand "db", DB
|
101
|
+
|
102
|
+
desc "server", "starts a grails server, with default port of 3000"
|
103
|
+
def server
|
104
|
+
raise "Cannot find Grails app" unless project_root
|
105
|
+
require_relative "#{project_root}/config/routes"
|
106
|
+
require_relative '../bin/server'
|
107
|
+
server = GrailsServer.new(3000)
|
108
|
+
server.start
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|