active_scaffold 3.1.4 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,179 @@
1
+ module ActiveScaffold::DataStructures
2
+ class ActionLink
3
+ # provides a quick way to set any property of the object from a hash
4
+ def initialize(action, options = {})
5
+ # set defaults
6
+ self.action = action.to_s
7
+ self.label = action
8
+ self.confirm = false
9
+ self.type = :collection
10
+ self.inline = true
11
+ self.method = :get
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
+ self.crud_type ||= :read
16
+ self.parameters = {}
17
+ self.html_options = {}
18
+ self.column = nil
19
+ self.image = nil
20
+ self.dynamic_parameters = nil
21
+
22
+ # apply quick properties
23
+ options.each_pair do |k, v|
24
+ setter = "#{k}="
25
+ self.send(setter, v) if self.respond_to? setter
26
+ end
27
+ end
28
+
29
+ # the action-path for this link. what page to request? this is required!
30
+ attr_accessor :action
31
+
32
+ # the controller for this action link. if nil, the current controller should be assumed.
33
+ attr_writer :controller
34
+
35
+ def controller
36
+ @controller = @controller.call if @controller.is_a?(Proc)
37
+ @controller
38
+ end
39
+
40
+ def static_controller?
41
+ !(@controller.is_a?(Proc) || (@controller == :polymorph))
42
+ end
43
+
44
+ # a hash of request parameters
45
+ attr_accessor :parameters
46
+
47
+ # a block for dynamic_parameters
48
+ attr_accessor :dynamic_parameters
49
+
50
+ # the RESTful method
51
+ attr_accessor :method
52
+
53
+ # what string to use to represent this action
54
+ attr_writer :label
55
+ def label
56
+ @label.is_a?(Symbol) ? as_(@label) : @label
57
+ end
58
+
59
+ # image to use {:name => 'arrow.png', :size => '16x16'}
60
+ attr_accessor :image
61
+
62
+ # if the action requires confirmation
63
+ def confirm=(value)
64
+ @dhtml_confirm = nil if value
65
+ @confirm = value
66
+ end
67
+ def confirm(label = '')
68
+ @confirm.is_a?(String) ? @confirm : as_(@confirm, :label => label)
69
+ end
70
+ def confirm?
71
+ !!@confirm
72
+ end
73
+
74
+ # if the action uses a DHTML based (i.e. 2-phase) confirmation
75
+ attr_accessor :dhtml_confirm
76
+ def dhtml_confirm=(value)
77
+ @confirm = nil if value
78
+ @dhtml_confirm = value
79
+ end
80
+ def dhtml_confirm?
81
+ !!@dhtml_confirm
82
+ end
83
+
84
+ # what method to call on the controller to see if this action_link should be visible
85
+ # note that this is only the UI part of the security. to prevent URL hax0rz, you also need security on requests (e.g. don't execute update method unless authorized).
86
+ attr_writer :security_method
87
+ def security_method
88
+ @security_method || "#{self.action}_authorized?"
89
+ end
90
+
91
+ def security_method_set?
92
+ !!@security_method
93
+ end
94
+
95
+ attr_accessor :ignore_method
96
+
97
+ # the crud type of the (eventual?) action. different than :method, because this crud action may not be imminent.
98
+ # this is used to determine record-level authorization (e.g. record.authorized_for?(:crud_type => link.crud_type).
99
+ # options are :create, :read, :update, and :delete
100
+ attr_accessor :crud_type
101
+
102
+ # an "inline" link is inserted into the existing page
103
+ # exclusive with popup? and page?
104
+ def inline=(val)
105
+ @inline = (val == true)
106
+ self.popup = self.page = false if @inline
107
+ end
108
+ def inline?; @inline end
109
+
110
+ # a "popup" link displays in a separate (browser?) window. this will eventually take arguments.
111
+ # exclusive with inline? and page?
112
+ def popup=(val)
113
+ @popup = (val == true)
114
+ if @popup
115
+ self.inline = self.page = false
116
+
117
+ # the :method parameter doesn't mix with the :popup parameter
118
+ # when/if we start using DHTML popups, we can bring :method back
119
+ self.method = nil
120
+ end
121
+ end
122
+ def popup?; @popup end
123
+
124
+ # a "page" link displays by reloading the current page
125
+ # exclusive with inline? and popup?
126
+ def page=(val)
127
+ @page = (val == true)
128
+ if @page
129
+ self.inline = self.popup = false
130
+
131
+ # when :method is defined, ActionView adds an onclick to use a form ...
132
+ # so it's best to just empty out :method whenever possible.
133
+ # we only ever need to know @method = :get for things that default to POST.
134
+ # the only things that default to POST are forms and ajax calls.
135
+ # when @page = true, we don't use ajax.
136
+ self.method = nil if method == :get
137
+ end
138
+ end
139
+ def page?; @page end
140
+
141
+ # where the result of this action should insert in the display.
142
+ # for :type => :collection, supported values are:
143
+ # :top
144
+ # :bottom
145
+ # :replace (for updating the entire table)
146
+ # false (no attempt at positioning)
147
+ # for :type => :member, supported values are:
148
+ # :before
149
+ # :replace
150
+ # :after
151
+ # false (no attempt at positioning)
152
+ attr_writer :position
153
+ def position
154
+ return @position unless @position.nil? or @position == true
155
+ return :replace if self.type == :member
156
+ return :top if self.type == :collection
157
+ raise "what should the default position be for #{self.type}?"
158
+ end
159
+
160
+ # what type of link this is. currently supported values are :collection and :member.
161
+ attr_accessor :type
162
+
163
+ # html options for the link
164
+ attr_accessor :html_options
165
+
166
+ # nested action_links are referencing a column
167
+ attr_accessor :column
168
+
169
+ # indicates that this a nested_link
170
+ def nested_link?
171
+ @column || (parameters && parameters[:named_scope])
172
+ end
173
+
174
+ # Internal use: generated eid for this action_link
175
+ attr_accessor :eid
176
+
177
+
178
+ end
179
+ end
@@ -0,0 +1,124 @@
1
+ module ActiveScaffold::DataStructures
2
+ class NestedInfo
3
+ def self.get(model, session_storage)
4
+ if session_storage[:nested].nil?
5
+ nil
6
+ else
7
+ session_info = session_storage[:nested].clone
8
+ begin
9
+ debugger
10
+ session_info[:parent_scaffold] = "#{session_info[:parent_scaffold].to_s.camelize}Controller".constantize
11
+ session_info[:parent_model] = session_info[:parent_scaffold].active_scaffold_config.model
12
+ session_info[:association] = session_info[:parent_model].reflect_on_association(session_info[:name])
13
+ unless session_info[:association].nil?
14
+ ActiveScaffold::DataStructures::NestedInfoAssociation.new(model, session_info)
15
+ else
16
+ ActiveScaffold::DataStructures::NestedInfoScope.new(model, session_info)
17
+ end
18
+ rescue ActiveScaffold::ControllerNotFound
19
+ nil
20
+ end
21
+ end
22
+ end
23
+
24
+ attr_accessor :association, :child_association, :parent_model, :parent_scaffold, :parent_id, :constrained_fields, :scope
25
+
26
+ def initialize(model, session_info)
27
+ @parent_model = session_info[:parent_model]
28
+ @parent_id = session_info[:parent_id]
29
+ @parent_scaffold = session_info[:parent_scaffold]
30
+ end
31
+
32
+ def new_instance?
33
+ result = @new_instance.nil?
34
+ @new_instance = false
35
+ result
36
+ end
37
+
38
+ def parent_scope
39
+ parent_model.find(parent_id)
40
+ end
41
+
42
+ def habtm?
43
+ false
44
+ end
45
+
46
+ def belongs_to?
47
+ false
48
+ end
49
+
50
+ def has_one?
51
+ false
52
+ end
53
+
54
+ def readonly?
55
+ false
56
+ end
57
+
58
+ def sorted?
59
+ false
60
+ end
61
+ end
62
+
63
+ class NestedInfoAssociation < NestedInfo
64
+ def initialize(model, session_info)
65
+ super(model, session_info)
66
+ @association = session_info[:association]
67
+ iterate_model_associations(model)
68
+ end
69
+
70
+ def habtm?
71
+ association.macro == :has_and_belongs_to_many
72
+ end
73
+
74
+ def belongs_to?
75
+ association.belongs_to?
76
+ end
77
+
78
+ def has_one?
79
+ association.macro == :has_one
80
+ end
81
+
82
+ def readonly?
83
+ if association.options.has_key? :readonly
84
+ association.options[:readonly]
85
+ else
86
+ association.options.has_key? :through
87
+ end
88
+ end
89
+
90
+ def sorted?
91
+ association.options.has_key? :order
92
+ end
93
+
94
+ def default_sorting
95
+ association.options[:order]
96
+ end
97
+
98
+ protected
99
+
100
+ def iterate_model_associations(model)
101
+ @constrained_fields = []
102
+ @constrained_fields << association.foreign_key.to_sym unless association.belongs_to?
103
+ model.reflect_on_all_associations.each do |current|
104
+ if !current.belongs_to? && association.foreign_key == current.association_foreign_key
105
+ constrained_fields << current.name.to_sym
106
+ @child_association = current if current.klass == @parent_model
107
+ end
108
+ if association.foreign_key == current.foreign_key
109
+ # show columns for has_many and has_one child associationes
110
+ constrained_fields << current.name.to_sym if current.belongs_to?
111
+ @child_association = current
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ class NestedInfoScope < NestedInfo
118
+ def initialize(model, session_info)
119
+ super(model, session_info)
120
+ @scope = session_info[:name]
121
+ @constrained_fields = []
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,62 @@
1
+ # save and validation support for associations.
2
+ class ActiveRecord::Base
3
+ def associated_valid?(path = [])
4
+ return true if path.include?(self) # prevent recursion (if associated and parent are new records)
5
+ path << self
6
+ # using [].all? syntax to avoid a short-circuit
7
+ with_unsaved_associated { |a| debugger; [a.valid?, a.associated_valid?(path)].all? {|v| v == true} }
8
+ end
9
+
10
+ def save_associated
11
+ with_unsaved_associated { |a| a.save and a.save_associated }
12
+ end
13
+
14
+ def save_associated!
15
+ save_associated or raise(ActiveRecord::RecordNotSaved)
16
+ end
17
+
18
+ def no_errors_in_associated?
19
+ with_unsaved_associated {|a| a.errors.count == 0 and a.no_errors_in_associated?}
20
+ end
21
+
22
+ protected
23
+
24
+ # Provide an override to allow the model to restrict which associations are considered
25
+ # by ActiveScaffolds update mechanism. This allows the model to restrict things like
26
+ # Acts-As-Versioned versions associations being traversed.
27
+ #
28
+ # By defining the method :scaffold_update_nofollow returning an array of associations
29
+ # these associations will not be traversed.
30
+ # By defining the method :scaffold_update_follow returning an array of associations,
31
+ # only those associations will be traversed.
32
+ #
33
+ # Otherwise the default behaviour of traversing all associations will be preserved.
34
+ def associations_for_update
35
+ if self.respond_to?( :scaffold_update_nofollow )
36
+ self.class.reflect_on_all_associations.reject { |association| self.scaffold_update_nofollow.include?( association.name ) }
37
+ elsif self.respond_to?( :scaffold_update_follow )
38
+ self.class.reflect_on_all_associations.select { |association| self.scaffold_update_follow.include?( association.name ) }
39
+ else
40
+ self.class.reflect_on_all_associations
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # yields every associated object that has been instantiated and is flagged as unsaved.
47
+ # returns false if any yield returns false.
48
+ # returns true otherwise, even when none of the associations have been instantiated. build wrapper methods accordingly.
49
+ def with_unsaved_associated
50
+ associations_for_update.all? do |association|
51
+ association_proxy = send(association.name)
52
+ if association_proxy
53
+ records = association_proxy
54
+ records = [records] unless records.is_a? Array # convert singular associations into collections for ease of use
55
+ debugger
56
+ records.select {|r| r.unsaved? and not r.readonly?}.all? {|r| yield r} # must use select instead of find_all, which Rails overrides on association proxies for db access
57
+ else
58
+ true
59
+ end
60
+ end
61
+ end
62
+ end
@@ -108,11 +108,18 @@ module ActiveScaffold
108
108
 
