netzke-basepack 0.2.0.1 → 0.2.2

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/netzke/column.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module Netzke
2
+ # TODO: rename this class, or better even, make a module out of it
2
3
  class Column
3
- def self.default_columns_for_widget(widget)
4
+ def self.default_dbfields_for_widget(widget, mode = :grid)
4
5
  raise ArgumentError, "No data_class_name specified for widget #{widget.config[:name]}" if widget.config[:data_class_name].nil?
5
6
 
6
7
  # layout = NetzkeLayout.create(:widget_name => widget.id_name, :items_class => self.name, :user_id => NetzkeLayout.user_id)
@@ -26,11 +27,19 @@ module Netzke
26
27
  res = []
27
28
  for c in columns_for_create
28
29
  # finally reverse-merge them with the defaults from the data_class
29
- res << data_class.default_column_config(c)
30
+ res << (mode == :grid ? data_class.default_column_config(c) : data_class.default_field_config(c))
30
31
  end
31
32
 
32
33
  res
33
34
  end
35
+
36
+ def self.default_columns_for_widget(widget)
37
+ default_dbfields_for_widget(widget, :grid)
38
+ end
39
+
40
+ def self.default_fields_for_widget(widget)
41
+ default_dbfields_for_widget(widget, :form)
42
+ end
34
43
 
35
44
  protected
36
45
 
