Class: Netzke::Basepack::DataAdapters::ActiveRecordAdapter

Inherits:
AbstractAdapter
  • Object
show all
Defined in:
lib/netzke/basepack/data_adapters/active_record_adapter.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from AbstractAdapter

adapter_class, #errors_array, #first, #get_property_type, inherited, #initialize, #map_type, #new_record, #save_record

Constructor Details

This class inherits a constructor from Netzke::Basepack::DataAdapters::AbstractAdapter

Class Method Details

.for_class?(model_class) ⇒ Boolean

Returns:

  • (Boolean)


3
4
5
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 3

def self.for_class?(model_class)
  model_class <= ActiveRecord::Base
end

Instance Method Details

#apply_column_filters(relation, column_filter) ⇒ Object

Parses and applies grid column filters, calling consequent “where” methods on the passed relation. Returns the updated relation.

Example column grid data:

{"0" => {
  "data" => {
    "type" => "numeric",
    "comparison" => "gt",
    "value" => 10 },
  "field" => "id"
},
"1" => {
  "data" => {
    "type" => "string",
    "value" => "pizza"
  },
  "field" => "food_name"
}}

This will result in:

relation.where(["id > ?", 10]).where(["food_name like ?", "%pizza%"])


221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 221

def apply_column_filters(relation, column_filter)
  res = relation
  operator_map = {"lt" => "<", "gt" => ">", "eq" => "="}

  # these are still JSON-encoded due to the migration to Ext.direct
  column_filter=JSON.parse(column_filter)
  column_filter.each do |v|
    assoc, method = v["field"].split('__')
    if method
      assoc = @model_class.reflect_on_association(assoc.to_sym)
      field = [assoc.klass.table_name, method].join('.').to_sym
    else
      field = assoc.to_sym
    end

    value = v["value"]

    op = operator_map[v['comparison']]

    case v["type"]
    when "string"
      res = res.where(["#{field} like ?", "%#{value}%"])
    when "date"
      # convert value to the DB date
      value.match /(\d\d)\/(\d\d)\/(\d\d\d\d)/
      res = res.where("#{field} #{op} ?", "#{$3}-#{$1}-#{$2}")
    when "numeric"
      res = res.where(["#{field} #{op} ?", value])
    else
      res = res.where(["#{field} = ?", value])
    end
  end

  res
end

#assoc_and_assoc_method_for_attr(column_name) ⇒ Object

Returns association and association method for a column



159
160
161
162
163
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 159

def assoc_and_assoc_method_for_attr(column_name)
  assoc_name, assoc_method = column_name.split('__')
  assoc = @model_class.reflect_on_association(assoc_name.to_sym) if assoc_method
  [assoc, assoc_method]
end

#class_for(assoc_name) ⇒ Object

Returns the model class for association columns



124
125
126
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 124

def class_for assoc_name
  @model_class.reflect_on_association(assoc_name.to_sym).klass
end

#column_virtual?(c) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
67
68
69
70
71
72
73
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 64

def column_virtual? c
  assoc_name, asso = c[:name].split('__')
  assoc, assoc_method = assoc_and_assoc_method_for_attr(c[:name])

  if assoc
    return !assoc.klass.column_names.map(&:to_sym).include?(assoc_method.to_sym)
  else
    return !@model_class.column_names.map(&:to_sym).include?(c[:name].to_sym)
  end
end

#combobox_options_for_column(column, method_options = {}) ⇒ Object

Returns options for comboboxes in grids/forms



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 76

