Module: Waxx::View

Defined in:
lib/waxx/view.rb

Overview

A Waxx::View is like a database view. You define the primary object and related tables and fields on the view and it handles some routine processes for you. You can also just use it as a container for specialized business logic or complicated queries.

Example usage:

“‘ module App::Usr::List

extend Waxx::View
extend self

# Define what layouts to allow
as :json

# Define what fields are in the view
has(
  # Fields that are in the 'usr' table
  :id,
  :usr_name,
  :last_login_date,
  :last_login_host,
  :failed_login_count,
  # Fields that are in the 'person' table. Relationships are defined in App::Usr.has(...)
  "person_id: person.id",  # This column is accessible as 'person_id' on this view
  "person.first_name",
  "person.last_name",
)

end “‘

This view definition will provide you with the following functionality:

“‘ App::Usr::List.get(x) # Executes the following SQL: SELECT usr.id, usr.usr_name, usr.last_login_date, usr.last_login_host, usr.failed_login_count,

person.id AS person_id, person.first_name, person.last_name

FROM usr LEFT JOIN person ON usr.id = person.id # And returns a PG::Result (If you are using the PG database connector) “‘

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#columnsObject (readonly)

A hash of columns (See Waxx::Pg.has)



52
53
54
# File 'lib/waxx/view.rb', line 52

def columns
  @columns
end

#initial_whereObject (readonly)

Initial where (view will always include this filter (plus any additional where clauses)



66
67
68
# File 'lib/waxx/view.rb', line 66

def initial_where
  @initial_where
end

#joinsObject (readonly)

A hash of name: join_sql. Normally set automatically when the columns are parsed.



54
55
56
# File 'lib/waxx/view.rb', line 54

def joins
  @joins
end

#matchesObject (readonly)

How to search the view by specific field



58
59
60
# File 'lib/waxx/view.rb', line 58

def matches
  @matches
end

#objectObject (readonly)

The parent (primary) object. For example in App::Usr::List, App::Usr is the @object



48
49
50
# File 'lib/waxx/view.rb', line 48

def object
  @object
end

#order_byObject (readonly)

The default order of the results



62
63
64
# File 'lib/waxx/view.rb', line 62

def order_by
  @order_by
end

#ordersObject (readonly)

A hash of how you can sort this view



64
65
66
# File 'lib/waxx/view.rb', line 64

def orders
  @orders
end

#relationsObject (readonly)

A hash of related tables. Normally set automatically when the columns are parsed.



56
57
58
# File 'lib/waxx/view.rb', line 56

def relations
  @relations
end

#searchesObject (readonly)

How to search the view by the “q” parameter



60
61
62
# File 'lib/waxx/view.rb', line 60

def searches
  @searches
end

#tableObject (readonly)

The table name of the primary object



50
51
52
# File 'lib/waxx/view.rb', line 50

def table
  @table
end

Instance Method Details

#[](c) ⇒ Object

Get a column on the view

“‘ App::Usr::Record => :label=>“User Name”, :table=>:usr, :column=>:usr_name, :views=>[App::Usr::Record, App::Usr::List, App::Usr::Signup] “`



94
95
96
# File 'lib/waxx/view.rb', line 94

def [](c)
  @columns[c.to_sym]
end

#as(*views) ⇒ Object

Autogenerate the modules to do standard layouts like Json, Csv, or Tab

“‘ as :json “`

This will generate the following code and allow the output of json formatted data for the view

“‘ module App::Usr::List::Json

extend Waxx::Json
extend self

end “‘



244
245
246
247
248
249
250
251
252
253
# File 'lib/waxx/view.rb', line 244

def as(*views)
  views.each{|v|
    eval("
      module #{name}::#{v.to_s.capitalize} 
        extend Waxx::#{v.to_s.capitalize}
        extend self
      end
    ")
  }
end

#build_where(x, args: {}, matches: @matches, searches: @searches) ⇒ Object

Automatically build the where clause of SQL based on the parameters passed in and the definition of matches and searches.



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/waxx/view.rb', line 319

def build_where(x, args: {}, matches: @matches, searches: @searches)
  return nil if args.nil? or args.empty? or (matches.nil? and searches.nil?)
  w_str = @initial_where[0].dup
  w_args = @initial_where[1].dup
  q = args/:q || x['q']
  if q and searches
    w_str += " AND " if w_str != ""
    w_str += "("
    searches.each_with_index{|c, i|
      col = @columns/c
      w_str += " OR " if i > 0
      w_str += "LOWER(#{col/:table}.#{col/:column}) like $1"
    }
    w_args << "%#{q.downcase}%"
    w_str += ")"
  end
  if matches
    matches.each_with_index{|c, i|
      next if (x/c).to_s == "" and (args/c).to_s == ""
      w_str += " AND " if w_str != ""
      col = self[c.to_sym]
      # match in args needs to be an array
      if col[:match].to_s == 'in'
        w_str += "#{c} in (#{([args/c || x/c].flatten).map{|v|
          w_args << v
          "$#{w_args.size}"
        }.join(',')})"
      else
        w_str += "#{c} #{col[:match] || "="} $#{w_args.size + 1}"
        w_args << (args/c || x/c)
      end
    }
  end
  [w_str, w_args]
