Class: YesRecord

Inherits:
Object
  • Object
show all
Extended by:
Yescode::Strings
Defined in:
lib/yes_record.rb

Defined Under Namespace

Classes: Column, Query, QueryNotFound, RecordNotFound

Constant Summary

Constants included from Yescode::Strings

Yescode::Strings::SNAKE_CASE_REGEX

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Yescode::Strings

camel_case, class_name, filename, pascal_case, snake_case

Constructor Details

#initialize(args = {}) ⇒ YesRecord

Returns a new instance of YesRecord.



171
172
173
174
# File 'lib/yes_record.rb', line 171

def initialize(args = {})
  self.class.connect
  load(args)
end

Class Attribute Details

.table_nameObject



14
15
16
# File 'lib/yes_record.rb', line 14

def table_name
  @table_name || filename
end

Instance Attribute Details

#errorsObject

Returns the value of attribute errors.



169
170
171
# File 'lib/yes_record.rb', line 169

def errors
  @errors
end

Class Method Details

.all(name = :all, *params) ⇒ Object



111
112
113
# File 'lib/yes_record.rb', line 111

def all(name = :all, *params)
  select(name, *params)
end

.clear_request_cache!Object



74
75
76
# File 'lib/yes_record.rb', line 74

def clear_request_cache!
  Yescode::RequestCache.store[table_name] = nil
end

.column_namesObject



36
37
38
# File 'lib/yes_record.rb', line 36

def column_names
  @column_names ||= columns.map(&:name)
end

.columnsObject



26
27
28
29
30
31
32
33
34
# File 'lib/yes_record.rb', line 26

def columns
  @columns ||= schema.map do |r|
    Column.new(
      r["columnName"],
      r["columnType"],
      r["pk"] == 1
    )
  end
end

.connectObject



50
51
52
53
54
55
56
# File 'lib/yes_record.rb', line 50

def connect
  column_names.each do |column|
    attr_accessor column.to_sym
  end

  self
end

.count(name = :count, *params) ⇒ Object



119
120
121
# File 'lib/yes_record.rb', line 119

def count(name = :count, *params)
  value(name, *params)
end

.delete_allObject



161
162
163
164
165
166
# File 'lib/yes_record.rb', line 161

def delete_all
  result = Yescode::Database.execute "delete from #{table_name}"
  clear_request_cache!

  result
end

.execute_query(name, *params) ⇒ Object

Raises:



78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/yes_record.rb', line 78

def execute_query(name, *params)
  query = @queries[name]
  key = params
  request_cache[name] ||= { key => nil }

  raise QueryNotFound, "Can't find a query in #{@query_filename} with name #{name}" unless query

  query.statement ||= Yescode::Database.connection.prepare(query.sql)
  Yescode::Database.logger&.debug(cached: !request_cache.dig(name, key).nil?, sql: query.sql, params:)

  request_cache[name][key] ||= query.statement.execute(*params).to_a
end

.first(name, *params) ⇒ Object



91
92
93
94
95
# File 'lib/yes_record.rb', line 91

def first(name, *params)
  row = execute_query(name, *params).first

  new(row) if row
end

.first!(name, *params) ⇒ Object

Raises:



97
98
99
100
101
102
103
# File 'lib/yes_record.rb', line 97

def first!(name, *params)
  row = execute_query(name, *params).first

  raise RecordNotFound unless row

  new(row) if row
end

.insert(params) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/yes_record.rb', line 135

def insert(params)
  params.transform_keys!(&:to_sym)
  params[:created_at] ||= Time.now.to_i if column_names.include?("created_at")
  sql = insert_sql(params.keys)
  inserted = Yescode::Database.get_first_row(sql, params)

  clear_request_cache!

  new(inserted)
rescue SQLite3::ConstraintException => e
  Yescode::Database.logger.error(msg: e.message)

  record = new(params)
  record.rescue_constraint_error(e)

  record
end

.insert_allObject



153
154
155
# File 'lib/yes_record.rb', line 153

def insert_all
  # TODO: do it
end

.insert_sql(keys) ⇒ Object



131
132
133
# File 'lib/yes_record.rb', line 131

def insert_sql(keys)
  "insert into #{table_name} (#{keys.join(', ')}) #{values(keys)} returning *"
end

.inspectObject



44
45
46
47
48
# File 'lib/yes_record.rb', line 44

def inspect
  attr_str = columns.map { |c| "  #{c.name} => #{c.type}" }.join("\n")

  "#{self} {\n#{attr_str}\n}"
end

.primary_key_columnObject



40
41
42
# File 'lib/yes_record.rb', line 40

def primary_key_column
  @primary_key_column ||= columns.find(&:primary_key).first
end

.queries(filename) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/yes_record.rb', line 58

def queries(filename)
  @query_filename = filename
  filepath = File.join(".", "app", "models", filename)
  queries = Yescode::Queries.queries(filepath)
  @queries = {}
  queries.each do |name, sql|
    @queries[name] = Query.new(name, sql)
  end

  @queries
end

.request_cacheObject



70
71
72
# File 'lib/yes_record.rb', line 70

def request_cache
  Yescode::RequestCache.store[table_name] ||= {}
end

.schemaObject



22
23
24
# File 'lib/yes_record.rb', line 22