109
109
  def condition_value_for_datetime(value, conversion = :to_time)
110
110
  if value.is_a? Hash
111
- Time.zone.local(*[:year, :month, :day, :hour, :minute, :second].collect {|part| value[field][part].to_i}) rescue nil
111
+ Time.zone.local(*[:year, :month, :day, :hour, :minute, :second].collect {|part| value[part].to_i}) rescue nil
112
112
  elsif value.respond_to?(:strftime)
113
113
  value.send(conversion)
114
+ elsif conversion == :to_date
115
+ Date.strptime(value, I18n.t('date.formats.default')) rescue nil
114
116
  else
115
- Time.zone.parse(value).in_time_zone.send(conversion) rescue nil
117
+ parts = Date._parse(value)
118
+ time_parts = [[:hour, '%H'], [:min, '%M'], [:sec, '%S']].collect {|part, format_part| format_part if parts[part].present?}.compact
119
+ format = "#{I18n.t('date.formats.default')} #{time_parts.join(':')} #{'%z' if parts[:offset].present?}"
120
+ time = DateTime.strptime(value, format)
121
+ time = Time.zone.local_to_utc(time) unless parts[:offset]
122
+ time.in_time_zone.send(conversion) rescue nil
116
123
  end unless value.nil? || value.blank?
