module Typus
module Orm
module ClassMethods
# Model fields as an ActiveSupport::OrderedHash.
def model_fields
hash = ActiveSupport::OrderedHash.new
columns.map { |u| hash[u.name.to_sym] = u.type.to_sym }
return hash
end
# Model relationships as an ActiveSupport::OrderedHash.
def model_relationships
hash = ActiveSupport::OrderedHash.new
reflect_on_all_associations.map { |i| hash[i.name] = i.macro }
return hash
end
# Model description for admin panel.
def typus_description
Typus::Configuration.config[self.name]['description']
end
# Form and list fields
def typus_fields_for(filter)
fields_with_type = ActiveSupport::OrderedHash.new
begin
fields = Typus::Configuration.config[name]['fields'][filter.to_s]
fields = fields.extract_settings.collect { |f| f.to_sym }
rescue
return [] if filter == 'default'
filter = 'default'
retry
end
begin
fields.each do |field|
attribute_type = model_fields[field]
if reflect_on_association(field)
attribute_type = reflect_on_association(field).macro
end
if typus_field_options_for(:selectors).include?(field)
attribute_type = :selector
end
# Custom field_type depending on the attribute name.
case field.to_s
when 'parent', 'parent_id' then attribute_type = :tree
when /password/ then attribute_type = :password
when 'position' then attribute_type = :position
when /\./ then attribute_type = :transversal
end
if respond_to?(:attachment_definitions) && attachment_definitions.try(:has_key?, field)
attribute_type = :file
end
# And finally insert the field and the attribute_type
# into the fields_with_type ordered hash.
fields_with_type[field.to_s] = attribute_type
end
rescue
fields = Typus::Configuration.config[name]['fields']['default'].extract_settings
retry
end
return fields_with_type
end
def typus_filters
fields_with_type = ActiveSupport::OrderedHash.new
data = Typus::Configuration.config[name]['filters']
return [] unless data
fields = data.extract_settings.collect { |i| i.to_sym }
fields.each do |field|
attribute_type = model_fields[field.to_sym]
if reflect_on_association(field.to_sym)
attribute_type = reflect_on_association(field.to_sym).macro
end
fields_with_type[field.to_s] = attribute_type
end
return fields_with_type
end
# Extended actions for this model on Typus.
def typus_actions_on(filter)
Typus::Configuration.config[name]['actions'][filter.to_s].extract_settings
rescue
[]
end
# Used for +search+, +relationships+
def typus_defaults_for(filter)
data = Typus::Configuration.config[name][filter.to_s]
return data.try(:extract_settings) || []
end
def typus_search_fields
data = typus_defaults_for(:search)
search = {}
data.each do |field|
if field.starts_with?("=")
field.slice!(0)
search[field] = "="
elsif field.starts_with?("^")
field.slice!(0)
search[field] = "^"
else
search[field] = "@"
end
end
return search
end
def typus_application
Typus::Configuration.config[name]["application"] || "Unknown"
end
def typus_field_options_for(filter)
Typus::Configuration.config[name]['fields']['options'][filter.to_s].extract_settings.collect { |i| i.to_sym }
rescue
[]
end
#--
# Options are defined for all resources on the initializer:
#
# Typus::Resources.setup do |config|
# config.per_page = 15
# end
#
# But sometimes we need to define theme by model:
#
# Post:
# ...
# options:
# per_page: 15
#++
def typus_options_for(filter)
data = Typus::Configuration.config[name]
unless data['options'].nil?
value = data['options'][filter.to_s] unless data['options'][filter.to_s].nil?
end
value || Typus::Resources.send(filter)
end
def typus_export_formats
Typus::Configuration.config[name]['export'].try(:extract_settings) || []
end
def typus_order_by
typus_defaults_for(:order_by).map do |field|
field.include?('-') ? "#{table_name}.#{field.delete('-')} DESC" : "#{table_name}.#{field} ASC"
end.join(', ')
end
#--
# Define our own boolean mappings.
#
# Post:
# fields:
# default: title, status
# options:
# booleans:
# status: "Published", "Not published"
#
#++
def typus_boolean(attribute = :default)
boolean = Typus::Configuration.config[name]['fields']['options']['booleans'][attribute.to_s]
boolean = boolean.extract_settings
{ boolean.first => "true", boolean.last => "false" }
rescue
{ "True" => "true", "False" => "false" }
end
#--
# Custom date formats.
#++
def typus_date_format(attribute = :default)
Typus::Configuration.config[name]['fields']['options']['date_formats'][attribute.to_s].to_sym
rescue
:db
end
#--
# This is user to use custome templates for attribute:
#
# Post:
# fields:
# form: title, body, status
# options:
# templates:
# body: rich_text
#
# Templates are stored on app/views/admin/templates.
#++
def typus_template(attribute)
Typus::Configuration.config[name]['fields']['options']['templates'][attribute.to_s]
rescue
nil
end
#--
# Sidebar filters:
#
# - Booleans: true, false
# - Datetime: today, last_few_days, last_7_days, last_30_days
# - Integer & String: *_id and "selectors" (p.ej. category_id)
#++
def build_conditions(params)
adapter = ActiveRecord::Base.configurations[Rails.env]['adapter']
conditions, joins = merge_conditions, []
query_params = params.dup
%w(action controller).each { |param| query_params.delete(param) }
# Remove from params those with empty string.
query_params.delete_if { |k, v| v.empty? }
# If a search is performed.
if query_params[:search]
query = ActiveRecord::Base.connection.quote_string(query_params[:search].downcase)
search = []
typus_search_fields.each do |key, value|
_query = case value
when "=" then query
when "^" then "#{query}%"
when "@" then "%#{query}%"
end
key = "TEXT(#{key})" if adapter == 'postgresql'
search << "#{key} LIKE '#{_query}'"
end
conditions = merge_conditions(conditions, search.join(" OR "))
end
query_params.each do |key, value|
filter_type = model_fields[key.to_sym] || model_relationships[key.to_sym]
case filter_type
when :boolean
condition = { key => (value == 'true') ? true : false }
conditions = merge_conditions(conditions, condition)
when :datetime
interval = case value
when 'today' then Time.zone.now.beginning_of_day..Time.zone.now.beginning_of_day.tomorrow
when 'last_few_days' then 3.days.ago.beginning_of_day..Time.zone.now.beginning_of_day.tomorrow
when 'last_7_days' then 6.days.ago.beginning_of_day..Time.zone.now.beginning_of_day.tomorrow
when 'last_30_days' then Time.zone.now.beginning_of_day.prev_month..Time.zone.now.beginning_of_day.tomorrow
end
condition = ["#{key} BETWEEN ? AND ?", interval.first.to_s(:db), interval.last.to_s(:db)]
conditions = merge_conditions(conditions, condition)
when :date
if value.kind_of?(Hash)
date_format = Date::DATE_FORMATS[typus_date_format(key)]
begin
unless value["from"].blank?
date_from = Date.strptime(value["from"], date_format)
conditions = merge_conditions(conditions, ["#{key} >= ?", date_from])
end
unless value["to"].blank?
date_to = Date.strptime(value["to"], date_format)
conditions = merge_conditions(conditions, ["#{key} <= ?", date_to])
end
rescue
end
else
# TODO: Improve and test filters.
interval = case value
when 'today' then nil
when 'last_few_days' then 3.days.ago.to_date..Date.tomorrow
when 'last_7_days' then 6.days.ago.beginning_of_day..Date.tomorrow
when 'last_30_days' then (Date.today << 1)..Date.tomorrow
end
if interval
condition = ["#{key} BETWEEN ? AND ?", interval.first, interval.last]
elsif value == 'today'
condition = ["#{key} = ?", Date.today]
end
conditions = merge_conditions(conditions, condition)
end
when :integer, :string
condition = { key => value }
conditions = merge_conditions(conditions, condition)
when :has_and_belongs_to_many
condition = { key => { :id => value } }
conditions = merge_conditions(conditions, condition)
joins << key.to_sym
end
end
return conditions, joins
end
def typus_user_id?
columns.map { |u| u.name }.include?(Typus.user_fk)
end
end
module InstanceMethods
def owned_by?(user)
send(Typus.user_fk) == user.id
end
end
end
end
if defined?(ActiveRecord)
ActiveRecord::Base.extend Typus::Orm::ClassMethods
ActiveRecord::Base.send :include, Typus::Orm::InstanceMethods
end