tgauge 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/README.md +36 -0
- data/Rakefile +2 -0
- data/TGauge.gemspec +40 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/tgauge.rb +119 -0
- data/lib/app/controllers/flash.rb +90 -0
- data/lib/app/controllers/session.rb +27 -0
- data/lib/app/controllers/tcontroller_base.rb +91 -0
- data/lib/app/models/associatable.rb +117 -0
- data/lib/app/models/searchable.rb +47 -0
- data/lib/app/models/trecord_base.rb +163 -0
- data/lib/db/db_connection.rb +99 -0
- data/lib/db/router.rb +74 -0
- data/lib/db/server.rb +27 -0
- data/lib/db/show_exceptions.rb +62 -0
- data/lib/db/static_viewer.rb +52 -0
- data/lib/db/templates/error_page.html.erb +62 -0
- data/lib/templates/Gemfile +3 -0
- data/lib/templates/config/routes.rb +7 -0
- data/lib/templates/db/seeds.rb +7 -0
- data/lib/tgauge.rb +16 -0
- data/lib/version.rb +3 -0
- metadata +180 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d5d4baf3c8db65764294e937b63e35329622aa02
|
4
|
+
data.tar.gz: d91028cb4530550397075d31af4b18230c5cd9c9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 190e0c6bc495d2d0f3e065471656f1c5ed0ac479624a20b3a7528529fbea151ed3cd109ad65b9c5a1c84a176e648a0475ae191d163e842b72e8b96911b5a192e
|
7
|
+
data.tar.gz: 8913871c7735f2c118ef88daaeb5a5344b2d7bf12d89b5efc5208567804fb5900709c02052058ccb61f4d5415a93406291a4e48f1497e5aa7e1cb0def388f1f7
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# TGauge
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/TGauge`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'TGauge'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install TGauge
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
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).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/TGauge.
|
36
|
+
|
data/Rakefile
ADDED
data/TGauge.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
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 = "tgauge"
|
8
|
+
spec.version = TGauge::VERSION
|
9
|
+
spec.authors = ["Nam Kim"]
|
10
|
+
spec.email = ["nryulkim@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Simple Ruby ORM and MVC}
|
13
|
+
spec.description = %q{Allows you to build a simple webapp}
|
14
|
+
spec.homepage = "https://github.com/nryulkim/tgauge"
|
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_development_dependency "bundler", "~> 1.13"
|
34
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
+
spec.add_runtime_dependency 'thor', '~> 0.19'
|
36
|
+
spec.add_runtime_dependency 'pg', '~> 0.18'
|
37
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.2', '>= 4.2.5.1'
|
38
|
+
spec.add_runtime_dependency 'rack', '~> 1.6', '>=1.6.4'
|
39
|
+
spec.add_runtime_dependency 'fileutils', '~> 0.7'
|
40
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "TGauge"
|
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/setup
ADDED
data/bin/tgauge.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'thor'
|
3
|
+
require 'active_support/inflector'
|
4
|
+
#Generates models, controllers, and migrations. Alias 'g'
|
5
|
+
class Generate < Thor
|
6
|
+
desc 'model <name>', 'generate a model.'
|
7
|
+
def model(name)
|
8
|
+
File.open('./app/models/#{name.downcase}.rb', 'w') do |f|
|
9
|
+
f.write("class #{name.capitalize} < TGauge::TRecordBase\n\n")
|
10
|
+
f.write("end\n")
|
11
|
+
f.write("#{name.capitalize}.finalize!")
|
12
|
+
end
|
13
|
+
|
14
|
+
migration("Create#{name.capitalize}}")
|
15
|
+
puts "#{name} model created"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'controller <name>', 'generate a controller.'
|
19
|
+
def controller(name)
|
20
|
+
File.open('./app/controllers/#{name.downcase}.rb', 'w') do |f|
|
21
|
+
f.write("class #{name.capitalize}Controller < TGauge::TControllerBase\n\n")
|
22
|
+
f.write("end")
|
23
|
+
end
|
24
|
+
|
25
|
+
Dir.mkdir "./app/views/#{name.downcase}_controller"
|
26
|
+
puts "#{name} controller created"
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'migration <name>', 'generate an empty sql file.'
|
30
|
+
def migration(name)
|
31
|
+
#timestamp
|
32
|
+
ts = Time.now.to_i
|
33
|
+
filename = "#{ts}_#{name.underscore.downcase}"
|
34
|
+
|
35
|
+
File.open('./db/migrations/#{filename}.sql', 'w') do |f|
|
36
|
+
f.write "CREATE TABLE IF NOT EXISTS #{name} (\n"
|
37
|
+
f.write "\tid SERIAL PRIMARY KEY,\n"
|
38
|
+
f.write ')'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Db < Thor
|
44
|
+
desc 'create', 'creates the DB'
|
45
|
+
def create
|
46
|
+
require_relative '../lib/db_connection'
|
47
|
+
TGauge::DBConnection.reset
|
48
|
+
puts 'db created!'
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'migrate', 'runs pending migrations'
|
52
|
+
def migrate
|
53
|
+
# Creates Version table if necessary,
|
54
|
+
# then runs needed migrations in order.
|
55
|
+
require_relative '../lib/db_connection'
|
56
|
+
TGauge::DBConnection.migrate
|
57
|
+
puts 'migrated!'
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'seed', 'seeds the DB'
|
61
|
+
def seed
|
62
|
+
require_relative '../lib/puffs'
|
63
|
+
TGauge::Seed.populate
|
64
|
+
puts 'db seeded!'
|
65
|
+
end
|
66
|
+
|
67
|
+
desc 'reset', 'resets the DB and seeds it'
|
68
|
+
def reset
|
69
|
+
create
|
70
|
+
migrate
|
71
|
+
seed
|
72
|
+
puts 'db reset!'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
#top level commands
|
77
|
+
class CLI < Thor
|
78
|
+
register(Generate, 'generate', 'generate <command>', 'Generates a model, migration, or controller.')
|
79
|
+
register(Db, 'db', 'db <command>', 'Accesses commands for the DB.')
|
80
|
+
|
81
|
+
subcommand 'g', Generate
|
82
|
+
|
83
|
+
desc 'server', 'starts the WebBrick server'
|
84
|
+
def server
|
85
|
+
require_relative '../lib/tgauge.rb'
|
86
|
+
TGauge::Server.start
|
87
|
+
end
|
88
|
+
|
89
|
+
desc 'new', 'creates a new TGauge app'
|
90
|
+
def new(name)
|
91
|
+
Dir.mkdir "./#{name}"
|
92
|
+
Dir.mkdir "./#{name}/config"
|
93
|
+
|
94
|
+
File.open("./#{name}/config/database.yml", 'w') do |f|
|
95
|
+
f.write("database: #{name}")
|
96
|
+
end
|
97
|
+
|
98
|
+
Dir.mkdir "./#{name}/app"
|
99
|
+
Dir.mkdir "./#{name}/app/models"
|
100
|
+
Dir.mkdir "./#{name}/app/views"
|
101
|
+
Dir.mkdir "./#{name}/app/controllers"
|
102
|
+
File.open("./#{name}/app/controllers/application_controller.rb", 'w') do |f|
|
103
|
+
f.write File.read(File.expand_path('../../lib/template/app/controllers/application_controller.rb', __FILE__))
|
104
|
+
end
|
105
|
+
File.open("./#{name}/config/routes.rb", 'w') do |f|
|
106
|
+
f.write File.read(File.expand_path('../../lib/template/config/routes.rb', __FILE__))
|
107
|
+
end
|
108
|
+
Dir.mkdir "./#{name}/db"
|
109
|
+
Dir.mkdir "./#{name}/db/migrations"
|
110
|
+
File.open("./#{name}/db/seeds.rb", 'w') do |f|
|
111
|
+
f.write File.read(File.expand_path('../../lib/template/db/seeds.rb', __FILE__))
|
112
|
+
end
|
113
|
+
File.open("./#{name}/Gemfile", 'w') do |f|
|
114
|
+
f.write File.read(File.expand_path('../../lib/template/Gemfile', __FILE__))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
CLI.start(ARGV)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Flash
|
4
|
+
def initialize(req)
|
5
|
+
cookie_hash = req.cookies["_rails_lite_app_flash"]
|
6
|
+
@existing = cookie_hash ? JSON.parse(cookie_hash) : {}
|
7
|
+
@new = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
merged = @existing.merge(@new)
|
12
|
+
merged[key.to_s]
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(key, val)
|
16
|
+
if @add_next
|
17
|
+
@existing[key.to_s] = val
|
18
|
+
@add_next = false
|
19
|
+
else
|
20
|
+
@new[key.to_s] = val
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def now
|
25
|
+
@add_next = true
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def store_flash(res)
|
30
|
+
out_cookie = @new.to_json
|
31
|
+
res.set_cookie('_rails_lite_app_flash', { path: "/", value: out_cookie })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# class Flash
|
37
|
+
# attr_reader :flashy_cookie
|
38
|
+
#
|
39
|
+
# def initialize(req)
|
40
|
+
# cookie_hash = req.cookies["_rails_lite_app_flash"]
|
41
|
+
# existing_cookie = JSON.parse(cookie_hash) if cookie_hash
|
42
|
+
# @flashy_cookie = existing_cookie ? existing_cookie : {}
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# def [](key)
|
46
|
+
# @flashy_cookie[key.to_s]
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# def []=(key, val)
|
50
|
+
# if @add_next
|
51
|
+
# old_keys.add(key.to_s)
|
52
|
+
# @add_next = false
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @flashy_cookie[key] = val
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# def now
|
59
|
+
# @add_next = true
|
60
|
+
# self
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# def delete_old
|
64
|
+
# old_keys.each do |key|
|
65
|
+
# @flashy_cookie.delete(key)
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# @flashy_cookie.delete(:old)
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# # serialize the hash into json and save in a cookie
|
72
|
+
# # add to the responses cookies
|
73
|
+
# def store_flash(res)
|
74
|
+
# delete_old
|
75
|
+
# old_keys = @flashy_cookie.keys
|
76
|
+
# out_cookie = @flashy_cookie.to_json
|
77
|
+
# res.set_cookie('_rails_lite_app_flash', out_cookie)
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# private
|
81
|
+
# def old_keys
|
82
|
+
# @flashy_cookie[:old] ||= Set.new
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# def old_keys=(key_arr)
|
86
|
+
# old_keys
|
87
|
+
# keys.each { |key| @flash_cookie[:old].add(key) }
|
88
|
+
# old_keys.delete(:old)
|
89
|
+
# end
|
90
|
+
# end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'byebug'
|
3
|
+
class Session
|
4
|
+
# find the cookie for this app
|
5
|
+
# deserialize the cookie into a hash
|
6
|
+
|
7
|
+
def initialize(req)
|
8
|
+
cookie_hash = req.cookies["_rails_lite_app"]
|
9
|
+
existing_cookie = JSON.parse(cookie_hash) if cookie_hash
|
10
|
+
@cookie = existing_cookie ? existing_cookie : {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
@cookie[key]
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, val)
|
18
|
+
@cookie[key] = val
|
19
|
+
end
|
20
|
+
|
21
|
+
# serialize the hash into json and save in a cookie
|
22
|
+
# add to the responses cookies
|
23
|
+
def store_session(res)
|
24
|
+
out_cookie = @cookie.to_json
|
25
|
+
res.set_cookie('_rails_lite_app', { path: "/", value: out_cookie })
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
require 'erb'
|
5
|
+
require_relative './session'
|
6
|
+
require_relative './flash'
|
7
|
+
|
8
|
+
module TGauge
|
9
|
+
class TControllerBase
|
10
|
+
@@defender = false
|
11
|
+
def self.protect_from_forgery
|
12
|
+
@@defender = true
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :req, :res, :params
|
16
|
+
# Setup the controller
|
17
|
+
def initialize(req, res, route_params = {})
|
18
|
+
@req = req
|
19
|
+
@res = res
|
20
|
+
@params = @req.params.merge(route_params)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Helper method to alias @already_built_response
|
24
|
+
def already_built_response?
|
25
|
+
@rendered
|
26
|
+
end
|
27
|
+
|
28
|
+
# Set the response status code and header
|
29
|
+
def redirect_to(url)
|
30
|
+
@rendered ? raise {'Cannote render twice'} : @rendered = true
|
31
|
+
@res['Location'] = url
|
32
|
+
@res.status = 302
|
33
|
+
@session.store_session(res) if @session
|
34
|
+
end
|
35
|
+
|
36
|
+
# Populate the response with content.
|
37
|
+
# Set the response's content type to the given type.
|
38
|
+
# Raise an error if the developer tries to double render.
|
39
|
+
def render_content(content, content_type)
|
40
|
+
@rendered ? raise {'Cannote render twice'} : @rendered = true
|
41
|
+
@res.write(content)
|
42
|
+
@session.store_session(res) if @session
|
43
|
+
@flash.store_flash(res) if @flash
|
44
|
+
@res['Content-Type'] = content_type
|
45
|
+
end
|
46
|
+
|
47
|
+
# use ERB and binding to evaluate templates
|
48
|
+
# pass the rendered html to render_content
|
49
|
+
def render(template_name)
|
50
|
+
class_name = self.class.to_s.underscore
|
51
|
+
class_name.slice! "_controller"
|
52
|
+
view_path = "app/views/#{class_name}/#{template_name}.html.erb"
|
53
|
+
erb = ERB.new(File.read(view_path)).result(binding)
|
54
|
+
render_content(erb, 'text/html')
|
55
|
+
end
|
56
|
+
|
57
|
+
# method exposing a `Session` object
|
58
|
+
def session
|
59
|
+
@session ||= Session.new(@req)
|
60
|
+
end
|
61
|
+
|
62
|
+
def flash
|
63
|
+
@flash ||= Flash.new(@req)
|
64
|
+
end
|
65
|
+
|
66
|
+
# use this with the router to call action_name (:index, :show, :create...)
|
67
|
+
def invoke_action(name)
|
68
|
+
if @@defender && check_authenticity_token
|
69
|
+
@res.write("ATTACK ATTACK!! RUN AND HIDE!")
|
70
|
+
@res.status = 403
|
71
|
+
@res['Content-Type'] = "text/html"
|
72
|
+
else
|
73
|
+
self.send(name)
|
74
|
+
render(name) unless already_built_response?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def form_authenticity_token
|
79
|
+
flash[:_csurf_master_code] = SecureRandom.urlsafe_base64
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def check_authenticity_token
|
84
|
+
!@req.get? && (master_code.nil? || master_code != @req.params["authenticity_token"])
|
85
|
+
end
|
86
|
+
|
87
|
+
def master_code
|
88
|
+
flash[:_csurf_master_code]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
class AssocOptions
|
2
|
+
attr_accessor(
|
3
|
+
:foreign_key,
|
4
|
+
:class_name,
|
5
|
+
:primary_key
|
6
|
+
)
|
7
|
+
|
8
|
+
def model_class
|
9
|
+
class_name.constantize
|
10
|
+
end
|
11
|
+
|
12
|
+
def table_name
|
13
|
+
model_class.table_name
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class BelongsToOptions < AssocOptions
|
19
|
+
def initialize(name, options = {})
|
20
|
+
@foreign_key = options[:foreign_key] || (name.to_s.foreign_key).to_sym
|
21
|
+
@primary_key = options[:primary_key] || :id
|
22
|
+
@class_name = options[:class_name] || name.to_s.classify
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class HasManyOptions < AssocOptions
|
27
|
+
def initialize(name, self_class_name, options = {})
|
28
|
+
@foreign_key = options[:foreign_key] || (self_class_name.foreign_key).to_sym
|
29
|
+
@primary_key = options[:primary_key] || :id
|
30
|
+
@class_name = options[:class_name] || name.to_s.singularize.classify
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Associatable
|
35
|
+
# Phase IIIb
|
36
|
+
def belongs_to(name, options = {})
|
37
|
+
options = BelongsToOptions.new(name, options)
|
38
|
+
|
39
|
+
define_method(name) do
|
40
|
+
fkey_val = self.send(options.foreign_key)
|
41
|
+
class_obj = options.model_class
|
42
|
+
|
43
|
+
class_obj.where(options.primary_key => fkey_val).first
|
44
|
+
end
|
45
|
+
|
46
|
+
assoc_options[name] = options
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_many(name, options = {})
|
50
|
+
# ...
|
51
|
+
unless options.first[0] == :through
|
52
|
+
options = HasManyOptions.new(name, self.to_s, options)
|
53
|
+
|
54
|
+
define_method(name) do
|
55
|
+
class_obj = options.model_class
|
56
|
+
pr_key = self.send(options.primary_key)
|
57
|
+
class_obj.where(options.foreign_key => pr_key)
|
58
|
+
end
|
59
|
+
|
60
|
+
assoc_options[name] = options
|
61
|
+
else
|
62
|
+
options[:source] ||= name
|
63
|
+
through(name, options[:through], options[:source])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def through(name, through_name, source_name)
|
68
|
+
define_method(name) do
|
69
|
+
through_options = self.class.assoc_options[through_name]
|
70
|
+
source_options = through_options.model_class.assoc_options[source_name]
|
71
|
+
|
72
|
+
if through_options.is_a?(BelongsToOptions)
|
73
|
+
# A to B has a belongs_to relationship
|
74
|
+
a_test_key = self.send(through_options.foreign_key)
|
75
|
+
b_column = through_options.primary_key
|
76
|
+
else
|
77
|
+
# A to B has a has_many relationship
|
78
|
+
a_test_key = self.send(through_options.primary_key)
|
79
|
+
b_column = through_options.foreign_key
|
80
|
+
end
|
81
|
+
|
82
|
+
if source_options.is_a?(BelongsToOptions)
|
83
|
+
# B to C has a belongs_to relationship
|
84
|
+
b_test_key = source_options.foreign_key
|
85
|
+
c_column = source_options.primary_key
|
86
|
+
else
|
87
|
+
# B to C has a has_many relationship
|
88
|
+
b_test_key = source_options.primary_key
|
89
|
+
c_column = source_options.foreign_key
|
90
|
+
end
|
91
|
+
source_table = source_options.table_name
|
92
|
+
through_table = through_options.table_name
|
93
|
+
|
94
|
+
all_data = DBConnection.execute(<<-SQL)
|
95
|
+
SELECT
|
96
|
+
#{source_table}.*
|
97
|
+
FROM
|
98
|
+
#{through_table}
|
99
|
+
JOIN
|
100
|
+
#{source_table} ON #{source_table}.#{c_column} = #{through_table}.#{b_test_key}
|
101
|
+
WHERE
|
102
|
+
#{through_table}.#{b_column} = #{a_test_key}
|
103
|
+
SQL
|
104
|
+
|
105
|
+
source_options.model_class.parse_all(all_data)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def has_one (name, options = {})
|
110
|
+
options[:source] ||= name
|
111
|
+
through(name, options[:through], options[:source])
|
112
|
+
end
|
113
|
+
|
114
|
+
def assoc_options
|
115
|
+
@assoc_options ||= {}
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Searchable
|
2
|
+
def where(params)
|
3
|
+
relation_obj ||= LazyRelation.new(self, self.table_name, {})
|
4
|
+
relation_obj.where(params)
|
5
|
+
relation_obj
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class LazyRelation
|
10
|
+
def initialize(klass, table_name, command_hash)
|
11
|
+
@klass = klass
|
12
|
+
@table_name = table_name
|
13
|
+
@command_hash = command_hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def where(params)
|
17
|
+
@command_hash = params.merge(@command_hash)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def where_str
|
22
|
+
@command_hash.map do |col, val|
|
23
|
+
val = "'#{val}'" if val.is_a?(String)
|
24
|
+
"#{@table_name}.#{col} = #{val}"
|
25
|
+
end.join(" AND ")
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def execute
|
30
|
+
all_attrs = DBConnection.execute(<<-SQL)
|
31
|
+
SELECT
|
32
|
+
#{@table_name}.*
|
33
|
+
FROM
|
34
|
+
#{@table_name}
|
35
|
+
WHERE
|
36
|
+
#{where_str}
|
37
|
+
SQL
|
38
|
+
|
39
|
+
all_attrs.map { |atts| @klass.new(atts) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def method_missing(method_name, *args)
|
43
|
+
obj = execute
|
44
|
+
obj[0].send(method_name, *args)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|