def schema
  @schema ||= Yescode::Database.schema[table_name]
end

.select(name, *params) ⇒ Object



105
106
107
108
109
# File 'lib/yes_record.rb', line 105

def select(name, *params)
  rows = execute_query(name, *params)

  rows.map { |r| new(r) }
end

.table(name) ⇒ Object



18
19
20
# File 'lib/yes_record.rb', line 18

def table(name)
  @table_name = name
end

.update_allObject



157
158
159
# File 'lib/yes_record.rb', line 157

def update_all
  # TODO: do it
end

.value(name, *params) ⇒ Object



115
116
117
# File 'lib/yes_record.rb', line 115

def value(name, *params)
  execute_query(name, *params).first&.values&.first
end

.values(keys) ⇒ Object



123
124
125
126
127
128
129
# File 'lib/yes_record.rb', line 123

def values(keys)
  if keys.empty?
    "default values"
  else
    "values (#{keys.map(&:inspect).join(', ')})"
  end
end

Instance Method Details

#check?(constraint_name) ⇒ Boolean

Returns:

  • (Boolean)


232
233
234
235
236
# File 'lib/yes_record.rb', line 232

def check?(constraint_name)
  return false unless @errors

  @errors[:check]&.include?(constraint_name.to_s)
end

#deleteObject



224
225
226
227
228
229
230
# File 'lib/yes_record.rb', line 224

def delete
  sql = "delete from #{self.class.table_name} where #{pk_column} = ?"
  Yescode::Database.execute(sql, pk)
  self.class.clear_request_cache!

  true
end

#duplicate?(column_name) ⇒ Boolean

Returns:

  • (Boolean)


244
245
246
247
248
# File 'lib/yes_record.rb', line 244

def duplicate?(column_name)
  return false unless @errors

  @errors[:unique]&.include?(column_name.to_s)
end

#error?(name) ⇒ Boolean

Returns:

  • (Boolean)


250
251
252
# File 'lib/yes_record.rb', line 250

def error?(name)
  check?(name) || null?(name) || duplicate?(name)
end

#errors?Boolean

Returns:

  • (Boolean)


254
255
256
# File 'lib/yes_record.rb', line 254

def errors?
  @errors.any?
end

#load(args = {}) ⇒ Object



176
177
178
179
180
181
# File 'lib/yes_record.rb', line 176

def load(args = {})
  args.each do |k, v|
    self.class.attr_accessor(k.to_sym) unless respond_to?(k.to_sym)
    public_send("#{k}=", v)
  end
end

#null?(column_name) ⇒ Boolean

Returns:

  • (Boolean)


238
239
240
241
242
# File 'lib/yes_record.rb', line 238

def null?(column_name)
  return false unless @errors

  @errors[:null]&.include?(column_name.to_s)
end

#pkObject



187
188
189
# File 'lib/yes_record.rb', line 187

def pk
  to_h[pk_column]
end

#pk_columnObject



183
184
185
# File 'lib/yes_record.rb', line 183

def pk_column
  self.class.primary_key_column.to_sym
end

#pk_paramObject



191
192
193
# File 'lib/yes_record.rb', line 191

def pk_param
  { pk_column => pk }
end

#rescue_constraint_error(error) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/yes_record.rb', line 258

def rescue_constraint_error(error)
  message = error.message
  name = message.gsub(/(CHECK|NOT NULL|UNIQUE) constraint failed: (\w+\.)?/, '')
  @errors ||= { check: [], null: [], unique: [] }

  if message.start_with?("CHECK")
    @errors[:check] << name
  elsif message.start_with?("NOT NULL")
    @errors[:null] << name
  elsif message.start_with?("UNIQUE")
    @errors[:unique] << name
  end
end

#saved?Boolean

Returns:

  • (Boolean)


276
277
278
# File 'lib/yes_record.rb', line 276

def saved?
  !pk.nil?
end

#to_hObject



272
273
274
# File 'lib/yes_record.rb', line 272

def to_h
  @to_h ||= self.class.column_names.map { |c| [c.to_sym, public_send(c)] }.to_h
end

#to_paramObject



280
281
282
# File 'lib/yes_record.rb', line 280

def to_param
  to_h.slice(self.class.primary_key_column.to_sym)
end

#update(params) ⇒ Object



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/yes_record.rb', line 208

def update(params)
  update_params = update_params(params)
  sql = update_sql(update_params.keys)
  update_params.merge!(pk_param)
  updated = Yescode::Database.get_first_row(sql, update_params)
  self.class.clear_request_cache!
  load(updated)

  true
rescue SQLite3::ConstraintException => e
  Yescode::Database.logger.error(msg: e.message)
  rescue_constraint_error(e)

  false
end

#update_params(params) ⇒ Object



195
196
197
198
199
200
# File 'lib/yes_record.rb', line 195

def update_params(params)
  params.transform_keys!(&:to_sym)
  params[:updated_at] ||= Time.now.to_i if self.class.column_names.include?("updated_at")

  params
end

#update_sql(keys) ⇒ Object



202
203
204
205
206
# File 'lib/yes_record.rb', line 202

def update_sql(keys)
  set_clause = keys.map { |k| "#{k} = :#{k}" }.join(", ")

  "update #{self.class.table_name} set #{set_clause} where #{pk_column} = :#{pk_column} returning *"
end