netzke-basepack 0.7.4 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. data/.travis.yml +11 -0
  2. data/CHANGELOG.rdoc +10 -0
  3. data/README.md +36 -2
  4. data/Rakefile +1 -3
  5. data/config/ci/before-travis.sh +28 -0
  6. data/lib/netzke/active_record.rb +10 -8
  7. data/lib/netzke/active_record/attributes.rb +28 -17
  8. data/lib/netzke/active_record/relation_extensions.rb +3 -1
  9. data/lib/netzke/basepack.rb +10 -2
  10. data/lib/netzke/basepack/action_column.rb +6 -8
  11. data/lib/netzke/basepack/data_accessor.rb +11 -174
  12. data/lib/netzke/basepack/data_adapters/abstract_adapter.rb +164 -0
  13. data/lib/netzke/basepack/data_adapters/active_record_adapter.rb +279 -0
  14. data/lib/netzke/basepack/data_adapters/data_mapper_adapter.rb +264 -0
  15. data/lib/netzke/basepack/data_adapters/sequel_adapter.rb +260 -0
  16. data/lib/netzke/basepack/form_panel.rb +3 -3
  17. data/lib/netzke/basepack/form_panel/fields.rb +6 -10
  18. data/lib/netzke/basepack/form_panel/javascripts/form_panel.js +1 -0
  19. data/lib/netzke/basepack/form_panel/services.rb +15 -16
  20. data/lib/netzke/basepack/grid_panel.rb +16 -10
  21. data/lib/netzke/basepack/grid_panel/columns.rb +6 -7
  22. data/lib/netzke/basepack/grid_panel/javascripts/event_handling.js +29 -27
  23. data/lib/netzke/basepack/grid_panel/services.rb +13 -90
  24. data/lib/netzke/basepack/paging_form_panel.rb +3 -3
  25. data/lib/netzke/basepack/query_builder.rb +2 -0
  26. data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +29 -19
  27. data/lib/netzke/basepack/search_panel.rb +6 -3
  28. data/lib/netzke/basepack/search_panel/javascripts/search_panel.js +2 -1
  29. data/lib/netzke/basepack/search_window.rb +2 -1
  30. data/lib/netzke/basepack/version.rb +1 -1
  31. data/lib/netzke/data_mapper.rb +18 -0
  32. data/lib/netzke/data_mapper/attributes.rb +273 -0
  33. data/lib/netzke/data_mapper/combobox_options.rb +11 -0
  34. data/lib/netzke/data_mapper/relation_extensions.rb +38 -0
  35. data/lib/netzke/sequel.rb +18 -0
  36. data/lib/netzke/sequel/attributes.rb +274 -0
  37. data/lib/netzke/sequel/combobox_options.rb +10 -0
  38. data/lib/netzke/sequel/relation_extensions.rb +40 -0
  39. data/netzke-basepack.gemspec +24 -13
  40. data/test/basepack_test_app/Gemfile +33 -8
  41. data/test/basepack_test_app/Gemfile.lock +98 -79
  42. data/test/basepack_test_app/Guardfile +46 -0
  43. data/test/basepack_test_app/app/components/book_grid_with_persistence.rb +3 -0
  44. data/test/basepack_test_app/app/components/extras/book_presentation.rb +10 -3
  45. data/test/basepack_test_app/app/models/address.rb +27 -1
  46. data/test/basepack_test_app/app/models/author.rb +28 -0
  47. data/test/basepack_test_app/app/models/book.rb +43 -0
  48. data/test/basepack_test_app/app/models/book_with_custom_primary_key.rb +22 -0
  49. data/test/basepack_test_app/app/models/role.rb +21 -0
  50. data/test/basepack_test_app/app/models/user.rb +24 -0
  51. data/test/basepack_test_app/config/database.yml.sample +11 -10
  52. data/test/basepack_test_app/config/database.yml.travis +15 -0
  53. data/test/basepack_test_app/config/initializers/data_mapper_logging.rb +3 -0
  54. data/test/basepack_test_app/config/initializers/sequel.rb +26 -0
  55. data/test/basepack_test_app/db/schema.rb +0 -3
  56. data/test/basepack_test_app/features/grid_panel.feature +28 -8
  57. data/test/basepack_test_app/features/grid_sorting.feature +6 -6
  58. data/test/basepack_test_app/features/paging_form_panel.feature +13 -13
  59. data/test/basepack_test_app/features/search_in_grid.feature +31 -31
  60. data/test/basepack_test_app/features/step_definitions/generic_steps.rb +3 -1
  61. data/test/basepack_test_app/features/support/env.rb +17 -4
  62. data/test/basepack_test_app/lib/tasks/travis.rake +7 -0
  63. data/test/basepack_test_app/spec/components/form_panel_spec.rb +2 -2
  64. data/test/basepack_test_app/spec/data_adapter/adapter_spec.rb +68 -0
  65. data/test/basepack_test_app/spec/{active_record → data_adapter}/attributes_spec.rb +12 -4
  66. data/test/basepack_test_app/spec/data_adapter/relation_extensions_spec.rb +125 -0
  67. data/test/basepack_test_app/spec/spec_helper.rb +9 -0
  68. data/test/unit/active_record_basepack_test.rb +1 -1
  69. data/test/unit/grid_panel_test.rb +1 -1
  70. metadata +26 -31
  71. data/app/models/netzke_field_list.rb +0 -261
  72. data/app/models/netzke_model_attr_list.rb +0 -21
  73. data/app/models/netzke_persistent_array_auto_model.rb +0 -57
  74. data/test/basepack_test_app/spec/active_record/relation_extensions_spec.rb +0 -44