end

#debug(str, level = 3) ⇒ Object

A shorcut to Waxx.debug



419
420
421
# File 'lib/waxx/view.rb', line 419

def debug(str, level=3)
  Waxx.debug(str, level)
end

#default_order(ord) ⇒ Object

Set the default order. Order is a key of the field name. Use _name to sort descending.



269
270
271
# File 'lib/waxx/view.rb', line 269

def default_order(ord)
  @order_by = ord
end

#delete(x, id) ⇒ Object

Delete a record by ID (primary key of the primary object)



396
397
398
# File 'lib/waxx/view.rb', line 396

def delete(x, id)
  @object.delete(x, id)
end

#get(x, where: nil, having: nil, order: nil, limit: nil, offset: nil, args: {}, &blk) ⇒ Object

Override this method in a view to change params



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/waxx/view.rb', line 357

def get(x, where:nil, having:nil, order:nil, limit:nil, offset:nil, args:{}, &blk)
  where  ||= build_where(x, args: args)
  order  ||= args/:order || @order_by
  limit  ||= args/:limit
  offset ||= args/:offset
  @object.get(x, 
    view: self, 
    where: where, 
    joins: joins_to_sql(),
    having: having,
    order: order, 
    limit: limit, 
    offset: offset
  )
end

#get_by_id(x, id) ⇒ Object Also known as: by_id

Get a single record of the view based on the primary key of the primary object



375
376
377
# File 'lib/waxx/view.rb', line 375

def get_by_id(x, id)
  @object.get_by_id(x, id, view: self)
end

#has(*cols) ⇒ Object

Columnas on a view can be defined in multiple ways:

