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
-
#columns ⇒ Object
readonly
A hash of columns (See Waxx::Pg.has).
-
#initial_where ⇒ Object
readonly
Initial where (view will always include this filter (plus any additional where clauses).
-
#joins ⇒ Object
readonly
A hash of name: join_sql.
-
#matches ⇒ Object
readonly
How to search the view by specific field.
-
#object ⇒ Object
readonly
The parent (primary) object.
-
#order_by ⇒ Object
readonly
The default order of the results.
-
#orders ⇒ Object
readonly
A hash of how you can sort this view.
-
#relations ⇒ Object
readonly
A hash of related tables.
-
#searches ⇒ Object
readonly
How to search the view by the “q” parameter.
-
#table ⇒ Object
readonly
The table name of the primary object.
Instance Method Summary collapse
-
#[](c) ⇒ Object
Get a column on the view.
-
#as(*views) ⇒ Object
Autogenerate the modules to do standard layouts like Json, Csv, or Tab.
-
#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.
-
#debug(str, level = 3) ⇒ Object
A shorcut to Waxx.debug.
-
#default_order(ord) ⇒ Object
Set the default order.
-
#delete(x, id) ⇒ Object
Delete a record by ID (primary key of the primary object).
-
#get(x, where: nil, having: nil, order: nil, limit: nil, offset: nil, args: {}, &blk) ⇒ Object
Override this method in a view to change params.
-
#get_by_id(x, id) ⇒ Object
(also: #by_id)
Get a single record of the view based on the primary key of the primary object.
-
#has(*cols) ⇒ Object
Columnas on a view can be defined in multiple ways:.
-
#init(tbl: nil, cols: nil, layouts: nil) ⇒ Object
Initialize a view.
-
#init_where(sql, args = []) ⇒ Object
Set the initial where clause.
-
#joins_to_sql ⇒ Object
Turn the @joins attribute into SQL for the JOIN clause.
-
#match_in(*cols) ⇒ Object
An array of columns to match in when passed in as params.
-
#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).
-
#parse_col(str) ⇒ Object
Parse a column (internal method used by col).
- #post(x, data, returning: nil) ⇒ Object
- #put(x, id, data, returning: nil) ⇒ Object
-
#put_post(x, id, data, args: nil, returning: nil) ⇒ Object
Save data.
-
#render(x, data, message: {}, as: x.ext, meth: x.meth) ⇒ Object
Render the view using the layout for meth and as.
-
#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: #view)
Gets the data for the view and displays it.
-
#search_in(*cols) ⇒ Object
Any array of columns to automatically search in using the “q” parameter.
-
#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.
Instance Attribute Details
#columns ⇒ Object (readonly)
A hash of columns (See Waxx::Pg.has)
52 53 54 |
# File 'lib/waxx/view.rb', line 52 def columns @columns end |
#initial_where ⇒ Object (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 |
#joins ⇒ Object (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 |
#matches ⇒ Object (readonly)
How to search the view by specific field
58 59 60 |
# File 'lib/waxx/view.rb', line 58 def matches @matches end |
#object ⇒ Object (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_by ⇒ Object (readonly)
The default order of the results
62 63 64 |
# File 'lib/waxx/view.rb', line 62 def order_by @order_by end |
#orders ⇒ Object (readonly)
A hash of how you can sort this view
64 65 66 |
# File 'lib/waxx/view.rb', line 64 def orders @orders end |
#relations ⇒ Object (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 |
#searches ⇒ Object (readonly)
How to search the view by the “q” parameter
60 61 62 |
# File 'lib/waxx/view.rb', line 60 def searches @searches end |
#table ⇒ Object (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_sql ⇒ Object
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: ) 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: ) 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: , as: as, meth: meth) else render(x, data, 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 |