117
124
  end
118
125
 
@@ -108,11 +108,18 @@ module ActiveScaffold
108
108
 
109
109
  def condition_value_for_datetime(value, conversion = :to_time)
110
110
  if value.is_a? Hash
111
- Time.zone.local(*[:year, :month, :day, :hour, :minute, :second].collect {|part| value[field][part].to_i}) rescue nil
111
+ Time.zone.local(*[:year, :month, :day, :hour, :minute, :second].collect {|part| value[part].to_i}) rescue nil
112
112
  elsif value.respond_to?(:strftime)
113
113
  value.send(conversion)
114
+ elsif conversion == :to_date
115
+ Date.strptime(value, I18n.t('date.formats.default')) rescue nil
114
116
  else
115
- Time.zone.parse(value).in_time_zone.send(conversion) rescue nil
117
+ parts = Date._parse(value)
118
+ time_parts = [[:hour, '%H'], [:min, '%M'], [:sec, '%S']].collect {|part, format_part| format_part if parts[part].present?}.compact
119
+ format = "#{I18n.t('date.formats.default')} #{time_parts.join(':')} #{'%z' if parts[:offset].present?}"
120
+ time = DateTime.strptime(value, format)
121
+ time = Time.zone.local_to_utc(time) unless parts[:offset]
122
+ time.in_time_zone.send(conversion) rescue nil
116
123
  end unless value.nil? || value.blank?