@@ -0,0 +1,260 @@
1
+ module Netzke::Basepack::DataAdapters
2
+ class SequelAdapter < AbstractAdapter
3
+ def self.for_class?(model_class)
4
+ model_class <= Sequel::Model
5
+ end
6
+
7
+ def get_records(params, columns=[])
8
+ get_dataset(params, columns).all
9
+ end
10
+
11
+ def count_records(params, columns=[])
12
+ # dont pass columns, JOINs will be done as necessary for filters
13
+ get_dataset(params, [], true).count
14
+ end
15
+
16
+ def map_type type
17
+ type
18
+ end
19
+
20
+ def get_assoc_property_type assoc_name, prop_name
21
+ db_schema=class_for(assoc_name.to_sym).db_schema
22
+ # return nil if prop_name not present in db schema (virtual column)
23
+ db_schema[prop_name.to_sym] ? db_schema[prop_name.to_sym][:type] : nil
24
+ end
25
+
26
+ # like get_assoc_property_type but for non-association columns
27
+ def get_property_type column
28
+ column[:type]
29
+ end
30
+
31
+ def column_virtual? c
32
+ assoc, method = c[:name].split '__'
33
+ if method
34
+ !class_for(assoc.to_sym).columns.include? method.to_sym
35
+ else
36
+ !@model_class.columns.include? assoc.to_sym
37
+ end
38
+ end
39
+
40
+ # Returns options for comboboxes in grids/forms
41
+ def combobox_options_for_column(column, method_options = {})
42
+ query = method_options[:query]
43
+
44
+ # First, check if we have options for this column defined in persistent storage
45
+ options = column[:combobox_options] && column[:combobox_options].split("\n")
46
+ if options
47
+ query ? options.select{ |o| o.index(/^#{query}/) }.map{ |el| [el] } : options
48
+ else
49
+ assoc_name, assoc_method = column[:name].split '__'
50
+
51
+ if assoc_name
52
+ # Options for an asssociation attribute
53
+ dataset = class_for(assoc_name)
54
+
55
+ dataset = dataset.extend_with(method_options[:scope]) if method_options[:scope]
56
+
57
+ if class_for(assoc_name).column_names.include?(assoc_method)
58
+ # apply query
59
+ dataset = dataset.where(assoc_method.to_sym.like("%#{query}%")) if query.present?
60
+ dataset.all.map{ |r| [r.id, r.send(assoc_method)] }
61
+ else
62
+ dataset.all.map{ |r| [r.id, r.send(assoc_method)] }.select{ |id,value| value =~ /^#{query}/ }
63
+ end
64
+ else
65
+ # Options for a non-association attribute
66
+ res=@model_class.netzke_combo_options_for(column[:name], method_options)
67
+
68
+ # ensure it is an array-in-array, as Ext will fail otherwise
69
+ raise RuntimeError, "netzke_combo_options_for should return an Array" unless res.kind_of? Array
70
+ return [[]] if res.empty?
71
+
72
+ unless res.first.kind_of? Array
73
+ res=res.map do |v|
74
+ [v]
75
+ end
76
+ end
77
+ return res
78
+ end
79
+ end
80
+ end
81
+
82
+ def foreign_key_for assoc_name
83
+ @model_class.association_reflection(assoc_name.to_sym)[:key].to_s
84
+ end
85
+
86
+ # Returns the model class for an association
87
+ def class_for assoc_name
88
+ @model_class.association_reflection(assoc_name.to_sym)[:class_name].constantize
89
+ end
90
+
91
+ def destroy(ids)
92
+ @model_class.where(:id => ids).destroy
93
+ end
94
+
95
+ def find_record(id)
96
+ @model_class[id]
97
+ end
98
+
99
+ # Build a hash of foreign keys and the associated model
100
+ def hash_fk_model
101
+ @model_class.all_association_reflections.inject({}) do |res, assoc|
102
+ res[assoc[:key]] = assoc[:class_name].constantize.model_name.underscore.to_sym
103
+ res
104
+ end
105
+ end
106
+
107
+ # TODO: is this possible with Sequel?
108
+ def move_records(params)
109
+ end
110
+
111
+ # give the data adapter the opportunity the set special options for
112
+ # saving
113
+ def save_record(record)
114
+ # don't raise an error on saving. basepack will evaluate record.errors
115
+ # to get validation errors
116
+ record.raise_on_save_failure = false
117
+ record.save
118
+ end
119
+
120
+ # give the data adapter the opporunity to process error messages
121
+ # must return an raay of the form ["Title can't be blank", "Foo can't be blank"]
122
+ def errors_array(record)
123
+ record.errors.to_a.inject([]) do |errors, error|
124
+ field, message = error
125
+ errors << "#{record.class.human_attribute_name(field)} #{message.join ', '}"
126
+ errors
127
+ end
128
+ end
129
+
130
+ # Needed for seed and tests
131
+ def last
132
+ @model_class.last
133
+ end
134
+
135
+ # Needed for seed and tests
136
+ def destroy_all
137
+ @model_class.destroy
138
+ end
139
+
140
+ private
141
+ def get_dataset params, columns, for_count=false
142
+ dataset = @model_class
143
+
144
+ graphed=[]
145
+
146
+ # Parses and applies grid column filters
147
+ #
148
+ # Example column grid data:
149
+ #
150
+ # {"0" => {
151
+ # "data" => {
152
+ # "type" => "numeric",
153
+ # "comparison" => "gt",
154
+ # "value" => 10 },
155
+ # "field" => "id"
156
+ # },
157
+ # "1" => {
158
+ # "data" => {
159
+ # "type" => "string",
160
+ # "value" => "pizza"
161
+ # },
162
+ # "field" => "food_name"
163
+ # }}
164
+ #
165
+
166
+ if params[:filter]
167
+ # these are still JSON-encoded due to the migration to Ext.direct
168
+ column_filter=JSON.parse(params[:filter])
169
+
170
+ column_filter.each do |v|
171
+ field = v["field"]
172
+ assoc, method = field.split('__')
173
+ if method
174
+ # when filtering on association's columns, we need to graph for LEFT OUTER JOIN
175
+ dataset = dataset.eager_graph assoc.to_sym unless graphed.include? assoc.to_sym
176
+ graphed << assoc.to_sym
177
+ end
178
+
179
+ value = v["value"]
180
+ type = v["type"]
181
+ op = v["comparison"]
182
+
183
+ if type == "string"
184
+ # strings are always LIKEd (case-insensitive)
185
+ dataset = dataset.filter field.to_sym.ilike("%#{value}%")
186
+ else
187
+ if type == "date"
188
+ # convert value to the DB date
189
+ value.match /(\d\d)\/(\d\d)\/(\d\d\d\d)/
190
+ value = "#{$3}-#{$1}-#{$2}"
191
+ end
192
+ # if it's NOT an association column, we need to qualify column name with model's table_name
193
+ qualified_column_name = method ? field.to_sym : field.to_sym.qualify(@model_class.table_name)
194
+ case op
195
+ when 'lt'
196
+ dataset = dataset.filter ":column < '#{value}'", :column => qualified_column_name
197
+ when 'gt'
198
+ dataset = dataset.filter ":column > '#{value}'", :column => qualified_column_name
199
+ else
200
+ dataset = dataset.filter qualified_column_name => value
201
+ end
202
+ end
203
+ end
204
+ end
205
+ # skip sorting, eager joining and paging if dataset is used for count
206
+ unless for_count
207
+ if params[:sort] && sort_params = params[:sort]
208
+ sort_params.each do |sort_param|
209
+ assoc, method = sort_param["property"].split("__")
210
+ dir = sort_param["direction"].downcase
211
+
212
+ # if a sorting scope is set, call the scope with the given direction
213
+ column = columns.detect { |c| c[:name] == sort_param["property"] }
214
+ if column.try(:'has_key?', :sorting_scope)
215
+ dataset = dataset.send(column[:sorting_scope].to_sym, dir.to_sym)
216
+ else
217
+ if method # sorting on associations column
218
+ # graph the association for LEFT OUTER JOIN
219
+ dataset = dataset.eager_graph(assoc.to_sym) unless graphed.include? assoc.to_sym
220
+ graphed << assoc.to_sym
221
+ end
222
+ # coincidentally, netzkes convention of specifying association's attributes
223
+ # i.e. "author__name" on Book matches sequel's convention
224
+ # so we can just pass symbolized property here
225
+ dataset = dataset.order(sort_param["property"].to_sym.send(dir))
226
+ end
227
+ end
228
+ end
229
+
230
+ # eager load the associations indicated by columns,
231
+ # but only if we didn't eager_graph them before (for ordering/filtering)
232
+ # because this saves a ID IN query
233
+ columns.each do |column|
234
+ if column[:name].index('__')
235
+ assoc, _ = column[:name].split('__')
236
+ dataset = dataset.eager(assoc.to_sym) unless graphed.include? assoc.to_sym
237
+ end
238
+ end
239
+
240
+ # apply paging
241
+ if params[:limit]
242
+ if params[:start]
243
+ dataset = dataset.limit params[:limit], params[:start]
244
+ else
245
+ dataset = dataset.limit params[:limit]
246
+ end
247
+ end
248
+ end
249
+
250
+ # apply scope
251
+ # need to symbolize_keys, because when the request is made from client-side (as opposed
252
+ # to server-side on inital render), the scope's keys are given as string {"author_id" => 1}
253
+ # If we give Sequel a filter like this, it will (correctly) do WHERE 'author_id' = 1 - note the quotes
254
+ # making the database match the string author_id to 1 and to the column.
255
+ dataset = dataset.extend_with(params[:scope].symbolize_keys) if params[:scope]
256
+ dataset
257
+ end
258
+ end
259
+
260
+ end
@@ -99,18 +99,18 @@ module Netzke
99
99
 
100
100
  def js_config
101
101
  super.tap do |res|
102
- res[:pri] = data_class && data_class.primary_key
102
+ res[:pri] = data_class && data_class.primary_key.to_s
103
103
  res[:record] = js_record_data if record
104
104
  end
105
105
  end
106
106
 
107
107
  # A hash of record data including the meta field
108
108
  def js_record_data
109
- record.to_hash(fields).merge(:_meta => meta_field).literalize_keys
109
+ record.netzke_hash(fields).merge(:_meta => meta_field).literalize_keys
110
110
  end
111
111
 
112
112
  def record
113
- @record ||= config[:record] || config[:record_id] && data_class && data_class.where(data_class.primary_key => config[:record_id]).first
113
+ @record ||= config[:record] || config[:record_id] && data_class && data_adapter.find_record(config[:record_id])
114
114
  end
115
115
 
116
116
  private
@@ -10,12 +10,11 @@ module Netzke
10
10
  @form_panel_items ||= begin
11
11
  res = normalize_fields(super || data_class && data_class.netzke_attributes || []) # netzke_attributes as default items
12
12
  # if primary key isn't there, insert it as first
13
- if data_class && !res.detect{ |f| f[:name] == data_class.primary_key}
13
+ if data_class && !res.detect{ |f| f[:name] == data_class.primary_key.to_s}
14
14
  primary_key_item = normalize_field(data_class.primary_key.to_sym)
15
15
  @fields_from_config[data_class.primary_key.to_sym] = primary_key_item
16
16
  res.insert(0, primary_key_item)
17
17
  end
18
-
19
18
  res
20
19
  end
21
20
  end
@@ -121,14 +120,11 @@ module Netzke
121
120
  def detect_association_with_method(c)
122
121
  if c[:name].index('__')
123
122
  assoc_name, method = c[:name].split('__').map(&:to_sym)
124
- if method && assoc = data_class.reflect_on_association(assoc_name)
125
- assoc_column = assoc.klass.columns_hash[method.to_s]
126
- assoc_method_type = assoc_column.try(:type)
127
- if c[:nested_attribute]
128
- c[:xtype] ||= xtype_for_attr_type(assoc_method_type)
129
- else
130
- c[:xtype] ||= assoc_method_type == :boolean ? xtype_for_attr_type(assoc_method_type) : xtype_for_association
131
- end
123
+ assoc_method_type = data_adapter.get_assoc_property_type(assoc_name, method)
124
+ if c[:nested_attribute]
125
+ c[:xtype] ||= xtype_for_attr_type(assoc_method_type)
126
+ else
127
+ c[:xtype] ||= assoc_method_type == :boolean ? xtype_for_attr_type(assoc_method_type) : xtype_for_association
132
128
  end
133
129
  end
134
130
  end
@@ -172,6 +172,7 @@
172
172
  }, this);
173
173
  }
