console_update 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT LICENSE
2
+
3
+ Copyright (c) 2009 Gabriel Horner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,112 @@
1
+ == Description
2
+
3
+ Updates records from the console via your preferred editor. You can update a record's columns as
4
+ well as <i>any attribute</i> that has accessor methods. Records are edited via a temporary file and
5
+ once saved, the records are updated. Records go through a filter before and after editing the file. Yaml is
6
+ the default filter, but you can define your own filter simply with a module and 2 expected methods.
7
+ See ConsoleUpdate::Filter for more details.
8
+
9
+ == Install
10
+
11
+ Install as a gem
12
+
13
+ bash> gem install console_update
14
+
15
+ # add in config/environment.rb
16
+ config.gem "console_update"
17
+
18
+ Or as a plugin
19
+
20
+ bash> script/plugin install git://github.com/cldwalker/console_update.git
21
+
22
+ == Examples
23
+
24
+ For a given model Url, update your records as you please:
25
+
26
+ bash> script/console
27
+
28
+ # Update a record from the object
29
+ >> Url.first.console_update
30
+
31
+ # Update a group of records
32
+ >> records = Url.all :limit=>10
33
+ >> Url.console_update records
34
+
35
+ # Find and update by a given id
36
+ >> Url.find_and_console_update 10
37
+
38
+ # Update through any named_scope ie tagged_with()
39
+ >> Url.tagged_with("sweetness").console_update
40
+
41
+ == Setup
42
+
43
+ Define your editor if not already picked up by environment variable $EDITOR:
44
+
45
+ ConsoleUpdate.editor = 'vim'
46
+
47
+ Configure model(s) to update from the console:
48
+
49
+ class Url
50
+ can_console_update
51
+ end
52
+
53
+ By default, can_console_update() has sensical defaults for what attributes to update.
54
+ But you can setup your own defaults as needed:
55
+
56
+ can_console_update :only=>%w{column1 column2 relation_accessor1}
57
+ can_console_update :except=>%w{column2}
58
+
59
+ To use the named_scope chaining, enable it once.
60
+ ConsoleUpdate.enable_named_scope
61
+
62
+ == More Examples
63
+
64
+ Although console_update() uses the default editable columns, it can take options to override
65
+ these as needed. Note these options can be passed to any of the console_update-like methods shown
66
+ above:
67
+
68
+ records = Url.all :limit=>100
69
+ # Only edit this one attribute
70
+ Url.console_update records, :only=>%w{description}
71
+ # Edit all the default attributes except this one
72
+ Url.console_update records, :except=>%w{description}
73
+
74
+ As mentioned above, any attribute can be edited. This means it's possible to edit associated
75
+ values as well as column values.
76
+
77
+ Say we have a Url that has many tags and accessor methods to edit them ie tag_list() and
78
+ tag_list=():
79
+
80
+ @url.tag_list = ['tag1', 'tag2']
81
+ @url.save
82
+ @url.tag_list # =>['tag1', 'tag2']
83
+
84
+ By simply passing 'tag_list' as another attribute to console_update() or can_console_update(),
85
+ we can edit these associated values:
86
+ class Url
87
+ can_console_update :only=>%w{column1 column2 tag_list}
88
+ end
89
+
90
+ Url.console_update records, :only=>%w{column1 column2 tag_list}
91
+
92
+ == Caveats
93
+ So should you be updating production records with this plugin? Yes and no.
94
+ Yes, if you're updating some simple string/text values. If editing more complex objects
95
+ ie non-string objects and associated objects, try edge cases to ensure the updates work as expected.
96
+ Although this plugin already comes with decent tests, I'm always open to patches for edge cases I
97
+ may have missed.
98
+
99
+ == Motivation
100
+ The need for editing speed in my {console-based project}[http://github.com/cldwalker/tag-tree].
101
+
102
+ == Issues/Bugs
103
+ Please report them {on github}[http://github.com/cldwalker/console_update/issues].
104
+
105
+ == Links
106
+ * http://tagaholic.me/2009/02/28/Console-Update-With-Your-Editor.html
107
+
108
+ == Todo
109
+
110
+ * Have a config file as an alternative configuration method which
111
+ doesn't clutter models with can_console_update() calls.
112
+ * Make ORM-agnostic.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ begin
5
+ require 'rcov/rcovtask'
6
+
7
+ Rcov::RcovTask.new do |t|
8
+ t.libs << 'test'
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ t.rcov_opts = ["-T -x '/Library/Ruby/*'"]
11
+ t.verbose = true
12
+ end
13
+ rescue LoadError
14
+ puts "Rcov not available. Install it for rcov-related tasks with: sudo gem install rcov"
15
+ end
16
+
17
+ begin
18
+ require 'jeweler'
19
+ Jeweler::Tasks.new do |s|
20
+ s.name = "console_update"
21
+ s.summary = "A rails plugin which allows you to edit your database records via the console and your favorite editor."
22
+ s.description = "Updates records from the console via your preferred editor. You can update a record's columns as well as any attribute that has accessor methods. Records are edited via a temporary file and once saved, the records are updated. Records go through a filter before and after editing the file. Yaml is the default filter, but you can define your own filters."
23
+ s.email = "gabriel.horner@gmail.com"
24
+ s.homepage = "http://tagaholic.me/console_update/"
25
+ s.authors = ["Gabriel Horner"]
26
+ s.rubyforge_project = 'tagaholic'
27
+ s.has_rdoc = true
28
+ s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
29
+ s.files = FileList["Rakefile", "VERSION.yml", "README.rdoc", "init.rb", "LICENSE.txt", "{rails,lib,test}/**/*"]
30
+ end
31
+
32
+ rescue LoadError
33
+ puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
34
+ end
35
+
36
+ Rake::TestTask.new do |t|
37
+ t.libs << 'lib'
38
+ t.pattern = 'test/**/*_test.rb'
39
+ t.verbose = false
40
+ end
41
+
42
+ Rake::RDocTask.new do |rdoc|
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = 'test'
45
+ rdoc.options << '--line-numbers' << '--inline-source'
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
49
+
50
+ task :default => :test
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 2
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'rails', 'init.rb')
@@ -0,0 +1,16 @@
1
+ require 'yaml'
2
+
3
+ module ConsoleUpdate
4
+ class Filter
5
+ module Yaml
6
+ def string_to_hashes(string)
7
+ YAML::load(string)
8
+ end
9
+
10
+ def hashes_to_string(hashes)
11
+ hashes.to_yaml
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,40 @@
1
+ require 'console_update/filter/yaml'
2
+
3
+ module ConsoleUpdate
4
+ # A filter converts database records to a string and vice versa.
5
+ # A database record is represented as a hash of attributes with stringified keys.
6
+ # Each hash should have an id attribute to map it to its database record.
7
+ # To create your own filter, create a module in the ConsoleUpdate::Filter namespace.
8
+ # Although the name of the module can be anything, if only the first letter is capitalized
9
+ # then a simple lowercase name of the filter can be used with ConsoleUpdate.filter() (ie :yaml
10
+ # instead of Yaml). For now, new filters have to be manually loaded/required.
11
+ #
12
+ # A filter should have two methods defined, string_to_hashes() and hashes_to_string().
13
+ # For a good example of a filter see ConsoleUpdate::Filter::Yaml.
14
+ #
15
+ class Filter
16
+ class AbstractMethodError < StandardError; end
17
+ class FilterNotFoundError < StandardError; end
18
+
19
+ # Creates a filter given a type.
20
+ def initialize(filter_type)
21
+ @filter_type = filter_type
22
+ begin
23
+ filter_module = self.class.const_get(filter_type.to_s.gsub(/^([a-z])/) {|e| $1.upcase })
24
+ self.extend(filter_module)
25
+ rescue NameError
26
+ raise FilterNotFoundError
27
+ end
28
+ end
29
+
30
+ # Takes an an array of hashes representing database records and converts them to a string for editing.
31
+ def hashes_to_string(hashes)
32
+ raise AbstractMethodError
33
+ end
34
+
35
+ # Takes the string from the updated file and converts back to an array of hashes.
36
+ def string_to_hashes(string)
37
+ raise AbstractMethodError
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module ConsoleUpdate
2
+
3
+ # Adds a global scoped method, console_update(), which can be chained
4
+ # to the end of any named scopes.
5
+ # For example, if Url has a named scope :tagged_with:
6
+ # Url.tagged_with('physics').console_update
7
+ def self.enable_named_scope
8
+ ActiveRecord::NamedScope::Scope.send :include, NamedScope
9
+ end
10
+
11
+ module NamedScope #:nodoc:
12
+ def console_update(options={})
13
+ records = send(:proxy_found)
14
+ unless records.empty?
15
+ records[0].class.console_update(records, options)
16
+ else
17
+ []
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,149 @@
1
+ current_dir = File.dirname(__FILE__)
2
+ $:.unshift(current_dir) unless $:.include?(current_dir) || $:.include?(File.expand_path(current_dir))
3
+ require 'tempfile'
4
+ require 'console_update/named_scope'
5
+ require 'console_update/filter'
6
+
7
+ module ConsoleUpdate
8
+ class <<self; attr_accessor(:filter, :editor); end
9
+ def self.included(base) #:nodoc:
10
+ @filter = :yaml
11
+ @editor = ENV["EDITOR"]
12
+ base.extend ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ # Enable a model to be updated via the console and an editor. By default editable
17
+ # attributes are columns with text, boolean or integer-like values.
18
+ # ==== Options:
19
+ # [:only] Sets these attributes as the default editable attributes.
20
+ # [:except] Sets the default editable attributes as normal except for these attributes.
21
+ # [:editor] Overrides global editor for just this model.
22
+ def can_console_update(options={})
23
+ cattr_accessor :console_editor
24
+ self.console_editor = options[:editor] || ConsoleUpdate.editor
25
+
26
+ cattr_accessor :default_editable_attributes
27
+ if options[:only]
28
+ self.default_editable_attributes = options[:only]
29
+ elsif options[:except]
30
+ self.default_editable_attributes = self.column_names.select {|e| !options[:except].include?(e) }
31
+ else
32
+ self.default_editable_attributes = get_default_editable_attributes
33
+ end
34
+
35
+ extend SingletonMethods
36
+ send :include, InstanceMethods
37
+ end
38
+
39
+ private
40
+ def default_types_to_exclude
41
+ [:datetime, :timestamp, :binary, :time, :timestamp]
42
+ end
43
+
44
+ def get_default_editable_attributes
45
+ self.columns.select {|e| !default_types_to_exclude.include?(e.type) }.map(&:name)
46
+ end
47
+ end
48
+
49
+ module SingletonMethods
50
+ # This is the main method for updating records.
51
+ # All other update-like methods pass their options through here.
52
+ # ==== Options:
53
+ # [:only] Only edit these attributes.
54
+ # [:except] Edit default attributes except for these.
55
+ # Examples:
56
+ # records = Url.all :limit=>10
57
+ # Url.console_update records
58
+ # Url.console_update records, :only=>%w{column1}
59
+ # Url.console_update records, :except=>%w{column1}
60
+ def console_update(records, options={})
61
+ begin
62
+ editable_attributes_array = records.map {|e| e.console_editable_attributes(options) }
63
+ editable_string = filter.hashes_to_string(editable_attributes_array)
64
+ new_attributes_array = editor_update(editable_string)
65
+ records.each do |record|
66
+ if (record_attributes = new_attributes_array.detect {|e| e['id'] == record.id })
67
+ record.update_console_attributes(record_attributes)
68
+ end
69
+ end
70
+ rescue ConsoleUpdate::Filter::AbstractMethodError
71
+ puts "Undefined filter method for #{ConsoleUpdate::filter} filter"
72
+ rescue Test::Unit::AssertionFailedError=>e
73
+ raise e
74
+ rescue Exception=>e
75
+ puts "Some record(s) didn't update because of this error: #{e}"
76
+ ensure
77
+ #this attribute should only last duration of method
78
+ reset_editable_attribute_names
79
+ end
80
+ end
81
+
82
+ def filter #:nodoc:
83
+ @filter ||= ConsoleUpdate::Filter.new(ConsoleUpdate.filter)
84
+ end
85
+
86
+ # Console updates a record given an id.
87
+ def find_and_console_update(id, options={})
88
+ console_update([find(id)], options)
89
+ end
90
+
91
+ # :stopdoc:
92
+ def editor_update(string)
93
+ tmpfile = Tempfile.new('console_update')
94
+ File.open(tmpfile.path, 'w+') {|f| f.write(string)}
95
+ system(console_editor, tmpfile.path)
96
+ updated_string = File.read(tmpfile.path)
97
+ filter.string_to_hashes(updated_string)
98
+ end
99
+
100
+ def reset_editable_attribute_names; @editable_attribute_names = nil ; end
101
+
102
+ def editable_attribute_names(options={})
103
+ unless @editable_attribute_names
104
+ @editable_attribute_names = if options[:only]
105
+ options[:only]
106
+ elsif options[:except]
107
+ default_editable_attributes - options[:except]
108
+ else
109
+ default_editable_attributes
110
+ end
111
+ end
112
+ @editable_attribute_names
113
+ end
114
+ # :startdoc:
115
+ end
116
+
117
+ module InstanceMethods
118
+ # Console updates the object.
119
+ def console_update(options={})
120
+ self.class.console_update([self], options)
121
+ end
122
+
123
+ # :stopdoc:
124
+ def update_console_attributes(new_attributes)
125
+ # delete if value is the same or is an attribute that isn't supposed to be edited
126
+ new_attributes.delete_if {|k,v|
127
+ attributes[k] == v || !self.class.editable_attribute_names.include?(k)
128
+ }
129
+ new_attributes.each do |k, v|
130
+ send("#{k}=", v)
131
+ end
132
+ save
133
+ end
134
+
135
+ def get_console_attributes(attribute_names)
136
+ attribute_names.inject({}) {|h,e|
137
+ h[e] = attributes.has_key?(e) ? attributes[e] : send(e)
138
+ h
139
+ }
140
+ end
141
+
142
+ def console_editable_attributes(options)
143
+ fresh_attributes = get_console_attributes(self.class.editable_attribute_names(options))
144
+ fresh_attributes['id'] ||= self.id
145
+ fresh_attributes
146
+ end
147
+ #:startdoc:
148
+ end
149
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'console_update'
2
+ ActiveRecord::Base.send :include, ConsoleUpdate
@@ -0,0 +1,133 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class ConsoleUpdateTest < Test::Unit::TestCase
4
+ def stub_editor_update_with_records(new_records, options={})
5
+ Bird.stub!(:system) { |editor, file|
6
+ if options[:expected_attributes]
7
+ YAML::load_file(file)[0].keys.sort.should == options[:expected_attributes]
8
+ end
9
+ File.open(file, 'w+') {|f| f.write(new_records.to_yaml)}
10
+ }
11
+ end
12
+
13
+ def create_big_bird(attributes={})
14
+ @big_bird = Bird.create({:name=>"big bird"}.update(attributes))
15
+ end
16
+
17
+ context "can_console_update" do
18
+ test "sets default_editable_attributes" do
19
+ Bird.column_names.include?('bin').should be(true)
20
+ Bird.default_editable_attributes.empty?.should be(false)
21
+ Bird.default_editable_attributes.include?('bin').should be(false)
22
+ end
23
+
24
+ test "sets default_editable_attributes with only option" do
25
+ Bird.can_console_update :only=>['description']
26
+ Bird.default_editable_attributes.should == ['description']
27
+ end
28
+
29
+ test "sets default_editable_attributes with except option" do
30
+ Bird.can_console_update :except=>['description']
31
+ Bird.default_editable_attributes.sort.should == ["bin", "created_at", "id", "name", "nickname", "updated_at"]
32
+ end
33
+
34
+ test "sets console_editor with editor option" do
35
+ Bird.can_console_update :editor=>'vi'
36
+ Bird.console_editor.should == 'vi'
37
+ end
38
+ end
39
+
40
+ context "console_update" do
41
+ before(:all) {|e| Bird.can_console_update }
42
+ before(:each) {|e| Bird.delete_all }
43
+
44
+ test "updates multiple records" do
45
+ create_big_bird
46
+ @dodo = Bird.create(:name=>"dodo")
47
+ new_records = [@big_bird.attributes.update('name'=>'big birded'), @dodo.attributes.update('name'=>'dudu')]
48
+ stub_editor_update_with_records(new_records)
49
+ Bird.console_update([@big_bird, @dodo])
50
+ @big_bird.reload.name.should == 'big birded'
51
+ @dodo.reload.name.should == 'dudu'
52
+ end
53
+
54
+ test "doesn't modify missing attributes" do
55
+ create_big_bird(:nickname=>'doofus')
56
+ # this record is missing all it's attributes except name
57
+ new_records = [{'name'=>'big birded', 'id'=>@big_bird.id}]
58
+ stub_editor_update_with_records(new_records)
59
+ Bird.console_update([@big_bird])
60
+ @big_bird.reload.nickname.should == 'doofus'
61
+ @big_bird.name.should == 'big birded'
62
+ end
63
+
64
+ test "updates attr_protected column" do
65
+ create_big_bird
66
+ new_records = [@big_bird.attributes.update('description'=>"it's big")]
67
+ stub_editor_update_with_records(new_records)
68
+ Bird.console_update([@big_bird])
69
+ @big_bird.reload.description.should == "it's big"
70
+ end
71
+
72
+ test "rescues exception" do
73
+ create_big_bird
74
+ Bird.stub!(:system) {|*args| raise "error" }
75
+ capture_stdout {
76
+ Bird.console_update([@big_bird])
77
+ }
78
+ end
79
+
80
+ test "via instance method" do
81
+ create_big_bird
82
+ stub_editor_update_with_records([@big_bird.attributes.update('name'=>'small bird')])
83
+ @big_bird.console_update
84
+ @big_bird.reload.name.should == 'small bird'
85
+ end
86
+
87
+ test "via find_and_console_update" do
88
+ create_big_bird
89
+ stub_editor_update_with_records([@big_bird.attributes.update('name'=>'small bird')])
90
+ Bird.find_and_console_update(@big_bird.id)
91
+ @big_bird.reload.name.should == 'small bird'
92
+ end
93
+
94
+ test "with only option only edits those columns" do
95
+ create_big_bird(:description=>"something")
96
+ stub_editor_update_with_records([@big_bird.attributes.update('name'=>'big birded')], :expected_attributes=>['id', 'name'])
97
+ Bird.console_update([@big_bird], :only=>['name'])
98
+ @big_bird.reload.name.should == 'big birded'
99
+ end
100
+
101
+ test "with except option edits all columns except those columns" do
102
+ create_big_bird(:description=>"something")
103
+ expected_attributes = ["id", "name", "nickname"]
104
+ stub_editor_update_with_records([@big_bird.attributes.update('name'=>'big birded')], :expected_attributes=>expected_attributes)
105
+ Bird.console_update([@big_bird], :except=>['description'])
106
+ @big_bird.reload.name.should == 'big birded'
107
+ end
108
+
109
+ test "sets a non column attribute" do
110
+ create_big_bird
111
+ stub_editor_update_with_records([{'tag_list'=>["yellow"], 'id'=>@big_bird.id}], :expected_attributes=>["id","tag_list"])
112
+ Bird.console_update([@big_bird], :only=>["tag_list"] )
113
+ @big_bird.tag_list.should == ['yellow']
114
+ end
115
+
116
+ test "updates and deletes extra attributes added in editor" do
117
+ create_big_bird
118
+ stub_editor_update_with_records([{'name'=>'big birded','nickname'=>'doofird', 'id'=>@big_bird.id}])
119
+ Bird.console_update([@big_bird], :only=>['name'])
120
+ @big_bird.reload.nickname.should_not == 'doofird'
121
+ end
122
+ end
123
+
124
+ test "editable_attribute_names expire per console_update" do
125
+ create_big_bird
126
+ stub_editor_update_with_records([{'name'=>'big birded', 'id'=>@big_bird.id}], :expected_attributes=>["id", "name"])
127
+ Bird.console_update([@big_bird], :only=>['name'])
128
+ @big_bird.instance_eval("@editable_attribute_names").should == nil
129
+ #these expected_attributes would fail if @editable_attribute_names wasn't reset for each console_update()
130
+ stub_editor_update_with_records([{'description'=>'big birded','id'=>@big_bird.id}], :expected_attributes=>["description", "id"])
131
+ Bird.console_update([@big_bird], :only=>['description'])
132
+ end
133
+ end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ #test filter
4
+ module ConsoleUpdate
5
+ class Filter
6
+ module Test
7
+ end
8
+ end
9
+ end
10
+
11
+ class ConsoleUpdate::FilterTest < Test::Unit::TestCase
12
+ test "incorrect filter name raises FilterNotFoundError" do
13
+ assert_raises(ConsoleUpdate::Filter::FilterNotFoundError) {
14
+ ConsoleUpdate::Filter.new(:blah)
15
+ }
16
+ end
17
+
18
+ test "filter without proper methods raises AbstractMethodError" do
19
+ assert_raises(ConsoleUpdate::Filter::AbstractMethodError) {
20
+ ConsoleUpdate::Filter.new(:test).string_to_hashes('blah')
21
+ }
22
+ end
23
+
24
+ test "extends filter for a non-lowercase filter name correctly" do
25
+ filter_meta_class = ConsoleUpdate::Filter.new("Test").instance_eval("class<<self; self;end")
26
+ filter_meta_class.ancestors.include?(ConsoleUpdate::Filter::Test).should be(true)
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class ConsoleUpdate::NamedScopeTest < Test::Unit::TestCase
4
+ test "chained console_update calls actual named_scope" do
5
+ big_bird = Bird.create({:name=>"big bird"})
6
+ Bird.stub!(:system) { |editor, file|
7
+ YAML::load_file(file)[0].keys.sort.should == Bird.default_editable_attributes.sort
8
+ }
9
+ ConsoleUpdate.enable_named_scope
10
+ Bird.just_big_bird.console_update
11
+ end
12
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,8 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :birds do |t|
3
+ t.string :name, :nickname
4
+ t.text :description
5
+ t.timestamps
6
+ t.binary :bin
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ require 'test/unit'
4
+ require 'context' #gem install jeremymcanally-context -s http://gems.github.com
5
+ require 'matchy' #gem install jeremymcanally-matchy -s http://gems.github.com
6
+ require 'stump' #gem install jeremymcanally-stump -s http://gems.github.com
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ require File.join(File.dirname(__FILE__), '..', 'init')
9
+
10
+ #Setup logger
11
+ require 'logger'
12
+ # ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "test.log"))
13
+ ActiveRecord::Base.logger = Logger.new(STDERR)
14
+ ActiveRecord::Base.logger.level = Logger::WARN
15
+
16
+ #Setup db
17
+ ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3', :database => ':memory:'}}
18
+ ActiveRecord::Base.establish_connection('sqlite3')
19
+
20
+ #Define schema
21
+ require File.join(File.dirname(__FILE__), 'schema')
22
+ class Bird < ActiveRecord::Base
23
+ named_scope :just_big_bird, :conditions=>{:name=>'big bird'}
24
+ attr_protected :description
25
+ attr_accessor :tag_list
26
+ can_console_update
27
+ end
28
+
29
+ class Test::Unit::TestCase
30
+ def capture_stdout(&block)
31
+ original_stdout = $stdout
32
+ $stdout = fake = StringIO.new
33
+ begin
34
+ yield
35
+ ensure
36
+ $stdout = original_stdout
37
+ end
38
+ fake.string
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: console_update
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Gabriel Horner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-22 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Updates records from the console via your preferred editor. You can update a record's columns as well as any attribute that has accessor methods. Records are edited via a temporary file and once saved, the records are updated. Records go through a filter before and after editing the file. Yaml is the default filter, but you can define your own filters.
17
+ email: gabriel.horner@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE.txt
24
+ - README.rdoc
25
+ files:
26
+ - LICENSE.txt
27
+ - README.rdoc
28
+ - Rakefile
29
+ - VERSION.yml
30
+ - init.rb
31
+ - lib/console_update.rb
32
+ - lib/console_update/filter.rb
33
+ - lib/console_update/filter/yaml.rb
34
+ - lib/console_update/named_scope.rb
35
+ - rails/init.rb
36
+ - test/console_update_test.rb
37
+ - test/filter_test.rb
38
+ - test/named_scope_test.rb
39
+ - test/schema.rb
40
+ - test/test_helper.rb
41
+ has_rdoc: true
42
+ homepage: http://tagaholic.me/console_update/
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --charset=UTF-8
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project: tagaholic
65
+ rubygems_version: 1.3.5
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: A rails plugin which allows you to edit your database records via the console and your favorite editor.
69
+ test_files:
70
+ - test/console_update_test.rb
71
+ - test/filter_test.rb
72
+ - test/named_scope_test.rb
73
+ - test/schema.rb
74
+ - test/test_helper.rb