117
124
  end
118
125
 
@@ -270,7 +277,7 @@ module ActiveScaffold
270
277
  # Returns a hash with options to count records, rejecting select and order options
271
278
  # See finder_options for valid options
272
279
  def count_options(find_options = {}, count_includes = nil)
273
- count_includes ||= find_options[:includes] unless find_options[:conditions].nil?
280
+ count_includes ||= find_options[:includes] unless find_options[:where].nil?
274
281
  options = find_options.reject{|k,v| [:select, :order].include? k}
275
282
  options[:includes] = count_includes
276
283
  options
@@ -289,6 +296,7 @@ module ActiveScaffold
289
296
  # NOTE: we must use :include in the count query, because some conditions may reference other tables
290
297
  if options[:pagination] && options[:pagination] != :infinite
291
298
  count_query = append_to_query(klass, count_options(find_options, options[:count_includes]))
299
+ debugger
292
300
  count = count_query.count unless options[:pagination] == :infinite
293
301
  end
294
302
 
@@ -34,13 +34,14 @@ module ActiveScaffold
34
34
  if column.link
35
35
  link = column.link
36
36
  associated = record.send(column.association.name) if column.association
37
- url_options = params_for(:action => nil, :id => record.id, :link => text)
37
+ url_options = params_for(:action => nil, :id => record.id)
38
38
 
