active_scaffold 3.1.4 → 3.1.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.
- data/lib/active_scaffold/actions/nested.rb +6 -2
- data/lib/active_scaffold/actions/nested.rb~ +7 -5
- data/lib/active_scaffold/actions/update.rb~ +2 -3
- data/lib/active_scaffold/bridges/shared/date_bridge.rb~ +209 -0
- data/lib/active_scaffold/config/nested.rb +1 -0
- data/lib/active_scaffold/config/nested.rb~ +41 -0
- data/lib/active_scaffold/constraints.rb~ +186 -0
- data/lib/active_scaffold/data_structures/action_link.rb +4 -4
- data/lib/active_scaffold/data_structures/action_link.rb~ +179 -0
- data/lib/active_scaffold/data_structures/nested_info.rb~ +124 -0
- data/lib/active_scaffold/extensions/unsaved_associated.rb~ +62 -0
- data/lib/active_scaffold/finder.rb +9 -2
- data/lib/active_scaffold/finder.rb~ +11 -3
- data/lib/active_scaffold/helpers/list_column_helpers.rb +9 -6
- data/lib/active_scaffold/helpers/list_column_helpers.rb~ +9 -6
- data/lib/active_scaffold/helpers/view_helpers.rb +1 -1
- data/lib/active_scaffold/helpers/view_helpers.rb~ +3 -3
- data/lib/active_scaffold/version.rb +1 -1
- data/lib/active_scaffold.rb +1 -1
- data/lib/active_scaffold.rb~ +362 -0
- metadata +17 -10
@@ -82,8 +82,12 @@ module ActiveScaffold::Actions
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def beginning_of_chain
|
85
|
-
if nested? && nested.association && nested.association.
|
86
|
-
nested.
|
85
|
+
if nested? && nested.association && !nested.association.belongs_to?
|
86
|
+
if nested.association.collection?
|
87
|
+
nested.parent_scope.send(nested.association.name)
|
88
|
+
elsif nested.child_association.belongs_to?
|
89
|
+
active_scaffold_config.model.where(nested.child_association.foreign_key => nested.parent_scope)
|
90
|
+
end
|
87
91
|
elsif nested? && nested.scope
|
88
92
|
nested.parent_scope.send(nested.scope)
|
89
93
|
else
|
@@ -82,8 +82,13 @@ module ActiveScaffold::Actions
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def beginning_of_chain
|
85
|
-
if nested? && nested.association && nested.association.
|
86
|
-
|
85
|
+
if nested? && nested.association && !nested.association.belongs_to?
|
86
|
+
debugger
|
87
|
+
if nested.association.collection?
|
88
|
+
nested.parent_scope.send(nested.association.name)
|
89
|
+
elsif nested.child_association.belongs_to?
|
90
|
+
active_scaffold_config.model.where(nested.child_association.foreign_key => nested.parent_scope)
|
91
|
+
end
|
87
92
|
elsif nested? && nested.scope
|
88
93
|
nested.parent_scope.send(nested.scope)
|
89
94
|
else
|
@@ -123,9 +128,6 @@ module ActiveScaffold::Actions::Nested
|
|
123
128
|
|
124
129
|
def self.included(base)
|
125
130
|
super
|
126
|
-
base.verify :method => :post,
|
127
|
-
:only => :add_existing,
|
128
|
-
:redirect_to => { :action => :index }
|
129
131
|
end
|
130
132
|
|
131
133
|
def new_existing
|
@@ -32,8 +32,7 @@ module ActiveScaffold::Actions
|
|
32
32
|
def edit_respond_to_js
|
33
33
|
render(:partial => 'update_form')
|
34
34
|
end
|
35
|
-
def update_respond_to_html
|
36
|
-
debugger
|
35
|
+
def update_respond_to_html
|
37
36
|
if params[:iframe]=='true' # was this an iframe post ?
|
38
37
|
responds_to_parent do
|
39
38
|
render :action => 'on_update.js', :layout => false
|
@@ -48,7 +47,6 @@ module ActiveScaffold::Actions
|
|
48
47
|
end
|
49
48
|
end
|
50
49
|
def update_respond_to_js
|
51
|
-
debugger
|
52
50
|
if successful? && update_refresh_list? && !render_parent?
|
53
51
|
do_search if respond_to? :do_search
|
54
52
|
do_list
|
@@ -84,6 +82,7 @@ module ActiveScaffold::Actions
|
|
84
82
|
@record = update_record_from_params(@record, active_scaffold_config.update.columns, params[:record]) unless options[:no_record_param_update]
|
85
83
|
before_update_save(@record)
|
86
84
|
self.successful = [@record.valid?, @record.associated_valid?].all? {|v| v == true} # this syntax avoids a short-circuit
|
85
|
+
debugger
|
87
86
|
if successful?
|
88
87
|
@record.save! and @record.save_associated!
|
89
88
|
after_update_save(@record)
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module ActiveScaffold
|
2
|
+
module Bridges
|
3
|
+
module Shared
|
4
|
+
module DateBridge
|
5
|
+
module SearchColumnHelpers
|
6
|
+
def active_scaffold_search_date_bridge(column, options)
|
7
|
+
current_search = {'from' => nil, 'to' => nil, 'opt' => 'BETWEEN',
|
8
|
+
'number' => 1, 'unit' => 'DAYS', 'range' => nil}
|
9
|
+
current_search.merge!(options[:value]) unless options[:value].nil?
|
10
|
+
tags = []
|
11
|
+
tags << active_scaffold_search_date_bridge_comparator_tag(column, options, current_search)
|
12
|
+
tags << active_scaffold_search_date_bridge_trend_tag(column, options, current_search)
|
13
|
+
tags << active_scaffold_search_date_bridge_numeric_tag(column, options, current_search)
|
14
|
+
tags << active_scaffold_search_date_bridge_range_tag(column, options, current_search)
|
15
|
+
tags.join(" ").html_safe
|
16
|
+
end
|
17
|
+
|
18
|
+
def active_scaffold_search_date_bridge_comparator_options(column)
|
19
|
+
select_options = ActiveScaffold::Finder::DateComparators.collect {|comp| [as_(comp.downcase.to_sym), comp]}
|
20
|
+
select_options + ActiveScaffold::Finder::NumericComparators.collect {|comp| [as_(comp.downcase.to_sym), comp]}
|
21
|
+
end
|
22
|
+
|
23
|
+
def active_scaffold_search_date_bridge_comparator_tag(column, options, current_search)
|
24
|
+
select_tag("#{options[:name]}[opt]", options_for_select(active_scaffold_search_date_bridge_comparator_options(column),current_search['opt']), :id => "#{options[:id]}_opt", :class => "as_search_range_option as_search_date_time_option")
|
25
|
+
end
|
26
|
+
|
27
|
+
def active_scaffold_search_date_bridge_numeric_tag(column, options, current_search)
|
28
|
+
numeric_controls = "" <<
|
29
|
+
active_scaffold_search_date_bridge_calendar_control(column, options, current_search, 'from') <<
|
30
|
+
content_tag(:span, (" - " + active_scaffold_search_date_bridge_calendar_control(column, options, current_search, 'to')).html_safe,
|
31
|
+
:id => "#{options[:id]}_between", :class => "as_search_range_between", :style => "display:#{current_search['opt'] == 'BETWEEN' ? '' : 'none'}")
|
32
|
+
content_tag("span", numeric_controls.html_safe, :id => "#{options[:id]}_numeric", :style => "display:#{ActiveScaffold::Finder::NumericComparators.include?(current_search['opt']) ? '' : 'none'}")
|
33
|
+
end
|
34
|
+
|
35
|
+
def active_scaffold_search_date_bridge_trend_tag(column, options, current_search)
|
36
|
+
active_scaffold_date_bridge_trend_tag(column, options,
|
37
|
+
{:name_prefix => 'search',
|
38
|
+
:number_value => current_search['number'],
|
39
|
+
:unit_value => current_search["unit"],
|
40
|
+
:show => (current_search['opt'] == 'PAST' || current_search['opt'] == 'FUTURE')})
|
41
|
+
end
|
42
|
+
|
43
|
+
def active_scaffold_date_bridge_trend_tag(column, options, trend_options)
|
44
|
+
trend_controls = text_field_tag("#{trend_options[:name_prefix]}[#{column.name}][number]", trend_options[:number_value], :class => 'text-input', :size => 10, :autocomplete => 'off') << " " <<
|
45
|
+
select_tag("#{trend_options[:name_prefix]}[#{column.name}][unit]",
|
46
|
+
options_for_select(active_scaffold_search_date_bridge_trend_units(column), trend_options[:unit_value]),
|
47
|
+
:class => 'text-input')
|
48
|
+
content_tag("span", trend_controls.html_safe, :id => "#{options[:id]}_trend", :style => "display:#{trend_options[:show] ? '' : 'none'}")
|
49
|
+
end
|
50
|
+
|
51
|
+
def active_scaffold_search_date_bridge_trend_units(column)
|
52
|
+
options = ActiveScaffold::Finder::DateUnits.collect{|unit| [as_(unit.downcase.to_sym), unit]}
|
53
|
+
options = ActiveScaffold::Finder::TimeUnits.collect{|unit| [as_(unit.downcase.to_sym), unit]} + options if column_datetime?(column)
|
54
|
+
options
|
55
|
+
end
|
56
|
+
|
57
|
+
def active_scaffold_search_date_bridge_range_tag(column, options, current_search)
|
58
|
+
range_controls = select_tag("search[#{column.name}][range]",
|
59
|
+
options_for_select( ActiveScaffold::Finder::DateRanges.collect{|range| [as_(range.downcase.to_sym), range]}, current_search["range"]),
|
60
|
+
:class => 'text-input')
|
61
|
+
content_tag("span", range_controls.html_safe, :id => "#{options[:id]}_range", :style => "display:#{(current_search['opt'] == 'RANGE') ? '' : 'none'}")
|
62
|
+
end
|
63
|
+
|
64
|
+
def column_datetime?(column)
|
65
|
+
(!column.column.nil? && [:datetime, :time].include?(column.column.type))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module HumanConditionHelpers
|
70
|
+
def active_scaffold_human_condition_date_bridge(column, value)
|
71
|
+
case value[:opt]
|
72
|
+
when 'RANGE'
|
73
|
+
range_type, range = value[:range].downcase.split('_')
|
74
|
+
format = active_scaffold_human_condition_date_bridge_range_format(range_type, range)
|
75
|
+
from, to = controller.class.date_bridge_from_to(column, value)
|
76
|
+
"#{column.active_record_class.human_attribute_name(column.name)} = #{as_(value[:range].downcase).downcase} (#{I18n.l(from, :format => format)})"
|
77
|
+
when 'PAST', 'FUTURE'
|
78
|
+
from, to = controller.class.date_bridge_from_to(column, value)
|
79
|
+
"#{column.active_record_class.human_attribute_name(column.name)} #{as_('BETWEEN'.downcase).downcase} #{I18n.l(from)} - #{I18n.l(to)}"
|
80
|
+
else
|
81
|
+
from, to = controller.class.date_bridge_from_to(column, value)
|
82
|
+
"#{column.active_record_class.human_attribute_name(column.name)} #{as_(value[:opt].downcase).downcase} #{I18n.l(from)} #{value[:opt] == 'BETWEEN' ? '- ' + I18n.l(to) : ''}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def active_scaffold_human_condition_date_bridge_range_format(range_type, range)
|
87
|
+
case range
|
88
|
+
when 'week'
|
89
|
+
first_day_of_week = I18n.translate 'active_scaffold.date_picker_options.firstDay'
|
90
|
+
if first_day_of_week == 1
|
91
|
+
'%W %Y'
|
92
|
+
else
|
93
|
+
'%U %Y'
|
94
|
+
end
|
95
|
+
when 'month'
|
96
|
+
'%b %Y'
|
97
|
+
when 'year'
|
98
|
+
'%Y'
|
99
|
+
else
|
100
|
+
I18n.translate 'date.formats.default'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
module Finder
|
106
|
+
module ClassMethods
|
107
|
+
def condition_for_date_bridge_type(column, value, like_pattern)
|
108
|
+
operator = ActiveScaffold::Finder::NumericComparators.include?(value[:opt]) && value[:opt] != 'BETWEEN' ? value[:opt] : nil
|
109
|
+
from_value, to_value = date_bridge_from_to(column, value)
|
110
|
+
|
111
|
+
if column.search_sql.is_a? Proc
|
112
|
+
column.search_sql.call(from_value, to_value, operator)
|
113
|
+
else
|
114
|
+
unless operator.nil?
|
115
|
+
["#{column.search_sql} #{value[:opt]} ?", from_value.to_s(:db)] unless from_value.nil?
|
116
|
+
else
|
117
|
+
["#{column.search_sql} BETWEEN ? AND ?", from_value.to_s(:db), to_value.to_s(:db)] unless from_value.nil? && to_value.nil?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def date_bridge_from_to(column, value)
|
123
|
+
conversion = column.column.type == :date ? :to_date : :to_time
|
124
|
+
case value[:opt]
|
125
|
+
when 'RANGE'
|
126
|
+
date_bridge_from_to_for_range(column, value).collect(&conversion)
|
127
|
+
when 'PAST', 'FUTURE'
|
128
|
+
date_bridge_from_to_for_trend(column, value).collect(&conversion)
|
129
|
+
else
|
130
|
+
['from', 'to'].collect { |field| condition_value_for_datetime(value[field], conversion)}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def date_bridge_now
|
135
|
+
Time.zone.now
|
136
|
+
end
|
137
|
+
|
138
|
+
def date_bridge_from_to_for_trend(column, value)
|
139
|
+
case value['opt']
|
140
|
+
when "PAST"
|
141
|
+
trend_number = [value['number'].to_i, 1].max
|
142
|
+
now = date_bridge_now
|
143
|
+
if date_bridge_column_date?(column)
|
144
|
+
from = now.beginning_of_day.ago((trend_number).send(value['unit'].downcase.singularize.to_sym))
|
145
|
+
to = now.end_of_day
|
146
|
+
else
|
147
|
+
from = now.ago((trend_number).send(value['unit'].downcase.singularize.to_sym))
|
148
|
+
to = now
|
149
|
+
end
|
150
|
+
return from, to
|
151
|
+
when "FUTURE"
|
152
|
+
trend_number = [value['number'].to_i, 1].max
|
153
|
+
now = date_bridge_now
|
154
|
+
if date_bridge_column_date?(column)
|
155
|
+
from = now.beginning_of_day
|
156
|
+
to = now.end_of_day.in((trend_number).send(value['unit'].downcase.singularize.to_sym))
|
157
|
+
else
|
158
|
+
from = now
|
159
|
+
to = now.in((trend_number).send(value['unit'].downcase.singularize.to_sym))
|
160
|
+
end
|
161
|
+
return from, to
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def date_bridge_from_to_for_range(column, value)
|
166
|
+
case value[:range]
|
167
|
+
when 'TODAY'
|
168
|
+
return date_bridge_now.beginning_of_day, date_bridge_now.end_of_day
|
169
|
+
when 'YESTERDAY'
|
170
|
+
return date_bridge_now.ago(1.day).beginning_of_day, date_bridge_now.ago(1.day).end_of_day
|
171
|
+
when 'TOMORROW'
|
172
|
+
return date_bridge_now.in(1.day).beginning_of_day, date_bridge_now.in(1.day).end_of_day
|
173
|
+
else
|
174
|
+
range_type, range = value[:range].downcase.split('_')
|
175
|
+
raise ArgumentError unless ['week', 'month', 'year'].include?(range)
|
176
|
+
case range_type
|
177
|
+
when 'this'
|
178
|
+
return date_bridge_now.send("beginning_of_#{range}".to_sym), date_bridge_now.send("end_of_#{range}")
|
179
|
+
when 'prev'
|
180
|
+
return date_bridge_now.ago(1.send(range.to_sym)).send("beginning_of_#{range}".to_sym), date_bridge_now.ago(1.send(range.to_sym)).send("end_of_#{range}".to_sym)
|
181
|
+
when 'next'
|
182
|
+
return date_bridge_now.in(1.send(range.to_sym)).send("beginning_of_#{range}".to_sym), date_bridge_now.in(1.send(range.to_sym)).send("end_of_#{range}".to_sym)
|
183
|
+
else
|
184
|
+
return nil, nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def date_bridge_column_date?(column)
|
190
|
+
if [:date_picker, :datetime_picker].include? column.form_ui
|
191
|
+
column.form_ui == :date_picker
|
192
|
+
else
|
193
|
+
(!column.column.nil? && [:date].include?(column.column.type))
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
ActiveScaffold::Finder.const_set('DateComparators', ["PAST", "FUTURE", "RANGE"])
|
204
|
+
ActiveScaffold::Finder.const_set('DateUnits', ["DAYS", "WEEKS", "MONTHS", "YEARS"])
|
205
|
+
ActiveScaffold::Finder.const_set('TimeUnits', ["SECONDS", "MINUTES", "HOURS"])
|
206
|
+
ActiveScaffold::Finder.const_set('DateRanges', ["TODAY", "YESTERDAY", "TOMORROW",
|
207
|
+
"THIS_WEEK", "PREV_WEEK", "NEXT_WEEK",
|
208
|
+
"THIS_MONTH", "PREV_MONTH", "NEXT_MONTH",
|
209
|
+
"THIS_YEAR", "PREV_YEAR", "NEXT_YEAR"])
|
@@ -24,6 +24,7 @@ module ActiveScaffold::Config
|
|
24
24
|
unless column.nil? || column.association.nil?
|
25
25
|
options.reverse_merge! :security_method => :nested_authorized?, :label => column.association.klass.model_name.human({:count => 2, :default => column.association.klass.name.pluralize})
|
26
26
|
action_link = @core.link_for_association(column, options)
|
27
|
+
action_link.action ||= :index
|
27
28
|
@core.action_links.add_to_group(action_link, action_group) unless action_link.nil?
|
28
29
|
else
|
29
30
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveScaffold::Config
|
2
|
+
class Nested < Base
|
3
|
+
self.crud_type = :read
|
4
|
+
|
5
|
+
def initialize(core_config)
|
6
|
+
super
|
7
|
+
@label = :add_existing_model
|
8
|
+
self.shallow_delete = self.class.shallow_delete
|
9
|
+
@action_group = self.class.action_group.clone if self.class.action_group
|
10
|
+
end
|
11
|
+
|
12
|
+
# global level configuration
|
13
|
+
# --------------------------
|
14
|
+
cattr_accessor :shallow_delete
|
15
|
+
@@shallow_delete = true
|
16
|
+
|
17
|
+
# instance-level configuration
|
18
|
+
# ----------------------------
|
19
|
+
attr_accessor :shallow_delete
|
20
|
+
|
21
|
+
# Add a nested ActionLink
|
22
|
+
def add_link(attribute, options = {})
|
23
|
+
column = @core.columns[attribute.to_sym]
|
24
|
+
unless column.nil? || column.association.nil?
|
25
|
+
options.reverse_merge! :security_method => :nested_authorized?, :label => column.association.klass.model_name.human({:count => 2, :default => column.association.klass.name.pluralize})
|
26
|
+
action_link = @core.link_for_association(column, options)
|
27
|
+
@core.action_links.add_to_group(action_link, action_group) unless action_link.nil?
|
28
|
+
else
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_scoped_link(named_scope, options = {})
|
34
|
+
action_link = @core.link_for_association_as_scope(named_scope.to_sym, options)
|
35
|
+
@core.action_links.add_to_group(action_link, action_group) unless action_link.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
# the label for this Nested action. used for the header.
|
39
|
+
attr_writer :label
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module ActiveScaffold
|
2
|
+
module Constraints
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
# Returns the current constraints
|
7
|
+
def active_scaffold_constraints
|
8
|
+
@active_scaffold_constraints ||= active_scaffold_session_storage[:constraints] || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_active_scaffold_constraints
|
12
|
+
associations_by_params = {}
|
13
|
+
active_scaffold_config.model.reflect_on_all_associations.each do |association|
|
14
|
+
associations_by_params[association.klass.name.foreign_key] = association.name unless association.options[:polymorphic]
|
15
|
+
end
|
16
|
+
params.each do |key, value|
|
17
|
+
active_scaffold_constraints[associations_by_params[key]] = value if associations_by_params.include? key
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# For each enabled action, adds the constrained columns to the ActionColumns object (if it exists).
|
22
|
+
# This lets the ActionColumns object skip constrained columns.
|
23
|
+
#
|
24
|
+
# If the constraint value is a Hash, then we assume the constraint is a multi-level association constraint (the reverse of a has_many :through) and we do NOT register the constraint column.
|
25
|
+
def register_constraints_with_action_columns(association_constrained_fields = [], exclude_actions = [])
|
26
|
+
debugger
|
27
|
+
constrained_fields = active_scaffold_constraints.reject{|k, v| v.is_a? Hash}.keys.collect{|k| k.to_sym}
|
28
|
+
constrained_fields = constrained_fields | association_constrained_fields
|
29
|
+
if self.class.uses_active_scaffold?
|
30
|
+
# we actually want to do this whether constrained_fields exist or not, so that we can reset the array when they don't
|
31
|
+
active_scaffold_config.actions.each do |action_name|
|
32
|
+
next if exclude_actions.include?(action_name)
|
33
|
+
action = active_scaffold_config.send(action_name)
|
34
|
+
next unless action.respond_to? :columns
|
35
|
+
action.columns.constraint_columns = constrained_fields
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns search conditions based on the current scaffold constraints.
|
41
|
+
#
|
42
|
+
# Supports constraints based on either a column name (in which case it checks for an association
|
43
|
+
# or just uses the search_sql) or a database field name.
|
44
|
+
#
|
45
|
+
# All of this work is primarily to support nested scaffolds in a manner generally useful for other
|
46
|
+
# embedded scaffolds.
|
47
|
+
def conditions_from_constraints
|
48
|
+
conditions = nil
|
49
|
+
debugger
|
50
|
+
active_scaffold_constraints.each do |k, v|
|
51
|
+
column = active_scaffold_config.columns[k]
|
52
|
+
constraint_condition = if column
|
53
|
+
# Assume this is a multi-level association constraint.
|
54
|
+
# example:
|
55
|
+
# data model: Park -> Den -> Bear
|
56
|
+
# constraint: :den => {:park => 5}
|
57
|
+
if v.is_a? Hash
|
58
|
+
far_association = column.association.klass.reflect_on_association(v.keys.first)
|
59
|
+
field = far_association.klass.primary_key
|
60
|
+
table = far_association.table_name
|
61
|
+
|
62
|
+
active_scaffold_includes.concat([{k => v.keys.first}]) # e.g. {:den => :park}
|
63
|
+
constraint_condition_for("#{table}.#{field}", v.values.first)
|
64
|
+
|
65
|
+
# association column constraint
|
66
|
+
elsif column.association
|
67
|
+
if column.association.macro == :has_and_belongs_to_many
|
68
|
+
active_scaffold_habtm_joins.concat column.includes
|
69
|
+
else
|
70
|
+
active_scaffold_includes.concat column.includes
|
71
|
+
end
|
72
|
+
condition_from_association_constraint(column.association, v)
|
73
|
+
|
74
|
+
# regular column constraints
|
75
|
+
elsif column.searchable?
|
76
|
+
active_scaffold_includes.concat column.includes
|
77
|
+
constraint_condition_for(column.search_sql, v)
|
78
|
+
end
|
79
|
+
# unknown-to-activescaffold-but-real-database-column constraint
|
80
|
+
elsif active_scaffold_config.model.column_names.include? k.to_s
|
81
|
+
constraint_condition_for(k.to_s, v)
|
82
|
+
else
|
83
|
+
raise ActiveScaffold::MalformedConstraint, constraint_error(active_scaffold_config.model, k), caller
|
84
|
+
end
|
85
|
+
|
86
|
+
conditions = merge_conditions(conditions, constraint_condition)
|
87
|
+
end
|
88
|
+
|
89
|
+
conditions
|
90
|
+
end
|
91
|
+
|
92
|
+
# We do NOT want to use .search_sql. If anything, search_sql will refer
|
93
|
+
# to a human-searchable value on the associated record.
|
94
|
+
def condition_from_association_constraint(association, value)
|
95
|
+
# when the reverse association is a :belongs_to, the id for the associated object only exists as
|
96
|
+
# the primary_key on the other table. so for :has_one and :has_many (when the reverse is :belongs_to),
|
97
|
+
# we have to use the other model's primary_key.
|
98
|
+
#
|
99
|
+
# please see the relevant tests for concrete examples.
|
100
|
+
field = if [:has_one, :has_many].include?(association.macro)
|
101
|
+
association.klass.primary_key
|
102
|
+
elsif [:has_and_belongs_to_many].include?(association.macro)
|
103
|
+
association.association_foreign_key
|
104
|
+
else
|
105
|
+
association.options[:foreign_key] || association.name.to_s.foreign_key
|
106
|
+
end
|
107
|
+
|
108
|
+
table = case association.macro
|
109
|
+
when :has_and_belongs_to_many
|
110
|
+
association.options[:join_table]
|
111
|
+
|
112
|
+
when :belongs_to
|
113
|
+
active_scaffold_config.model.table_name
|
114
|
+
|
115
|
+
else
|
116
|
+
association.table_name
|
117
|
+
end
|
118
|
+
|
119
|
+
if association.options[:primary_key]
|
120
|
+
value = association.klass.find(value).send(association.options[:primary_key])
|
121
|
+
end
|
122
|
+
|
123
|
+
condition = constraint_condition_for("#{table}.#{field}", value)
|
124
|
+
if association.options[:polymorphic]
|
125
|
+
begin
|
126
|
+
parent_scaffold = "#{session_info[:parent_scaffold].to_s.camelize}Controller".constantize
|
127
|
+
condition = merge_conditions(
|
128
|
+
condition,
|
129
|
+
constraint_condition_for("#{table}.#{association.name}_type", parent_scaffold.active_scaffold_config.model_id.to_s)
|
130
|
+
)
|
131
|
+
rescue ActiveScaffold::ControllerNotFound
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
condition
|
137
|
+
end
|
138
|
+
|
139
|
+
def constraint_error(klass, column_name)
|
140
|
+
"Malformed constraint `#{klass}##{column_name}'. If it's a legitimate column, and you are using a nested scaffold, please specify or double-check the reverse association name."
|
141
|
+
end
|
142
|
+
|
143
|
+
# Applies constraints to the given record.
|
144
|
+
#
|
145
|
+
# Searches through the known columns for association columns. If the given constraint is an association,
|
146
|
+
# it assumes that the constraint value is an id. It then does a association.klass.find with the value
|
147
|
+
# and adds the associated object to the record.
|
148
|
+
#
|
149
|
+
# For some operations ActiveRecord will automatically update the database. That's not always ok.
|
150
|
+
# If it *is* ok (e.g. you're in a transaction), then set :allow_autosave to true.
|
151
|
+
def apply_constraints_to_record(record, options = {})
|
152
|
+
options[:allow_autosave] = false if options[:allow_autosave].nil?
|
153
|
+
|
154
|
+
active_scaffold_constraints.each do |k, v|
|
155
|
+
column = active_scaffold_config.columns[k]
|
156
|
+
if column and column.association
|
157
|
+
if column.plural_association?
|
158
|
+
record.send("#{k}").send(:<<, column.association.klass.find(v))
|
159
|
+
elsif column.association.options[:polymorphic]
|
160
|
+
record.send("#{k}=", params[:parent_model].constantize.find(v))
|
161
|
+
else # regular singular association
|
162
|
+
record.send("#{k}=", column.association.klass.find(v))
|
163
|
+
|
164
|
+
# setting the belongs_to side of a has_one isn't safe. if the has_one was already
|
165
|
+
# specified, rails won't automatically clear out the previous associated record.
|
166
|
+
#
|
167
|
+
# note that we can't take the extra step to correct this unless we're permitted to
|
168
|
+
# run operations where activerecord auto-saves the object.
|
169
|
+
reverse = column.association.klass.reflect_on_association(column.association.reverse)
|
170
|
+
if reverse.macro == :has_one and options[:allow_autosave]
|
171
|
+
record.send(k).send("#{column.association.reverse}=", record)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
else
|
175
|
+
record.send("#{k}=", v)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def constraint_condition_for(sql, value)
|
183
|
+
value.nil? ? "#{sql} IS NULL" : ["#{sql} = ?", value]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -3,15 +3,15 @@ module ActiveScaffold::DataStructures
|
|
3
3
|
# provides a quick way to set any property of the object from a hash
|
4
4
|
def initialize(action, options = {})
|
5
5
|
# set defaults
|
6
|
-
self.action = action
|
6
|
+
self.action = action
|
7
7
|
self.label = action
|
8
8
|
self.confirm = false
|
9
9
|
self.type = :collection
|
10
10
|
self.inline = true
|
11
11
|
self.method = :get
|
12
|
-
self.crud_type = :delete if [:destroy].include?(action.to_sym)
|
13
|
-
self.crud_type = :create if [:create, :new].include?(action.to_sym)
|
14
|
-
self.crud_type = :update if [:edit, :update].include?(action.to_sym)
|
12
|
+
self.crud_type = :delete if [:destroy].include?(action.try(:to_sym))
|
13
|
+
self.crud_type = :create if [:create, :new].include?(action.try(:to_sym))
|
14
|
+
self.crud_type = :update if [:edit, :update].include?(action.try(:to_sym))
|
15
15
|
self.crud_type ||= :read
|
16
16
|
self.parameters = {}
|
17
17
|
self.html_options = {}
|