def combobox_options_for_column(column, method_options = {})
  query = method_options[:query]

  # First, check if we have options for this column defined in persistent storage
  options = column[:combobox_options] && column[:combobox_options].split("\n")
  if options
    query ? options.select{ |o| o.index(/^#{query}/) }.map{ |el| [el] } : options
  else
    assoc, assoc_method = assoc_and_assoc_method_for_attr(column[:name])

    if assoc
      # Options for an asssociation attribute

      relation = assoc.klass.scoped

      relation = relation.extend_with(method_options[:scope]) if method_options[:scope]

      if assoc.klass.column_names.include?(assoc_method)
        # apply query
        relation = relation.where(["#{assoc_method} like ?", "%#{query}%"]) if query.present?
        relation.all.map{ |r| [r.id, r.send(assoc_method)] }
      else
        relation.all.map{ |r| [r.id, r.send(assoc_method)] }.select{ |id,value| value =~ /^#{query}/ }
      end

    else
      # Options for a non-association attribute
      res=@model_class.netzke_combo_options_for(column[:name], method_options)

      # ensure it is an array-in-array, as Ext will fail otherwise
      raise RuntimeError, "netzke_combo_options_for should return an Array" unless res.kind_of? Array
      return [[]] if res.empty?

      unless res.first.kind_of? Array
        res=res.map do |v|
          [v]
        end
      end
      return res
    end
  end
end

#count_records(params, columns = []) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 44

def count_records(params, columns=[])
  # build initial relation based on passed params
  relation = get_relation(params)

  # addressing the n+1 query problem
  columns.each do |c|
    assoc, method = c[:name].split('__')
    relation = relation.includes(assoc.to_sym) if method
  end

  relation.count
end

#destroy(ids) ⇒ Object



128
129
130
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 128

def destroy(ids)
  @model_class.destroy(ids)
end

#find_record(id) ⇒ Object



132
133
134
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 132

def find_record(id)
  @model_class.where(@model_class.primary_key => id).first
end

#foreign_key_for(assoc_name) ⇒ Object



119
120
121
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 119

def foreign_key_for assoc_name
  @model_class.reflect_on_association(assoc_name.to_sym).foreign_key
end

#get_assoc_property_type(assoc_name, prop_name) ⇒ Object



57
58
59
60
61
62
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 57

def get_assoc_property_type assoc_name, prop_name
  if prop_name && assoc=@model_class.reflect_on_association(assoc_name)
    assoc_column = assoc.klass.columns_hash[prop_name.to_s]
    assoc_column.try(:type)
  end
end

#get_records(params, columns = []) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 7

def get_records(params, columns=[])
  # build initial relation based on passed params
  relation = get_relation(params)

  # addressing the n+1 query problem
  columns.each do |c|
    assoc, method = c[:name].split('__')
    relation = relation.includes(assoc.to_sym) if method
  end

  # apply sorting if needed
  if params[:sort] && sort_params = params[:sort].first
    assoc, method = sort_params["property"].split('__')
    dir = sort_params["direction"].downcase

    # if a sorting scope is set, call the scope with the given direction
    column = columns.detect { |c| c[:name] == sort_params["property"] }
    if column.has_key?(:sorting_scope)
      relation = relation.send(column[:sorting_scope].to_sym, dir.to_sym)
    else
      relation = if method.nil?
        relation.order("#{assoc} #{dir}")
      else
        assoc = @model_class.reflect_on_association(assoc.to_sym)
        relation.joins(assoc.name).order("#{assoc.klass.table_name}.#{method} #{dir}")
      end
    end
  end

  page = params[:limit] ? params[:start].to_i/params[:limit].to_i + 1 : 1
  if params[:limit]
    relation.offset(params[:start]).limit(params[:limit])
  else
    relation.all
  end
end

#get_relation(params = {}) ⇒ Object

An ActiveRecord::Relation instance encapsulating all the necessary conditions.



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
195
196
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 167

def get_relation(params = {})
  @arel = @model_class.arel_table

  relation = @model_class.scoped

  relation = apply_column_filters(relation, params[:filter]) if params[:filter]

  if params[:extra_conditions]
    extra_conditions = normalize_extra_conditions(ActiveSupport::JSON.decode(params[:extra_conditions]))
    relation = relation.extend_with_netzke_conditions(extra_conditions) if params[:extra_conditions]
  end

  query = params[:query] && ActiveSupport::JSON.decode(params[:query])

  if query.present?
    # array of arrays of conditions that should be joined by OR
    and_predicates = query.map do |conditions|
      predicates_for_and_conditions(conditions)
    end

    # join them by OR
    predicates = and_predicates[1..-1].inject(and_predicates.first){ |r,c| r.or(c) }
  end

  relation = relation.where(predicates)

  relation = relation.extend_with(params[:scope]) if params[:scope]

  relation
end

#hash_fk_modelObject

Build a hash of foreign keys and the associated model



137
138
139
140
141
142
143
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 137

def hash_fk_model
  foreign_keys = {}
  @model_class.reflect_on_all_associations(:belongs_to).map{ |r|
    foreign_keys[r.association_foreign_key.to_sym] = r.name
  }
  foreign_keys
end

#move_records(params) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 145

def move_records(params)
  if defined?(ActsAsList) && @model_class.ancestors.include?(ActsAsList::InstanceMethods)
    ids = JSON.parse(params[:ids]).reverse
    ids.each_with_index do |id, i|
      r = @model_class.find(id)
      r.insert_at(params[:new_index].to_i + i + 1)
    end
    on_data_changed
  else
    raise RuntimeError, "Model class should implement 'acts_as_list' to support reordering records"
  end
end

#predicates_for_and_conditions(conditions) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/netzke/basepack/data_adapters/active_record_adapter.rb', line 257

def predicates_for_and_conditions(conditions)
  return nil if conditions.empty?

  predicates = conditions.map do |q|
    value = q["value"]
    case q["operator"]
    when "contains"
      @arel[q["attr"]].matches "%#{value}%"
    else
      if value == false || value == true
        @arel[q["attr"]].eq(value ? 1 : 0)
      else
        @arel[q["attr"]].send(q["operator"], value)
      end
    end
  end

  # join them by AND
  predicates[1..-1].inject(predicates.first){ |r,p| r.and(p)  }
end