39
39
  # setup automatic link
40
40
  if column.autolink? && column.singular_association? # link to inline form
41
- link = action_link_to_inline_form(column, record, associated)
42
- return text if link.crud_type.nil?
43
- url_options[:link] = as_(:create_new) if link.crud_type == :create
41
+ link = action_link_to_inline_form(column, record, associated, text)
42
+ return text if link.nil?
43
+ else
44
+ url_options[:link] = text
44
45
  end
45
46
 
46
47
  if column_link_authorized?(link, column, record, associated)
@@ -55,8 +56,9 @@ module ActiveScaffold
55
56
  end
56
57
 
57
58
  # setup the action link to inline form
58
- def action_link_to_inline_form(column, record, associated)
59
+ def action_link_to_inline_form(column, record, associated, text)
59
60
  link = column.link.clone
61
+ link.label = text
60
62
  if column.polymorphic_association?
61
63
  polymorphic_controller = controller_path_for_activerecord(record.send(column.association.name).class)
62
64
  return link if polymorphic_controller.nil?
@@ -70,6 +72,7 @@ module ActiveScaffold
70
72
  if actions.include?(:new)
71
73
  link.action = 'new'
72
74
  link.crud_type = :create
75
+ link.label = as_(:create_new)
73
76
  end
74
77
  elsif actions.include?(:edit)
75
78
  link.action = 'edit'
@@ -81,7 +84,7 @@ module ActiveScaffold
81
84
  link.action = 'index'
82
85
  link.crud_type = :read
83
86
  end
84
- link
87
+ link if link.action.present?
85
88
  end
86
89
 
87
90
  def column_link_authorized?(link, column, record, associated)
@@ -34,13 +34,14 @@ module ActiveScaffold
34
34
  if column.link
35
35
  link = column.link
36
36
  associated = record.send(column.association.name) if column.association
37
- url_options = params_for(:action => nil, :id => record.id, :link => text)
37
+ url_options = params_for(:action => nil, :id => record.id)
38
38
 
39
39
  # setup automatic link
40
40
  if column.autolink? && column.singular_association? # link to inline form
41
- link = action_link_to_inline_form(column, record, associated)
42
- return text if link.crud_type.nil?
43
- url_options[:link] = as_(:create_new) if link.crud_type == :create
41
+ link = action_link_to_inline_form(column, record, associated, text)
42
+ return text if link.nil?
43
+ else
44
+ url_options[:link] = text
44
45
  end
45
46
 
46
47
  if column_link_authorized?(link, column, record, associated)
@@ -55,8 +56,9 @@ module ActiveScaffold
55
56
  end
56
57
 
57
58
  # setup the action link to inline form
58
- def action_link_to_inline_form(column, record, associated)
59
+ def action_link_to_inline_form(column, record, associated, text)
59
60
  link = column.link.clone
