console_update 0.1.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/LICENSE.txt +22 -0
- data/README.rdoc +112 -0
- data/Rakefile +50 -0
- data/VERSION.yml +4 -0
- data/init.rb +1 -0
- data/lib/console_update/filter/yaml.rb +16 -0
- data/lib/console_update/filter.rb +40 -0
- data/lib/console_update/named_scope.rb +21 -0
- data/lib/console_update.rb +149 -0
- data/rails/init.rb +2 -0
- data/test/console_update_test.rb +133 -0
- data/test/filter_test.rb +28 -0
- data/test/named_scope_test.rb +12 -0
- data/test/schema.rb +8 -0
- data/test/test_helper.rb +40 -0
- metadata +74 -0
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
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'rails', 'init.rb')
|
@@ -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,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
|
data/test/filter_test.rb
ADDED
@@ -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
data/test/test_helper.rb
ADDED
@@ -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
|