174
174
  }
175
+ this.fireEvent('afterApply', this);
175
176
  },
176
177
 
177
178
  setFormValues: function(values){
@@ -18,7 +18,7 @@ module Netzke
18
18
  #
19
19
  # someForm.netzkeLoad({id: 100});
20
20
  endpoint :netzke_load do |params|
21
- @record = data_class && data_class.find_by_id(params[:id])
21
+ @record = data_class && data_adapter.find_record(params[:id])
22
22
  {:set_form_values => js_record_data}
23
23
  end
24
24
 
@@ -42,16 +42,19 @@ module Netzke
42
42
 
43
43
  # Returns array of form values according to the configured columns
44
44
  # def array_of_values
45
- # @record && @record.to_array(fields)
45
+ # @record && @record.netzke_array(fields)
46
46
  # end
47
47
 
48
48
  def values
49
- record && record.to_hash(fields)
49
+ record && record.netzke_hash(fields)
50
50
  end
51
51
 
52
52
  # Implementation for the "netzke_submit" endpoint (for backward compatibility)
53
53
  def netzke_submit(params)
54
54
  data = ActiveSupport::JSON.decode(params[:data])
55
+ data.each_pair do |k,v|
56
+ data[k]=nil if v.blank? || v == "null" # Ext JS returns "null" on empty date fields, or "" for not filled optional integer fields, which gives errors when passed to model (at least in DataMapper)
57
+ end
55
58
 
56
59
  # File uploads are in raw params instead of "data" hash, so, mix them in into "data"
57
60
  if config[:file_upload]
@@ -66,8 +69,8 @@ module Netzke
66
69
  {:set_form_values => js_record_data, :set_result => true}
67
70
  else
68
71
  # flash eventual errors
69
- @record.errors.to_a.each do |msg|
70
- flash :error => msg
72
+ data_adapter.errors_array(@record).each do |error|
73
+ flash :error => error
71
74
  end
72
75
  {:netzke_feedback => @flash, :apply_form_errors => build_form_errors(record)}
73
76
  end
@@ -78,14 +81,10 @@ module Netzke
78
81
  # Builds the form errors
79
82
  def build_form_errors(record)
80
83
  form_errors = {}
81
- foreign_keys = {}
82
-
83
- # Build a hash of foreign keys and the associated model
84
- data_class.reflect_on_all_associations(:belongs_to).map{ |r|
85
- foreign_keys[r.association_foreign_key.to_sym] = r.name
86
- }
87
-
88
- record.errors.map{|field, error|
84
+ foreign_keys = data_adapter.hash_fk_model
85
+ record.errors.to_hash.map{|field, error|
86
+ # some ORM return an array for error
87
+ error = error.join ', ' if error.kind_of? Array
89
88
  # Get the correct field name for the errors on foreign keys
90
89
  if foreign_keys.has_key?(field)
91
90
  fields.each do |k, v|
@@ -93,7 +92,6 @@ module Netzke
93
92
  field = k.to_s.gsub('__', '____') if k.to_s.split('__').first == foreign_keys[field].to_s
94
93
  end
95
94
  end
96
-
97
95
  form_errors[field] ||= []
98
96
  form_errors[field] << error
99
97
  }
@@ -103,7 +101,8 @@ module Netzke
103
101
  # Creates/updates a record from hash
104
102
  def create_or_update_record(hsh)
105
103
  hsh.merge!(config[:strong_default_attrs]) if config[:strong_default_attrs]
106
- @record ||= data_class.find(:first, :conditions => {data_class.primary_key => hsh.delete(data_class.primary_key)}) # only pick up the record specified in the params if it was not provided in the configuration
104
+ @record ||= data_adapter.find_record hsh.delete(data_class.primary_key.to_s) # only pick up the record specified in the params if it was not provided in the configuration
105
+ #data_class.find(:first, :conditions => {data_class.primary_key => hsh.delete(data_class.primary_key)})
107
106
  success = true
108
107
 
109
108
  @record = data_class.new if @record.nil?
@@ -123,7 +122,7 @@ module Netzke
123
122
  #end
124
123
 
125
124
  # did we have complete success?
126
- success && @record.save
125
+ success && data_adapter.save_record(@record)
127
126
  end
128
127
 
129
128
  # API handling form load
@@ -126,6 +126,11 @@ module Netzke
126
126
  class GridPanel < Netzke::Base
127
127
  js_base_class "Ext.grid.Panel"
128
128
 
129
+ class_attribute :columns_attr
130
+
131
+ class_attribute :overridden_columns_attr
132
+ self.overridden_columns_attr = {}
133
+
129
134
  # Class-level configuration. These options directly influence the amount of generated
130
135
  # javascript code for this component's class. For example, if you don't want filters for the grid,
131
136
  # set column_filters_available to false, and the javascript for the filters won't be included at all.
@@ -198,15 +203,14 @@ module Netzke
198
203
  base.class_eval do
199
204
  class << self
200
205
  def column(name, config = {})
201
- columns = self.read_inheritable_attribute(:columns) || []
202
- columns << config.merge(:name => name.to_s)
203
- self.write_inheritable_attribute(:columns, columns)
206
+ columns = self.columns_attr || []
207
+ columns |= [config.merge(:name => name.to_s)]
208
+ self.columns_attr = columns
204
209
  end
205
210
 
206
211
  def override_column(name, config)
207
- columns = self.read_inheritable_attribute(:overridden_columns) || {}
208
- columns.merge!(name.to_sym => config)
209
- self.write_inheritable_attribute(:overridden_columns, columns)
212
+ columns = self.overridden_columns_attr.dup
213
+ self.overridden_columns_attr = columns.merge(name.to_sym => config)
210
214
  end
211
215
  end
212
216
  end
@@ -214,10 +218,10 @@ module Netzke
214
218
 
215
219
  def configuration
216
220
  super.tap do |c|
217
- c[:columns] ||= self.class.read_inheritable_attribute(:columns)
221
+ c[:columns] ||= self.columns_attr
218
222
 
219
223
  # user-passed :override_columns option should get deep_merged with the defaults
220
- c[:override_columns] = (self.class.read_inheritable_attribute(:overridden_columns) || {}).deep_merge(c[:override_columns] || {})
224
+ c[:override_columns] = self.overridden_columns_attr.deep_merge(c[:override_columns] || {})
221
225
  end
222
226
  end
223
227
 
@@ -243,8 +247,10 @@ module Netzke
243
247
 
244
248
  def get_default_association_values #:nodoc:
245
249
  columns.select{ |c| c[:name].index("__") && c[:default_value] }.each.inject({}) do |r,c|
246
- assoc, assoc_method = assoc_and_assoc_method_for_attr(c)
247
- assoc_instance = assoc.klass.find(c[:default_value])
250
+ assoc_name, assoc_method = c[:name].split '__'
251
+ assoc_class = data_adapter.class_for(assoc_name)
252
+ assoc_data_adapter = Netzke::Basepack::DataAdapters::AbstractAdapter.adapter_class(assoc_class).new(assoc_class)
253
+ assoc_instance = assoc_data_adapter.find_record c[:default_value]
248
254
  r.merge(c[:name] => assoc_instance.send(assoc_method))
249
255
  end
250
256
  end