@@ -0,0 +1,185 @@
1
+ module Netzke
2
+ class FormPanel < Base
3
+ interface :submit, :load
4
+
5
+ class << self
6
+ def js_base_class
7
+ "Ext.FormPanel"
8
+ end
9
+
10
+ def js_before_constructor
11
+ <<-JS
12
+ var fields = config.fields; // TODO: remove hidden fields
13
+ var recordFields = [];
14
+ var index = 0;
15
+ Ext.each(config.fields, function(field){recordFields.push({name:field.name, mapping:index++})});
16
+ var Record = Ext.data.Record.create(recordFields);
17
+ this.reader = new Ext.data.RecordArrayReader({}, Record);
18
+
19
+ JS
20
+ end
21
+
22
+ def js_default_config
23
+ super.merge({
24
+ :auto_scroll => true,
25
+ :bbar => "config.actions".l,
26
+ # :plugins => "plugins".l,
27
+ :items => "fields".l,
28
+ :default_type => 'textfield',
29
+ :body_style => 'padding:5px 5px 0',
30
+ :label_width => 150,
31
+ :listeners => {:afterlayout => {:fn => "this.afterlayoutHandler".l, :scope => this}},
32
+
33
+ #custom configs
34
+ :auto_load_data => true,
35
+ })
36
+ end
37
+
38
+
39
+ end
40
+
41
+ def self.js_extend_properties
42
+ {
43
+ :load_record => <<-JS.l,
44
+ function(id, neighbour){
45
+ var proxy = new Ext.data.HttpProxy({url:this.initialConfig.interface.load});
46
+ proxy.load({id:id, neighbour:neighbour}, this.reader, function(data){
47
+ if (data){
48
+ this.form.loadRecord(data.records[0])
49
+ }
50
+ }, this)
51
+ }
52
+ JS
53
+ :afterlayout_handler => <<-JS.l,
54
+ function() {
55
+ // Load initial data into the form
56
+ if (this.initialConfig.recordData){
57
+ var record = this.reader.readRecord(this.initialConfig.recordData);
58
+ this.form.loadRecord(record);
59
+ }
60
+ }
61
+ JS
62
+ :refresh_click => <<-JS.l,
63
+ function() {
64
+ this.loadRecord(3)
65
+ }
66
+ JS
67
+ :previous => <<-JS.l,
68
+ function() {
69
+ var currentId = this.form.getValues().id;
70
+ this.loadRecord(currentId, 'previous');
71
+ }
72
+ JS
73
+ :next => <<-JS.l,
74
+ function() {
75
+ var currentId = this.form.getValues().id;
76
+ this.loadRecord(currentId, 'next');
77
+ }
78
+ JS
79
+ :submit => <<-JS.l,
80
+ function() {
81
+ this.form.submit({
82
+ url:this.initialConfig.interface.submit
83
+ })
84
+ }
85
+ JS
86
+ }
87
+ end
88
+
89
+ # default configuration
90
+ def initial_config
91
+ {
92
+ :ext_config => {
93
+ :config_tool => false,
94
+ :border => true
95
+ },
96
+ :layout_manager => "NetzkeLayout",
97
+ :field_manager => "NetzkeFormPanelField"
98
+ }
99
+ end
100
+
101
+ def tools
102
+ [{:id => 'refresh', :on => {:click => 'refreshClick'}}]
103
+ end
104
+
105
+ def actions
106
+ [{
107
+ :text => 'Previous', :handler => 'previous'
108
+ },{
109
+ :text => 'Next', :handler => 'next'
110
+ },{
111
+ :text => 'Apply', :handler => 'submit', :disabled => !@permissions[:update] && !@permissions[:create]
112
+ }]
113
+ end
114
+
115
+ def js_config
116
+ res = super
117
+ # we pass column config at the time of instantiating the JS class
118
+ res.merge!(:fields => get_fields || config[:fields]) # first try to get columns from DB, then from config
119
+ res.merge!(:data_class_name => config[:data_class_name])
120
+ res.merge!(:record_data => config[:record].to_array(get_fields))
121
+ res
122
+ end
123
+
124
+ # get fields from layout manager
125
+ def get_fields
126
+ @fields ||=
127
+ if layout_manager_class && field_manager_class
128
+ layout = layout_manager_class.by_widget(id_name)
129
+ layout ||= field_manager_class.create_layout_for_widget(self)
130
+ layout.items_hash # TODO: bad name!
131
+ else
132
+ Netzke::Column.default_columns_for_widget(self)
133
+ end
134
+ end
135
+
136
+ def submit(params)
137
+ params.delete(:authenticity_token)
138
+ params.delete(:controller)
139
+ params.delete(:action)
140
+ book = Book.find(params[:id])
141
+ if book.nil?
142
+ book = Book.create(params)
143
+ else
144
+ book.update_attributes(params)
145
+ end
146
+ rescue ActiveRecord::UnknownAttributeError # unknown attributes get ignored
147
+ book.save
148
+ [book.to_array(get_fields)].to_json
149
+ end
150
+
151
+ def load(params)
152
+ logger.debug { "!!! params: #{params.inspect}" }
153
+ klass = config[:data_class_name].constantize
154
+ case params[:neighbour]
155
+ when "previous"
156
+ book = klass.previous(params[:id])
157
+ when "next"
158
+ book = klass.next(params[:id])
159
+ else
160
+ book = klass.find(params[:id])
161
+ end
162
+ [book && book.to_array(get_fields)].to_json
163
+ end
164
+
165
+ protected
166
+
167
+ def layout_manager_class
168
+ config[:layout_manager].constantize
169
+ rescue NameError
170
+ nil
171
+ end
172
+
173
+ def field_manager_class
174
+ config[:field_manager].constantize
175
+ rescue NameError
176
+ nil
177
+ end
178
+
179
+ def available_permissions
180
+ %w(read update create delete)
181
+ end
182
+
183
+
184
+ end
185
+ end
@@ -18,6 +18,31 @@ module Netzke
18
18
  # define connection points between client side and server side of GridPanel. See implementation of equally named methods in the GridPanelInterface module.
19
19
  interface :get_data, :post_data, :delete_data, :resize_column, :move_column, :get_cb_choices
20
20
 
21
+ module ClassMethods
22
+
23
+ # Global GridPanel configuration
24
+ def config
25
+ set_default_config({
26
+ :column_manager => "NetzkeGridPanelColumn"
27
+ })
28
+ end
29
+
30
+ def column_manager_class
31
+ config[:column_manager].constantize
32
+ rescue
33
+ nil
34
+ end
35
+ end
36
+ extend ClassMethods
37
+
38
+ def layout_manager_class
39
+ self.class.layout_manager_class
40
+ end
41
+
42
+ def column_manager_class
43
+ self.class.column_manager_class
44
+ end
45
+
21
46
  # default grid configuration
22
47
  def initial_config
23
48
  {
@@ -26,9 +51,11 @@ module Netzke
26
51
  :enable_column_filters => Netzke::Base.config[:grid_panel][:filters],
27
52
  :enable_column_move => true,
28
53
  :enable_column_resize => true,
29
- :border => true
54
+ :border => true,
55
+ :load_mask => true
30
56
  },
