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.
@@ -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