61
+ link.label = text
60
62
  if column.polymorphic_association?
61
63
  polymorphic_controller = controller_path_for_activerecord(record.send(column.association.name).class)
62
64
  return link if polymorphic_controller.nil?
@@ -70,6 +72,7 @@ module ActiveScaffold
70
72
  if actions.include?(:new)
71
73
  link.action = 'new'
72
74
  link.crud_type = :create
75
+ link.label = as_(:create_new)
73
76
  end
74
77
  elsif actions.include?(:edit)
75
78
  link.action = 'edit'
@@ -81,7 +84,7 @@ module ActiveScaffold
81
84
  link.action = 'index'
82
85
  link.crud_type = :read
83
86
  end
84
- link
87
+ link if link.crud_type.present?
85
88
  end
86
89
 
87
90
  def column_link_authorized?(link, column, record, associated)
@@ -134,7 +134,7 @@ module ActiveScaffold
134
134
 
135
135
  def action_link_html_options(link, url_options, record, html_options)
136
136
  link_id = get_action_link_id(url_options, record, link.column)
137
- html_options.reverse_merge! link.html_options.merge(:class => link.action)
137
+ html_options.reverse_merge! link.html_options.merge(:class => link.action.to_s)
138
138
 
139
139
  # Needs to be in html_options to as the adding _method to the url is no longer supported by Rails
140
140
  html_options[:method] = link.method if link.method != :get
@@ -70,7 +70,7 @@ module ActiveScaffold
70
70
  return false if column.polymorphic_association?
71
71
 
72
72
  # A column shouldn't be in the subform if it's the reverse association to the parent
73
- return false if column.association.reverse_for?(parent_record.class)
73
+ return false if column.association.inverse_for?(parent_record.class)
74
74
 
75
75
  return true
76
76
  end
@@ -134,7 +134,7 @@ module ActiveScaffold
134
134
 
135
135
  def action_link_html_options(link, url_options, record, html_options)
136
136
  link_id = get_action_link_id(url_options, record, link.column)
137
- html_options.reverse_merge! link.html_options.merge(:class => link.action)
137
+ html_options.reverse_merge! link.html_options.merge(:class => link.action.to_s)
138
138
 
139
139
  # Needs to be in html_options to as the adding _method to the url is no longer supported by Rails
140
140
  html_options[:method] = link.method if link.method != :get
@@ -194,7 +194,7 @@ module ActiveScaffold
194
194
  url_options[:eid] = link.eid
195
195
  elsif link.parameters && link.parameters[:named_scope]
196
196
  url_options[:assoc_id] = url_options.delete(:id)
197
- link.eid = "#{controller_id.from(3)}_#{record.id}_#{link.parameters[:named_scope]}" unless options.has_key?(:reuse_eid)
197
+ link.eid = "#{controller_id.from(3)}_#{record.id}_#{link.parameters[:named_scope]}" unless record.nil? || options.has_key?(:reuse_eid)
198
198
  url_options[:eid] = link.eid
199
199
  end
200
200
  end
@@ -2,7 +2,7 @@ module ActiveScaffold
2
2
  module Version
3
3
  MAJOR = 3
4
4
  MINOR = 1
5
- PATCH = 4
5
+ PATCH = 5
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  end
@@ -266,7 +266,7 @@ module ActiveScaffold
266
266
  column.actions_for_association_links.delete :new unless actions.include? :create
267
267
  column.actions_for_association_links.delete :edit unless actions.include? :update
268
268
  column.actions_for_association_links.delete :show unless actions.include? :show
269
- ActiveScaffold::DataStructures::ActionLink.new(:none, options.merge({:crud_type => nil, :html_options => {:class => column.name}}))
269
+ ActiveScaffold::DataStructures::ActionLink.new(nil, options.merge(:html_options => {:class => column.name}))
270
270
  end
271
271
  end
272
272
  end