“‘ has(

:id,                         # A field in the parent object
:name,                       # Another field in the parent object
"company_name:company.name", # name:rel_name.col_name  "name" is the name of the col in the query, rel_name is the join table as defined in object, col_name is the column in the foreign table 
[:creator, {table: "person", sql_select: "first_name || ' ' || last_name", label: "Creator"}]  # Array: [name, column (Hash)] 
{modifier: {table: "person", sql_select: "first_name || ' ' || last_name", label: "Creator"}}

“‘



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/waxx/view.rb', line 110

def has(*cols)
  return @columns if cols.empty?
  init if @object.nil?
  #@joins = {}
  @columns = {}
  cols.each{|c|
    n = col = nil
    case c
    # Get the col from the object
    when Symbol
      n = c
      col = @object[c]
    # A related col (must be defined in the related object)
    when String
      n, col = string_to_col(c)
    # A custom col [name, col] col is a Hash
    when Array
      n, col = c
    # A custom col {name: col}, col is a Hash
    when Hash
      n, col = c.to_a[0]
    end
    if col.nil?
      Waxx.debug "Column #{c} not defined in #{@object}."
      #raise "Column #{c} not defined in #{@object}."
    end
    @orders[n.to_sym] = col/:order || n
    @orders["_#{n}".to_sym] = col/:_order || "#{n} desc"
    #Waxx.debug @relations.inspect
    #TODO: Deal with relations that have different names than the tables
    col[:views] << self rescue col[:views] = [self]
    @columns[n.to_sym] = col
  }
  @joins ||= Hash[@relations.map{|n, r| [n, %(#{r/:join} JOIN #{r/:foreign_table} AS #{n} ON #{r/:table}.#{r/:col} = #{n}.#{r/:foreign_col})] }]
end

#init(tbl: nil, cols: nil, layouts: nil) ⇒ Object

Initialize a view. This is normally done automatically when calling ‘has`.

Call init if the table, object, or layouts are non-standard. You can also set the attrs directly like ‘@table = ’usr’‘

“‘ tbl: The name of the table cols: Same as has layouts: The layouts to auto-generate using waxx defaults: json, csv, tab, etc. “`



78
79
80
81
82
83
84
85
86
# File 'lib/waxx/view.rb', line 78

def init(tbl: nil, cols: nil, layouts: nil)
  @table = (tbl || App.table_from_class(name)).to_sym
  @object = App.get_const(App, @table)
  @relations = {}
  @orders = {}
  @initial_where = ["", []]
  has(*cols) if cols
  as(layouts) if layouts
end

#init_where(sql, args = []) ⇒ Object

Set the initial where clause. The view will always include this in the where.



275
276
277
# File 'lib/waxx/view.rb', line 275

def init_where(sql, args=[])
  @initial_where = [sql, args]
end

#joins_to_sqlObject

Turn the @joins attribute into SQL for the JOIN clause



224
225
226
227
# File 'lib/waxx/view.rb', line 224

def joins_to_sql()
  return nil if @joins.nil? or @joins.empty?
  @joins.map{|n,v| v}.join(" ")
end

#match_in(*cols) ⇒ Object

An array of columns to match in when passed in as params



257
258
259
# File 'lib/waxx/view.rb', line 257

def match_in(*cols)
  @matches = cols.flatten
end

#not_found(x, data: {}, message: {type: "NotFound", message:"The record you requested was not found."}, as: x.ext) ⇒ Object

Send a not found message back to the client using the appropriate layout (json, csv, etc)



413
414
415
# File 'lib/waxx/view.rb', line 413

def not_found(x, data:{}, message: {type: "NotFound", message:"The record you requested was not found."}, as:x.ext)
  self.const_get(as.to_s.capitalize).not_found(x, data:{}, message: message)
end

#parse_col(str) ⇒ Object

Parse a column (internal method used by col)



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/waxx/view.rb', line 198

def parse_col(str)
  nam = rel = col = nil
  parts = str.split(/[:\.]/)
  case str
  # alias:relationship.column
  when /^\w+:\s*\w+\.\w+$/
    nam, rel, col = str.split(/[:\.]/).map{|part| part.strip}
  # relationship.column
  when /^\w+\.\w+$/
    rel, col = str.split(".")
  # alias:column (from primary object/table)
  when /^\w+:\w+$/
    nam, col = str.split(":")
  # column (from primary object/table)
  when /^\w+$/
    col = str
  else
    raise "Could not parse column definition in Waxx::View.parse_col (#{name}). Unknown match: #{str}."
  end             
  nam = col if nam.nil? 
  rel = @table if rel.nil?
  [nam, rel, col]
end

#post(x, data, returning: nil) ⇒ Object



386
387
388
# File 'lib/waxx/view.rb', line 386

def post(x, data, returning: nil)
  @object.post(x, data, returning: returning, view: self)
end

#put(x, id, data, returning: nil) ⇒ Object



390
391
392
# File 'lib/waxx/view.rb', line 390

def put(x, id, data, returning: nil)
  @object.put(x, id, data, returning: returning, view: self)
end

#put_post(x, id, data, args: nil, returning: nil) ⇒ Object

Save data



382
383
384
# File 'lib/waxx/view.rb', line 382

def put_post(x, id, data, args:nil, returning: nil)
  @object.put_post(x, id, data, view: self, returning: returning)
end

#render(x, data, message: {}, as: x.ext, meth: x.meth) ⇒ Object

Render the view using the layout for meth and as

‘render(x, data, as: ’json’, meth: ‘get’)‘

Uses logical defaults based on x.req



406
407
408
409
# File 'lib/waxx/view.rb', line 406

def render(x, data, message: {}, as:x.ext, meth:x.meth)
  return App.not_found(x) unless const_defined?(as.to_s.capitalize)
  const_get(as.to_s.capitalize).send(meth, x, data, message: message)
end

#run(x, id: nil, data: nil, where: nil, having: nil, order: nil, limit: nil, offset: nil, message: {}, as: x.ext, meth: x.meth, args: {}) ⇒ Object Also known as: view

Gets the data for the view and displays it. This is just a shortcut method.

This is normally called from the handler method defined in Object

“‘ App::Usr::List.run(x) # Given a get request with the json extention, the above is a shortcut to: data = App::Usr::List.get(x) App::Usr::List::Json.get(x, data) “`



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/waxx/view.rb', line 290

def run(x, id:nil, data:nil, where:nil, having:nil, order:nil, limit:nil, offset:nil, message:{}, as:x.ext, meth:x.meth, args:{})
  case meth.to_sym
  when :get, :head
    if data.nil? or data.empty?
      if id
        data = get_by_id(x, id)
      else
        data = get(x, where:where, having:having, order:(order||x['order']), limit:(limit||x['limit']), offset:(offset||x['offset']), args:args)
      end
    end
  when :put, :post, :patch
    data = put_post(x, id, data, args:args)
  when :delete
    delete(x, id, args:args)
  else
    raise "Unknown request method in Waxx::View.run(#{name})"
  end
  layout = const_get(as.to_s.capitalize) rescue nil
  return App.not_found(x, message:"No layout defined for #{as}") if not layout
  if layout.respond_to? meth
    render(x, data, message: message, as: as, meth: meth) 
  else
    render(x, data, message: message, as: as, meth: "get") 
  end
end

#search_in(*cols) ⇒ Object

Any array of columns to automatically search in using the “q” parameter



263
264
265
# File 'lib/waxx/view.rb', line 263

def search_in(*cols)
  @searches = cols.flatten
end

#string_to_col(str) ⇒ Object

Column defined as a string in the format: name:foreign_table.foreign_col Converted to SQL: foreign_table.foreign_col AS name Also adds entries in the @relations hash. @relations drive the SQL join statement Joins are defined in the primary object of this view.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/waxx/view.rb', line 151

def string_to_col(str)
  n, rel_name, col_name = parse_col(str)
  # Look in the primary and related objects for relations
  j = @object.joins/rel_name || @relations.values.map{|foreign_rel| 
    #Waxx.debug "REL: #{foreign_rel}"
    o = App.get_const(App, foreign_rel/:foreign_table)
    #Waxx.debug o
    #Waxx.debug o.joins.inspect
    o.joins/rel_name
  }.compact.first
  #Waxx.debug "j:#{j.inspect}, n: #{n}, rel: #{rel_name}, col: #{col_name}"
  begin
    col = (App.get_const(App, j/:foreign_table)/col_name).dup
    col[:table] = rel_name
  rescue NoMethodError => e
    if j.nil?
      Waxx.debug %(ERROR: \n\n
      NoMethodError: The #{rel_name} relationship is not defined in any columns of #{@object.name}.
      FIX: Add "is: '#{rel_name}:table.foreign_key'" to a column definition in the 'has' method in #{@object.name}.)
    else
      Waxx.debug "ERROR: NoMethodError: #{rel_name} does not define col: #{col_name}"
    end
    raise e
  rescue NameError, TypeError => e
    Waxx.debug "ERROR: Name or Type Error: #{rel_name} does not define col: #{col_name}"
    raise e
  end
  begin
    @relations[rel_name] ||= j #(App.get_const(App, j/:table)).joins
    @orders[n] = App.get_const(App, j/:foreign_table).orders/n
    @orders["_#{n}"] = App.get_const(App, j/:foreign_table).orders/"_#{n}"
    #col[:table] = rel_name 
  rescue NoMethodError => e
    if col.nil?
      Waxx.debug "col is nil"
    else
      Waxx.debug "ERROR: App[#{col[:table]}] has no joins in View.has"
    end
    raise e
  end
  #Waxx.debug n
  #Waxx.debug col
  [n, col]
end