31
- :layout_manager => "NetzkeLayout"
57
+ :persistent_layout => true,
58
+ :persistent_config => true
32
59
  }
33
60
  end
34
61
 
@@ -36,7 +63,7 @@ module Netzke
36
63
  [{
37
64
  :name => 'columns',
38
65
  :widget_class_name => "GridPanel",
39
- :data_class_name => column_manager_class_name,
66
+ :data_class_name => column_manager_class.name,
40
67
  :ext_config => {:title => false, :config_tool => false},
41
68
  :active => true
42
69
  },{
@@ -63,25 +90,9 @@ module Netzke
63
90
  w = aggregatee_instance(:properties__general)
64
91
  w.interface_load_source(params)
65
92
  end
66
-
67
-
68
93
 
69
94
  protected
70
95
 
71
- def layout_manager_class
72
- config[:layout_manager] && config[:layout_manager].constantize
73
- end
74
-
75
- def column_manager_class_name
76
- "NetzkeGridPanelColumn"
77
- end
78
-
79
- def column_manager_class
80
- column_manager_class_name.constantize
81
- rescue NameError
82
- nil
83
- end
84
-
85
96
  def available_permissions
86
97
  %w(read update create delete)
87
98
  end
@@ -91,7 +102,7 @@ module Netzke
91
102
  # get columns from layout manager
92
103
  def get_columns
93
104
  @columns ||=
94
- if layout_manager_class && column_manager_class
105
+ if config[:persistent_layout] && layout_manager_class && column_manager_class
95
106
  layout = layout_manager_class.by_widget(id_name)
96
107
  layout ||= column_manager_class.create_layout_for_widget(self)
97
108
  layout.items_hash # TODO: bad name!
@@ -104,14 +104,18 @@ module Netzke::GridPanelInterface
104
104
  search_params = normalize_params(params)
105
105
  raise ArgumentError, "No data_class_name specified for widget '#{config[:name]}'" if !config[:data_class_name]
106
106
  records = config[:data_class_name].constantize.all(search_params.clone) # clone needed as searchlogic removes :conditions key from the hash
107
- output_array = []
108
- records.each do |r|
109
- r_array = []
110
- self.get_columns.each do |column|
111
- r_array << r.send(column[:name])
112
- end
113
- output_array << r_array
114
- end
107
+ # output_array = []
108
+ columns = get_columns
109
+ output_array = records.map{|r| r.to_array(columns)}
110
+
111
+ # records.each do |r|
112
+ # r_array = []
113
+ # self.get_columns.each do |column|
114
+ # r_array << r.send(column[:name])
115
+ # end
116
+ # output_array << r_array
117
+ # output_array << r.to_array(columns)
118
+ # end
115
119
 
116
120
  # add total_entries accessor to the result
117
121
  class << output_array
@@ -2,18 +2,18 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{netzke-basepack}
5
- s.version = "0.2.0.1"
5
+ s.version = "0.2.2"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Sergei Kozlov"]
9
- s.date = %q{2009-01-07}
9
+ s.date = %q{2009-01-23}
10
10
  s.description = %q{Base Netzke widgets - grid, form, tree, and more}
11
11
  s.email = %q{sergei@writelesscode.com}
12
- s.extra_rdoc_files = ["CHANGELOG", "lib/app/models/netzke_grid_panel_column.rb", "lib/netzke/accordion_panel.rb", "lib/netzke/ar_ext.rb", "lib/netzke/border_layout_panel.rb", "lib/netzke/column.rb", "lib/netzke/container.rb", "lib/netzke/grid_panel.rb", "lib/netzke/grid_panel_interface.rb", "lib/netzke/grid_panel_js_builder.rb", "lib/netzke/panel.rb", "lib/netzke/preference_grid.rb", "lib/netzke/properties_tool.rb", "lib/netzke/property_grid.rb", "lib/netzke/wrapper.rb", "lib/netzke-basepack.rb", "LICENSE", "README.mdown", "tasks/netzke_basepack_tasks.rake"]
13
- s.files = ["CHANGELOG", "css/basepack.css", "generators/netzke_basepack/netzke_basepack_generator.rb", "generators/netzke_basepack/netzke_grid_panel_generator.rb", "generators/netzke_basepack/templates/create_netzke_grid_panel_columns.rb", "generators/netzke_basepack/USAGE", "init.rb", "install.rb", "javascripts/basepack.js", "javascripts/filters.js", "lib/app/models/netzke_grid_panel_column.rb", "lib/netzke/accordion_panel.rb", "lib/netzke/ar_ext.rb", "lib/netzke/border_layout_panel.rb", "lib/netzke/column.rb", "lib/netzke/container.rb", "lib/netzke/grid_panel.rb", "lib/netzke/grid_panel_interface.rb", "lib/netzke/grid_panel_js_builder.rb", "lib/netzke/panel.rb", "lib/netzke/preference_grid.rb", "lib/netzke/properties_tool.rb", "lib/netzke/property_grid.rb", "lib/netzke/wrapper.rb", "lib/netzke-basepack.rb", "LICENSE", "Manifest", "Rakefile", "README.mdown", "tasks/netzke_basepack_tasks.rake", "test/app_root/app/controllers/application.rb", "test/app_root/app/models/book.rb", "test/app_root/app/models/category.rb", "test/app_root/app/models/city.rb", "test/app_root/app/models/continent.rb", "test/app_root/app/models/country.rb", "test/app_root/app/models/genre.rb", "test/app_root/config/boot.rb", "test/app_root/config/database.yml", "test/app_root/config/environment.rb", "test/app_root/config/environments/in_memory.rb", "test/app_root/config/environments/mysql.rb", "test/app_root/config/environments/postgresql.rb", "test/app_root/config/environments/sqlite.rb", "test/app_root/config/environments/sqlite3.rb", "test/app_root/config/routes.rb", "test/app_root/db/migrate/20081222033343_create_books.rb", "test/app_root/db/migrate/20081222033440_create_genres.rb", "test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb", "test/app_root/db/migrate/20081223024935_create_categories.rb", "test/app_root/db/migrate/20081223025635_create_countries.rb", "test/app_root/db/migrate/20081223025653_create_continents.rb", "test/app_root/db/migrate/20081223025732_create_cities.rb", "test/app_root/script/console", "test/ar_ext_test.rb", "test/border_layout_panel_test.rb", "test/column_test.rb", "test/console_with_fixtures.rb", "test/fixtures/books.yml", "test/fixtures/categories.yml", "test/fixtures/cities.yml", "test/fixtures/continents.yml", "test/fixtures/countries.yml", "test/fixtures/genres.yml", "test/grid_panel_test.rb", "test/netzke_basepack_test.rb", "test/schema.rb", "test/test_helper.rb", "uninstall.rb", "netzke-basepack.gemspec"]
12
+ s.extra_rdoc_files = ["CHANGELOG", "lib/app/models/netzke_form_panel_field.rb", "lib/app/models/netzke_grid_panel_column.rb", "lib/netzke/accordion_panel.rb", "lib/netzke/ar_ext.rb", "lib/netzke/border_layout_panel.rb", "lib/netzke/column.rb", "lib/netzke/container.rb", "lib/netzke/form_panel.rb", "lib/netzke/grid_panel.rb", "lib/netzke/grid_panel_interface.rb", "lib/netzke/grid_panel_js_builder.rb", "lib/netzke/panel.rb", "lib/netzke/preference_grid.rb", "lib/netzke/properties_tool.rb", "lib/netzke/property_grid.rb", "lib/netzke/wrapper.rb", "lib/netzke-basepack.rb", "LICENSE", "README.rdoc", "tasks/netzke_basepack_tasks.rake"]
13
+ s.files = ["CHANGELOG", "css/basepack.css", "generators/netzke_basepack/netzke_basepack_generator.rb", "generators/netzke_basepack/USAGE", "generators/netzke_form_panel/netzke_form_panel_generator.rb", "generators/netzke_form_panel/templates/create_netzke_form_panel_fields.rb", "generators/netzke_grid_panel/netzke_grid_panel_generator.rb", "generators/netzke_grid_panel/templates/create_netzke_grid_panel_columns.rb", "init.rb", "install.rb", "javascripts/basepack.js", "javascripts/filters.js", "lib/app/models/netzke_form_panel_field.rb", "lib/app/models/netzke_grid_panel_column.rb", "lib/netzke/accordion_panel.rb", "lib/netzke/ar_ext.rb", "lib/netzke/border_layout_panel.rb", "lib/netzke/column.rb", "lib/netzke/container.rb", "lib/netzke/form_panel.rb", "lib/netzke/grid_panel.rb", "lib/netzke/grid_panel_interface.rb", "lib/netzke/grid_panel_js_builder.rb", "lib/netzke/panel.rb", "lib/netzke/preference_grid.rb", "lib/netzke/properties_tool.rb", "lib/netzke/property_grid.rb", "lib/netzke/wrapper.rb", "lib/netzke-basepack.rb", "LICENSE", "Manifest", "Rakefile", "README.rdoc", "tasks/netzke_basepack_tasks.rake", "test/app_root/app/controllers/application.rb", "test/app_root/app/models/book.rb", "test/app_root/app/models/category.rb", "test/app_root/app/models/city.rb", "test/app_root/app/models/continent.rb", "test/app_root/app/models/country.rb", "test/app_root/app/models/genre.rb", "test/app_root/config/boot.rb", "test/app_root/config/database.yml", "test/app_root/config/environment.rb", "test/app_root/config/environments/in_memory.rb", "test/app_root/config/environments/mysql.rb", "test/app_root/config/environments/postgresql.rb", "test/app_root/config/environments/sqlite.rb", "test/app_root/config/environments/sqlite3.rb", "test/app_root/config/routes.rb", "test/app_root/db/migrate/20081222033343_create_books.rb", "test/app_root/db/migrate/20081222033440_create_genres.rb", "test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb", "test/app_root/db/migrate/20081223024935_create_categories.rb", "test/app_root/db/migrate/20081223025635_create_countries.rb", "test/app_root/db/migrate/20081223025653_create_continents.rb", "test/app_root/db/migrate/20081223025732_create_cities.rb", "test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb", "test/app_root/db/migrate/20090102223811_create_netzke_grid_panel_columns.rb", "test/app_root/script/console", "test/app_root/vendor/plugins/acts_as_list/init.rb", "test/app_root/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb", "test/app_root/vendor/plugins/acts_as_list/README", "test/ar_ext_test.rb", "test/border_layout_panel_test.rb", "test/column_test.rb", "test/console_with_fixtures.rb", "test/fixtures/books.yml", "test/fixtures/categories.yml", "test/fixtures/cities.yml", "test/fixtures/continents.yml", "test/fixtures/countries.yml", "test/fixtures/genres.yml", "test/grid_panel_test.rb", "test/netzke_basepack_test.rb", "test/schema.rb", "test/test_helper.rb", "uninstall.rb", "netzke-basepack.gemspec"]
14
14
  s.has_rdoc = true
15
15
  s.homepage = %q{http://writelesscode.com}
16
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Netzke-basepack", "--main", "README.mdown"]
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Netzke-basepack", "--main", "README.rdoc"]
17
17
  s.require_paths = ["lib"]
18
18
  s.rubyforge_project = %q{netzke-basepack}
19
19
  s.rubygems_version = %q{1.3.1}
@@ -26,13 +26,13 @@ Gem::Specification.new do |s|
26
26
 
27
27
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
28
  s.add_runtime_dependency(%q<searchlogic>, [">= 1.6.2"])
29
- s.add_runtime_dependency(%q<netzke-core>, [">= 0", "= 0.2.1"])
29
+ s.add_runtime_dependency(%q<netzke-core>, [">= 0", "= 0.2.2"])
30
30
  else
31
31
  s.add_dependency(%q<searchlogic>, [">= 1.6.2"])
32
- s.add_dependency(%q<netzke-core>, [">= 0", "= 0.2.1"])
32
+ s.add_dependency(%q<netzke-core>, [">= 0", "= 0.2.2"])
33
33
  end
34
34
  else
35
35
  s.add_dependency(%q<searchlogic>, [">= 1.6.2"])
36
- s.add_dependency(%q<netzke-core>, [">= 0", "= 0.2.1"])
36
+ s.add_dependency(%q<netzke-core>, [">= 0", "= 0.2.2"])
37
37
  end
38
38
  end
@@ -0,0 +1,14 @@
1
+ class CreateNetzkeLayouts < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :netzke_layouts do |t|
4
+ t.string :widget_name
5
+ t.string :items_class
6
+ t.integer :user_id
7
+ t.timestamps
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :netzke_layouts
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ class CreateNetzkeGridPanelColumns < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :netzke_grid_panel_columns do |t|
4
+ t.string :name
5
+ t.string :label
6
+ t.boolean :read_only
7
+ t.integer :position
8
+ t.boolean :hidden
9
+ t.integer :width
10
+ t.string :editor, :limit => 32
11
+
12
+ t.integer :layout_id
13
+
14
+ t.timestamps
15
+ end
16
+ end
17
+
18
+ def self.down
19
+ drop_table :netzke_grid_panel_columns
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ ActsAsList
2
+ ==========
3
+
4
+ This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a +position+ column defined as an integer on the mapped database table.
5
+
6
+
7
+ Example
8
+ =======
9
+
10
+ class TodoList < ActiveRecord::Base
11
+ has_many :todo_items, :order => "position"
12
+ end
13
+
14
+ class TodoItem < ActiveRecord::Base
15
+ belongs_to :todo_list
16
+ acts_as_list :scope => :todo_list
17
+ end
18
+
19
+ todo_list.first.move_to_bottom
20
+ todo_list.last.move_higher
21
+
22
+
23
+ Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
@@ -0,0 +1,3 @@
1
+ $:.unshift "#{File.dirname(__FILE__)}/lib"
2
+ require 'active_record/acts/list'
3
+ ActiveRecord::Base.class_eval { include ActiveRecord::Acts::List }
@@ -0,0 +1,256 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module List #:nodoc:
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
9
+ # The class that has this specified needs to have a +position+ column defined as an integer on
10
+ # the mapped database table.
11
+ #
12
+ # Todo list example:
13
+ #
14
+ # class TodoList < ActiveRecord::Base
15
+ # has_many :todo_items, :order => "position"
16
+ # end
17
+ #
18
+ # class TodoItem < ActiveRecord::Base
19
+ # belongs_to :todo_list
20
+ # acts_as_list :scope => :todo_list
21
+ # end
22
+ #
23
+ # todo_list.first.move_to_bottom
24
+ # todo_list.last.move_higher
25
+ module ClassMethods
26
+ # Configuration options are:
27
+ #
28
+ # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
29
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
30
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
31
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
32
+ # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
33
+ def acts_as_list(options = {})
34
+ configuration = { :column => "position", :scope => "1 = 1" }
35
+ configuration.update(options) if options.is_a?(Hash)
36
+
37
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
38
+
39
+ if configuration[:scope].is_a?(Symbol)
40
+ scope_condition_method = %(
41
+ def scope_condition
42
+ if #{configuration[:scope].to_s}.nil?
43
+ "#{configuration[:scope].to_s} IS NULL"
44
+ else
45
+ "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
46
+ end
47
+ end
48
+ )
49
+ else
50
+ scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
51
+ end
52
+
53
+ class_eval <<-EOV
54
+ include ActiveRecord::Acts::List::InstanceMethods
55
+
56
+ def acts_as_list_class
57
+ ::#{self.name}
58
+ end
59
+
60
+ def position_column
61
+ '#{configuration[:column]}'
62
+ end
63
+
64
+ #{scope_condition_method}
65
+
66
+ before_destroy :remove_from_list
67
+ before_create :add_to_list_bottom
68
+ EOV
69
+ end
70
+ end
71
+
72
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
73
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
74
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
75
+ # the first in the list of all chapters.
76
+ module InstanceMethods
77
+ # Insert the item at the given position (defaults to the top position of 1).
78
+ def insert_at(position = 1)
79
+ insert_at_position(position)
80
+ end
81
+
82
+ # Swap positions with the next lower item, if one exists.
83
+ def move_lower
84
+ return unless lower_item
85
+
86
+ acts_as_list_class.transaction do
87
+ lower_item.decrement_position
88
+ increment_position
89
+ end
90
+ end
91
+
92
+ # Swap positions with the next higher item, if one exists.
93
+ def move_higher
94
+ return unless higher_item
95
+
96
+ acts_as_list_class.transaction do
97
+ higher_item.increment_position
98
+ decrement_position
99
+ end
100
+ end
101
+
102
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
103
+ # position adjusted accordingly.
104
+ def move_to_bottom
105
+ return unless in_list?
106
+ acts_as_list_class.transaction do
107
+ decrement_positions_on_lower_items
108
+ assume_bottom_position
109
+ end
110
+ end
111
+
112
+ # Move to the top of the list. If the item is already in the list, the items above it have their
113
+ # position adjusted accordingly.
114
+ def move_to_top
115
+ return unless in_list?
116
+ acts_as_list_class.transaction do
117
+ increment_positions_on_higher_items
118
+ assume_top_position
119
+ end
120
+ end
121
+
122
+ # Removes the item from the list.
123
+ def remove_from_list
124
+ if in_list?
125
+ decrement_positions_on_lower_items
126
+ update_attribute position_column, nil
127
+ end
128
+ end
129
+
130
+ # Increase the position of this item without adjusting the rest of the list.
131
+ def increment_position
132
+ return unless in_list?
133
+ update_attribute position_column, self.send(position_column).to_i + 1
134
+ end
135
+
136
+ # Decrease the position of this item without adjusting the rest of the list.
137
+ def decrement_position
138
+ return unless in_list?
139
+ update_attribute position_column, self.send(position_column).to_i - 1
140
+ end
141
+
142
+ # Return +true+ if this object is the first in the list.
143
+ def first?
144
+ return false unless in_list?
145
+ self.send(position_column) == 1
146
+ end
147
+
148
+ # Return +true+ if this object is the last in the list.
149
+ def last?
150
+ return false unless in_list?
151
+ self.send(position_column) == bottom_position_in_list
152
+ end
153
+
154
+ # Return the next higher item in the list.
155
+ def higher_item
156
+ return nil unless in_list?
157
+ acts_as_list_class.find(:first, :conditions =>
158
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
159
+ )
160
+ end
161
+
162
+ # Return the next lower item in the list.
163
+ def lower_item
164
+ return nil unless in_list?
165
+ acts_as_list_class.find(:first, :conditions =>
166
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
167
+ )
168
+ end
169
+
170
+ # Test if this record is in a list
171
+ def in_list?
172
+ !send(position_column).nil?
173
+ end
174
+
175
+ private
176
+ def add_to_list_top
177
+ increment_positions_on_all_items
178
+ end
179
+
180
+ def add_to_list_bottom
181
+ self[position_column] = bottom_position_in_list.to_i + 1
182
+ end
183
+
184
+ # Overwrite this method to define the scope of the list changes
185
+ def scope_condition() "1" end
186
+
187
+ # Returns the bottom position number in the list.
188
+ # bottom_position_in_list # => 2
189
+ def bottom_position_in_list(except = nil)
190
+ item = bottom_item(except)
191
+ item ? item.send(position_column) : 0
192
+ end
193
+
194
+ # Returns the bottom item
195
+ def bottom_item(except = nil)
196
+ conditions = scope_condition
197
+ conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
198
+ acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
199
+ end
200
+
201
+ # Forces item to assume the bottom position in the list.
202
+ def assume_bottom_position
203
+ update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
204
+ end
205
+
206
+ # Forces item to assume the top position in the list.
207
+ def assume_top_position
208
+ update_attribute(position_column, 1)
209
+ end
210
+
211
+ # This has the effect of moving all the higher items up one.
212
+ def decrement_positions_on_higher_items(position)
213
+ acts_as_list_class.update_all(
214
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
215
+ )
216
+ end
217
+
218
+ # This has the effect of moving all the lower items up one.
219
+ def decrement_positions_on_lower_items
220
+ return unless in_list?
221
+ acts_as_list_class.update_all(
222
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
223
+ )
224
+ end
225
+
226
+ # This has the effect of moving all the higher items down one.
227
+ def increment_positions_on_higher_items
228
+ return unless in_list?
229
+ acts_as_list_class.update_all(
230
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
231
+ )
232
+ end
233
+
234
+ # This has the effect of moving all the lower items down one.
235
+ def increment_positions_on_lower_items(position)
236
+ acts_as_list_class.update_all(
237
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
238
+ )
239
+ end
240
+
241
+ # Increments position (<tt>position_column</tt>) of all items in the list.
242
+ def increment_positions_on_all_items
243
+ acts_as_list_class.update_all(
244
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
245
+ )
246
+ end
247
+
248
+ def insert_at_position(position)
249
+ remove_from_list
250
+ increment_positions_on_lower_items(position)
251
+ self.update_attribute(position_column, position)
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end