tina4ruby 0.4.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/CHANGELOG.md +80 -0
- data/LICENSE.txt +21 -0
- data/README.md +768 -0
- data/exe/tina4 +4 -0
- data/lib/tina4/api.rb +152 -0
- data/lib/tina4/auth.rb +139 -0
- data/lib/tina4/cli.rb +349 -0
- data/lib/tina4/crud.rb +124 -0
- data/lib/tina4/database.rb +135 -0
- data/lib/tina4/database_result.rb +89 -0
- data/lib/tina4/debug.rb +83 -0
- data/lib/tina4/dev.rb +15 -0
- data/lib/tina4/dev_reload.rb +68 -0
- data/lib/tina4/drivers/firebird_driver.rb +94 -0
- data/lib/tina4/drivers/mssql_driver.rb +112 -0
- data/lib/tina4/drivers/mysql_driver.rb +90 -0
- data/lib/tina4/drivers/postgres_driver.rb +99 -0
- data/lib/tina4/drivers/sqlite_driver.rb +85 -0
- data/lib/tina4/env.rb +55 -0
- data/lib/tina4/field_types.rb +84 -0
- data/lib/tina4/graphql.rb +837 -0
- data/lib/tina4/localization.rb +100 -0
- data/lib/tina4/middleware.rb +59 -0
- data/lib/tina4/migration.rb +124 -0
- data/lib/tina4/orm.rb +168 -0
- data/lib/tina4/public/css/tina4.css +2286 -0
- data/lib/tina4/public/css/tina4.min.css +2 -0
- data/lib/tina4/public/js/tina4.js +134 -0
- data/lib/tina4/public/js/tina4helper.js +387 -0
- data/lib/tina4/queue.rb +117 -0
- data/lib/tina4/queue_backends/kafka_backend.rb +80 -0
- data/lib/tina4/queue_backends/lite_backend.rb +79 -0
- data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -0
- data/lib/tina4/rack_app.rb +150 -0
- data/lib/tina4/request.rb +158 -0
- data/lib/tina4/response.rb +172 -0
- data/lib/tina4/router.rb +148 -0
- data/lib/tina4/scss/tina4css/_alerts.scss +34 -0
- data/lib/tina4/scss/tina4css/_badges.scss +22 -0
- data/lib/tina4/scss/tina4css/_buttons.scss +69 -0
- data/lib/tina4/scss/tina4css/_cards.scss +49 -0
- data/lib/tina4/scss/tina4css/_forms.scss +156 -0
- data/lib/tina4/scss/tina4css/_grid.scss +81 -0
- data/lib/tina4/scss/tina4css/_modals.scss +84 -0
- data/lib/tina4/scss/tina4css/_nav.scss +149 -0
- data/lib/tina4/scss/tina4css/_reset.scss +94 -0
- data/lib/tina4/scss/tina4css/_tables.scss +54 -0
- data/lib/tina4/scss/tina4css/_typography.scss +55 -0
- data/lib/tina4/scss/tina4css/_utilities.scss +197 -0
- data/lib/tina4/scss/tina4css/_variables.scss +117 -0
- data/lib/tina4/scss/tina4css/base.scss +1 -0
- data/lib/tina4/scss/tina4css/colors.scss +48 -0
- data/lib/tina4/scss/tina4css/tina4.scss +17 -0
- data/lib/tina4/scss_compiler.rb +131 -0
- data/lib/tina4/seeder.rb +529 -0
- data/lib/tina4/session.rb +145 -0
- data/lib/tina4/session_handlers/file_handler.rb +55 -0
- data/lib/tina4/session_handlers/mongo_handler.rb +49 -0
- data/lib/tina4/session_handlers/redis_handler.rb +43 -0
- data/lib/tina4/swagger.rb +123 -0
- data/lib/tina4/template.rb +478 -0
- data/lib/tina4/templates/base.twig +26 -0
- data/lib/tina4/templates/errors/403.twig +22 -0
- data/lib/tina4/templates/errors/404.twig +22 -0
- data/lib/tina4/templates/errors/500.twig +22 -0
- data/lib/tina4/testing.rb +213 -0
- data/lib/tina4/version.rb +5 -0
- data/lib/tina4/webserver.rb +101 -0
- data/lib/tina4/websocket.rb +167 -0
- data/lib/tina4/wsdl.rb +164 -0
- data/lib/tina4.rb +259 -0
- data/lib/tina4ruby.rb +4 -0
- metadata +324 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Tina4
|
|
5
|
+
module Localization
|
|
6
|
+
LOCALE_DIRS = %w[locales translations i18n src/locales].freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def translations
|
|
10
|
+
@translations ||= {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def current_locale
|
|
14
|
+
@current_locale || ENV["TINA4_LANGUAGE"] || "en"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def current_locale=(locale)
|
|
18
|
+
@current_locale = locale.to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load(root_dir = Dir.pwd)
|
|
22
|
+
LOCALE_DIRS.each do |dir|
|
|
23
|
+
locale_dir = File.join(root_dir, dir)
|
|
24
|
+
next unless Dir.exist?(locale_dir)
|
|
25
|
+
|
|
26
|
+
Dir.glob(File.join(locale_dir, "*.json")).each do |file|
|
|
27
|
+
locale = File.basename(file, ".json")
|
|
28
|
+
data = JSON.parse(File.read(file))
|
|
29
|
+
translations[locale] ||= {}
|
|
30
|
+
translations[locale].merge!(data)
|
|
31
|
+
Tina4::Debug.debug("Loaded locale: #{locale} from #{file}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Also support YAML
|
|
35
|
+
Dir.glob(File.join(locale_dir, "*.{yml,yaml}")).each do |file|
|
|
36
|
+
begin
|
|
37
|
+
require "yaml"
|
|
38
|
+
locale = File.basename(file, File.extname(file))
|
|
39
|
+
data = YAML.safe_load(File.read(file))
|
|
40
|
+
translations[locale] ||= {}
|
|
41
|
+
translations[locale].merge!(data) if data.is_a?(Hash)
|
|
42
|
+
rescue LoadError
|
|
43
|
+
Tina4::Debug.warning("YAML support requires the 'yaml' gem")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def t(key, locale: nil, default: nil, **interpolations)
|
|
50
|
+
lang = locale || current_locale
|
|
51
|
+
value = lookup(lang, key)
|
|
52
|
+
|
|
53
|
+
if value.nil? && lang != "en"
|
|
54
|
+
value = lookup("en", key)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
value = default || key if value.nil?
|
|
58
|
+
|
|
59
|
+
# Interpolation: "Hello %{name}" => "Hello World"
|
|
60
|
+
interpolations.each do |k, v|
|
|
61
|
+
value = value.gsub("%{#{k}}", v.to_s)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def add(locale, key, value)
|
|
68
|
+
translations[locale.to_s] ||= {}
|
|
69
|
+
keys = key.to_s.split(".")
|
|
70
|
+
hash = translations[locale.to_s]
|
|
71
|
+
keys[0..-2].each do |k|
|
|
72
|
+
hash[k] ||= {}
|
|
73
|
+
hash = hash[k]
|
|
74
|
+
end
|
|
75
|
+
hash[keys.last] = value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def available_locales
|
|
79
|
+
translations.keys
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def lookup(locale, key)
|
|
85
|
+
keys = key.to_s.split(".")
|
|
86
|
+
result = translations[locale]
|
|
87
|
+
return nil unless result
|
|
88
|
+
|
|
89
|
+
keys.each do |k|
|
|
90
|
+
if result.is_a?(Hash)
|
|
91
|
+
result = result[k] || result[k.to_sym]
|
|
92
|
+
else
|
|
93
|
+
return nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
result.is_a?(String) ? result : nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tina4
|
|
4
|
+
class Middleware
|
|
5
|
+
class << self
|
|
6
|
+
def before_handlers
|
|
7
|
+
@before_handlers ||= []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def after_handlers
|
|
11
|
+
@after_handlers ||= []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def before(pattern = nil, &block)
|
|
15
|
+
before_handlers << { pattern: pattern, handler: block }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def after(pattern = nil, &block)
|
|
19
|
+
after_handlers << { pattern: pattern, handler: block }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def clear!
|
|
23
|
+
@before_handlers = []
|
|
24
|
+
@after_handlers = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run_before(request, response)
|
|
28
|
+
before_handlers.each do |entry|
|
|
29
|
+
next unless matches_pattern?(request.path, entry[:pattern])
|
|
30
|
+
result = entry[:handler].call(request, response)
|
|
31
|
+
# If handler returns false, halt the request
|
|
32
|
+
return false if result == false
|
|
33
|
+
end
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def run_after(request, response)
|
|
38
|
+
after_handlers.each do |entry|
|
|
39
|
+
next unless matches_pattern?(request.path, entry[:pattern])
|
|
40
|
+
entry[:handler].call(request, response)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def matches_pattern?(path, pattern)
|
|
47
|
+
return true if pattern.nil?
|
|
48
|
+
case pattern
|
|
49
|
+
when String
|
|
50
|
+
path.start_with?(pattern)
|
|
51
|
+
when Regexp
|
|
52
|
+
pattern.match?(path)
|
|
53
|
+
else
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Tina4
|
|
5
|
+
class Migration
|
|
6
|
+
TRACKING_TABLE = "tina4_migrations"
|
|
7
|
+
|
|
8
|
+
def initialize(db, migrations_dir: nil)
|
|
9
|
+
@db = db
|
|
10
|
+
@migrations_dir = migrations_dir || File.join(Dir.pwd, "migrations")
|
|
11
|
+
ensure_tracking_table
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
pending = pending_migrations
|
|
16
|
+
if pending.empty?
|
|
17
|
+
Tina4::Debug.info("No pending migrations")
|
|
18
|
+
return []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
results = []
|
|
22
|
+
pending.each do |file|
|
|
23
|
+
result = run_migration(file)
|
|
24
|
+
results << result
|
|
25
|
+
end
|
|
26
|
+
results
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def rollback(steps = 1)
|
|
30
|
+
completed = completed_migrations.last(steps)
|
|
31
|
+
completed.reverse.each do |name|
|
|
32
|
+
down_file = File.join(@migrations_dir, name.sub(".sql", ".down.sql"))
|
|
33
|
+
if File.exist?(down_file)
|
|
34
|
+
execute_sql_file(down_file)
|
|
35
|
+
remove_migration_record(name)
|
|
36
|
+
Tina4::Debug.info("Rolled back: #{name}")
|
|
37
|
+
else
|
|
38
|
+
Tina4::Debug.warning("No rollback file for: #{name}")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def status
|
|
44
|
+
{
|
|
45
|
+
completed: completed_migrations,
|
|
46
|
+
pending: pending_migrations.map { |f| File.basename(f) }
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def create(name)
|
|
51
|
+
FileUtils.mkdir_p(@migrations_dir)
|
|
52
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
|
53
|
+
filename = "#{timestamp}_#{name.gsub(/\s+/, '_')}.sql"
|
|
54
|
+
filepath = File.join(@migrations_dir, filename)
|
|
55
|
+
File.write(filepath, "-- Migration: #{name}\n-- Created: #{Time.now}\n\n")
|
|
56
|
+
|
|
57
|
+
down_filepath = filepath.sub(".sql", ".down.sql")
|
|
58
|
+
File.write(down_filepath, "-- Rollback: #{name}\n-- Created: #{Time.now}\n\n")
|
|
59
|
+
|
|
60
|
+
Tina4::Debug.info("Created migration: #{filename}")
|
|
61
|
+
filepath
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def ensure_tracking_table
|
|
67
|
+
unless @db.table_exists?(TRACKING_TABLE)
|
|
68
|
+
@db.execute(<<~SQL)
|
|
69
|
+
CREATE TABLE #{TRACKING_TABLE} (
|
|
70
|
+
id INTEGER PRIMARY KEY,
|
|
71
|
+
migration_name VARCHAR(255) NOT NULL,
|
|
72
|
+
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
73
|
+
)
|
|
74
|
+
SQL
|
|
75
|
+
Tina4::Debug.info("Created migrations tracking table")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def completed_migrations
|
|
80
|
+
result = @db.fetch("SELECT migration_name FROM #{TRACKING_TABLE} ORDER BY id")
|
|
81
|
+
result.map { |r| r[:migration_name] }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def pending_migrations
|
|
85
|
+
return [] unless Dir.exist?(@migrations_dir)
|
|
86
|
+
|
|
87
|
+
completed = completed_migrations
|
|
88
|
+
Dir.glob(File.join(@migrations_dir, "*.sql"))
|
|
89
|
+
.reject { |f| f.end_with?(".down.sql") }
|
|
90
|
+
.sort
|
|
91
|
+
.reject { |f| completed.include?(File.basename(f)) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def run_migration(file)
|
|
95
|
+
name = File.basename(file)
|
|
96
|
+
Tina4::Debug.info("Running migration: #{name}")
|
|
97
|
+
begin
|
|
98
|
+
execute_sql_file(file)
|
|
99
|
+
record_migration(name)
|
|
100
|
+
{ name: name, status: "success" }
|
|
101
|
+
rescue => e
|
|
102
|
+
Tina4::Debug.error("Migration failed: #{name} - #{e.message}")
|
|
103
|
+
{ name: name, status: "failed", error: e.message }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def execute_sql_file(file)
|
|
108
|
+
sql = File.read(file)
|
|
109
|
+
statements = sql.split(";").map(&:strip).reject(&:empty?)
|
|
110
|
+
statements.each do |stmt|
|
|
111
|
+
next if stmt.start_with?("--")
|
|
112
|
+
@db.execute(stmt)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def record_migration(name)
|
|
117
|
+
@db.insert(TRACKING_TABLE, { migration_name: name })
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def remove_migration_record(name)
|
|
121
|
+
@db.delete(TRACKING_TABLE, { migration_name: name })
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/tina4/orm.rb
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Tina4
|
|
5
|
+
class ORM
|
|
6
|
+
include Tina4::FieldTypes
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def db
|
|
10
|
+
Tina4.database
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def find(id)
|
|
14
|
+
pk = primary_key_field || :id
|
|
15
|
+
result = db.fetch_one("SELECT * FROM #{table_name} WHERE #{pk} = ?", [id])
|
|
16
|
+
return nil unless result
|
|
17
|
+
from_hash(result)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def where(conditions, params = [])
|
|
21
|
+
sql = "SELECT * FROM #{table_name} WHERE #{conditions}"
|
|
22
|
+
results = db.fetch(sql, params)
|
|
23
|
+
results.map { |row| from_hash(row) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def all(limit: nil, skip: nil, order_by: nil)
|
|
27
|
+
sql = "SELECT * FROM #{table_name}"
|
|
28
|
+
sql += " ORDER BY #{order_by}" if order_by
|
|
29
|
+
results = db.fetch(sql, [], limit: limit, skip: skip)
|
|
30
|
+
results.map { |row| from_hash(row) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def count(conditions = nil, params = [])
|
|
34
|
+
sql = "SELECT COUNT(*) as cnt FROM #{table_name}"
|
|
35
|
+
sql += " WHERE #{conditions}" if conditions
|
|
36
|
+
result = db.fetch_one(sql, params)
|
|
37
|
+
result[:cnt].to_i
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create(attributes = {})
|
|
41
|
+
instance = new(attributes)
|
|
42
|
+
instance.save
|
|
43
|
+
instance
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def from_hash(hash)
|
|
47
|
+
instance = new
|
|
48
|
+
hash.each do |key, value|
|
|
49
|
+
setter = "#{key}="
|
|
50
|
+
instance.send(setter, value) if instance.respond_to?(setter)
|
|
51
|
+
end
|
|
52
|
+
instance.instance_variable_set(:@persisted, true)
|
|
53
|
+
instance
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize(attributes = {})
|
|
58
|
+
@persisted = false
|
|
59
|
+
@errors = []
|
|
60
|
+
attributes.each do |key, value|
|
|
61
|
+
setter = "#{key}="
|
|
62
|
+
send(setter, value) if respond_to?(setter)
|
|
63
|
+
end
|
|
64
|
+
# Set defaults
|
|
65
|
+
self.class.field_definitions.each do |name, opts|
|
|
66
|
+
if send(name).nil? && opts[:default]
|
|
67
|
+
send("#{name}=", opts[:default])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def save
|
|
73
|
+
@errors = []
|
|
74
|
+
validate_fields
|
|
75
|
+
return false unless @errors.empty?
|
|
76
|
+
|
|
77
|
+
data = to_hash(exclude_nil: true)
|
|
78
|
+
pk = self.class.primary_key_field || :id
|
|
79
|
+
pk_value = send(pk)
|
|
80
|
+
|
|
81
|
+
if @persisted && pk_value
|
|
82
|
+
filter = { pk => pk_value }
|
|
83
|
+
data.delete(pk)
|
|
84
|
+
self.class.db.update(self.class.table_name, data, filter)
|
|
85
|
+
else
|
|
86
|
+
result = self.class.db.insert(self.class.table_name, data)
|
|
87
|
+
if result[:last_id] && respond_to?("#{pk}=")
|
|
88
|
+
send("#{pk}=", result[:last_id])
|
|
89
|
+
end
|
|
90
|
+
@persisted = true
|
|
91
|
+
end
|
|
92
|
+
true
|
|
93
|
+
rescue => e
|
|
94
|
+
@errors << e.message
|
|
95
|
+
false
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def delete
|
|
99
|
+
pk = self.class.primary_key_field || :id
|
|
100
|
+
pk_value = send(pk)
|
|
101
|
+
return false unless pk_value
|
|
102
|
+
|
|
103
|
+
self.class.db.delete(self.class.table_name, { pk => pk_value })
|
|
104
|
+
@persisted = false
|
|
105
|
+
true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def load(id = nil)
|
|
109
|
+
pk = self.class.primary_key_field || :id
|
|
110
|
+
id ||= send(pk)
|
|
111
|
+
return false unless id
|
|
112
|
+
|
|
113
|
+
result = self.class.db.fetch_one("SELECT * FROM #{self.class.table_name} WHERE #{pk} = ?", [id])
|
|
114
|
+
return false unless result
|
|
115
|
+
|
|
116
|
+
result.each do |key, value|
|
|
117
|
+
setter = "#{key}="
|
|
118
|
+
send(setter, value) if respond_to?(setter)
|
|
119
|
+
end
|
|
120
|
+
@persisted = true
|
|
121
|
+
true
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def persisted?
|
|
125
|
+
@persisted
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def errors
|
|
129
|
+
@errors
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def to_hash(exclude_nil: false)
|
|
133
|
+
hash = {}
|
|
134
|
+
self.class.field_definitions.each_key do |name|
|
|
135
|
+
value = send(name)
|
|
136
|
+
next if exclude_nil && value.nil?
|
|
137
|
+
hash[name] = value
|
|
138
|
+
end
|
|
139
|
+
hash
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def to_json(*_args)
|
|
143
|
+
JSON.generate(to_hash)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def to_s
|
|
147
|
+
"#<#{self.class.name} #{to_hash}>"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def select(*fields)
|
|
151
|
+
fields_str = fields.map(&:to_s).join(", ")
|
|
152
|
+
pk = self.class.primary_key_field || :id
|
|
153
|
+
pk_value = send(pk)
|
|
154
|
+
self.class.db.fetch_one("SELECT #{fields_str} FROM #{self.class.table_name} WHERE #{pk} = ?", [pk_value])
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def validate_fields
|
|
160
|
+
self.class.field_definitions.each do |name, opts|
|
|
161
|
+
value = send(name)
|
|
162
|
+
if !opts[:nullable] && value.nil? && !opts[:auto_increment] && !opts[:default]
|
|
163
|
+
@errors << "#{name} cannot be null"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|