active-record-binder 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/README.md +217 -0
  2. data/bin/arb +13 -0
  3. data/doc/Binder.html +150 -0
  4. data/doc/Binder/AR.html +1880 -0
  5. data/doc/Binder/Command.html +252 -0
  6. data/doc/Binder/Help.html +374 -0
  7. data/doc/Binder/Migrate.html +682 -0
  8. data/doc/Binder/Strategy.html +550 -0
  9. data/doc/Binder/Version.html +285 -0
  10. data/doc/Class.html +220 -0
  11. data/doc/CommandParser.html +268 -0
  12. data/doc/CommandParser/ParseError.html +123 -0
  13. data/doc/DeferedDelegator.html +414 -0
  14. data/doc/MigrationProcessError.html +123 -0
  15. data/doc/MigrationVersionError.html +123 -0
  16. data/doc/String.html +245 -0
  17. data/doc/_index.html +256 -0
  18. data/doc/class_list.html +53 -0
  19. data/doc/css/common.css +1 -0
  20. data/doc/css/full_list.css +57 -0
  21. data/doc/css/style.css +328 -0
  22. data/doc/file.README.html +300 -0
  23. data/doc/file_list.html +55 -0
  24. data/doc/frames.html +28 -0
  25. data/doc/index.html +300 -0
  26. data/doc/js/app.js +214 -0
  27. data/doc/js/full_list.js +173 -0
  28. data/doc/js/jquery.js +4 -0
  29. data/doc/method_list.html +348 -0
  30. data/doc/top-level-namespace.html +114 -0
  31. data/extras/cli_help.png +0 -0
  32. data/lib/active_record_binder.rb +21 -37
  33. data/lib/cli/command.rb +101 -0
  34. data/lib/cli/command_parser.rb +29 -0
  35. data/lib/cli/commands/commands.rb +4 -0
  36. data/lib/cli/commands/help.rb +35 -0
  37. data/lib/cli/commands/migrate.rb +77 -0
  38. data/lib/cli/commands/version.rb +16 -0
  39. data/lib/cli/core_ext.rb +21 -0
  40. data/lib/defered_delegator.rb +69 -0
  41. data/lib/version.rb +3 -0
  42. data/test/active_record_binder_test.rb +262 -0
  43. data/test/foo.sqlite3 +0 -0
  44. data/test/migrations.rb +29 -0
  45. data/test/minitest_helper.rb +15 -0
  46. data/test/mocks.rb +24 -0
  47. metadata +62 -5
@@ -0,0 +1,114 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.8.4.1
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" media="screen" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ hasFrames = window.top.frames.main ? true : false;
19
+ relpath = '';
20
+ framesUrl = "frames.html#!" + escape(window.location.href);
21
+ </script>
22
+
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
25
+
26
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
27
+
28
+
29
+ </head>
30
+ <body>
31
+ <div id="header">
32
+ <div id="menu">
33
+
34
+ <a href="_index.html">Index</a> &raquo;
35
+
36
+
37
+ <span class="title">Top Level Namespace</span>
38
+
39
+
40
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
41
+ </div>
42
+
43
+ <div id="search">
44
+
45
+ <a class="full_list_link" id="class_list_link"
46
+ href="class_list.html">
47
+ Class List
48
+ </a>
49
+
50
+ <a class="full_list_link" id="method_list_link"
51
+ href="method_list.html">
52
+ Method List
53
+ </a>
54
+
55
+ <a class="full_list_link" id="file_list_link"
56
+ href="file_list.html">
57
+ File List
58
+ </a>
59
+
60
+ </div>
61
+ <div class="clear"></div>
62
+ </div>
63
+
64
+ <iframe id="search_frame"></iframe>
65
+
66
+ <div id="content"><h1>Top Level Namespace
67
+
68
+
69
+
70
+ </h1>
71
+
72
+ <dl class="box">
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+ </dl>
82
+ <div class="clear"></div>
83
+
84
+ <h2>Defined Under Namespace</h2>
85
+ <p class="children">
86
+
87
+
88
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Binder.html" title="Binder (module)">Binder</a></span>, <span class='object_link'><a href="CommandParser.html" title="CommandParser (module)">CommandParser</a></span>, <span class='object_link'><a href="DeferedDelegator.html" title="DeferedDelegator (module)">DeferedDelegator</a></span>
89
+
90
+
91
+
92
+ <strong class="classes">Classes:</strong> <span class='object_link'><a href="Class.html" title="Class (class)">Class</a></span>, <span class='object_link'><a href="MigrationProcessError.html" title="MigrationProcessError (class)">MigrationProcessError</a></span>, <span class='object_link'><a href="MigrationVersionError.html" title="MigrationVersionError (class)">MigrationVersionError</a></span>, <span class='object_link'><a href="String.html" title="String (class)">String</a></span>
93
+
94
+
95
+ </p>
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+ </div>
106
+
107
+ <div id="footer">
108
+ Generated on Thu Feb 21 02:06:16 2013 by
109
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
110
+ 0.8.4.1 (ruby-1.9.3).
111
+ </div>
112
+
113
+ </body>
114
+ </html>
Binary file
@@ -1,33 +1,9 @@
1
1
  require 'active_record'
2
2
  require 'active_support/core_ext/string'
3
- require 'forwardable'
4
-
5
- # Private: A simple module that delegates classes methods when needed, keeping the calls in memory.
6
- module DifferedDelegator
7
- def register_delegators *args
8
- args.each do |delegator|
9
- delegator = delegator.to_s
10
- module_eval %Q{
11
- def self.#{delegator} *parameters
12
- @delegators ||= []
13
- @delegators << { name: :#{delegator}, params: parameters }
14
- end
15
- }
16
- end
17
- end
18
-
19
- def delegate_to klass_or_object
20
- @delegators.each do |data|
21
- unless data.empty?
22
- name = data[:name]
23
- args = data[:params]
24
- klass_or_object.send(name, *args)
25
- end
26
- end
27
- end
28
- end
3
+ require_relative './defered_delegator'
29
4
 
30
5
  class MigrationVersionError < Exception; end
6
+ class MigrationProcessError < Exception; end
31
7
 
32
8
  # Public: Namespace containing classes to create binder to.
33
9
  # A binder is simply a tool to use for Databases plugs or adaptors building.
@@ -73,7 +49,7 @@ module Binder
73
49
  # ARMySqlPlug::connection # => { :user => 'Foo', :password => 'Bar', :host => 'localhost' }
74
50
  #
75
51
  class AR
76
- extend DifferedDelegator
52
+ extend DeferedDelegator
77
53
 
78
54
  attr_reader :table_name, :table
79
55
  register_delegators :has_many, :has_one, :has_and_belongs_to_many, :belongs_to
@@ -93,10 +69,10 @@ module Binder
93
69
  # Returns an instance of the binder's class.
94
70
  def initialize table_name
95
71
  @table_name = table_name
96
- this = self.class
97
72
 
98
73
  # Retrieves or Create the ActiveRecord::Base subclass that will match the table.
99
- table = meta_def_ar_class(this.database, this.adapter, this.connection)
74
+ table = meta_def_ar_class
75
+
100
76
  # Handle ActiveRecord::Base delegation, to ensure painless associations
101
77
  self.class.delegate_to table
102
78
 
@@ -112,7 +88,7 @@ module Binder
112
88
  # _params - the connection parameters
113
89
  #
114
90
  # Returns the class object.
115
- def meta_def_ar_class(_database, _adapter, _params)
91
+ def meta_def_ar_class
116
92
  klass = table_name.to_s.classify
117
93
  binder = self.class
118
94
 
@@ -123,13 +99,7 @@ module Binder
123
99
  binder.const_set(klass,
124
100
  Class.new(ActiveRecord::Base) do # class `TableName` < ActiveRecord::Base
125
101
  singleton_class.send(:define_method, :connect) do # def self.connect
126
- opts = { database: _database, adapter: _adapter }.merge(_params)
127
- # We ensure we have a string for the adapter
128
- opts[:adapter] = opts[:adapter].to_s
129
- # If we have a symbol for the database and the adapter is sqlite3, we create a string and add '.sqlite3' to the end
130
- opts[:database] = "#{opts[:database]}.sqlite3" if opts[:adapter] == 'sqlite3' and opts[:database].class == Symbol
131
-
132
- ActiveRecord::Base.establish_connection(opts)
102
+ ActiveRecord::Base.establish_connection(binder.connection_data)
133
103
  end # end
134
104
  end) # end
135
105
  end #if
@@ -194,6 +164,18 @@ module Binder
194
164
  end
195
165
  alias :connect_with :connection
196
166
 
167
+ # Public: Retrieves a clean set of connection data to establish a connection
168
+ #
169
+ # Returns a Hash.
170
+ def connection_data
171
+ opts = { database: self.database, adapter: self.adapter }.merge(self.connection)
172
+ # We ensure we have a string for the adapter
173
+ opts[:adapter] = opts[:adapter].to_s
174
+ # If we have a symbol for the database and the adapter is sqlite3, we create a string and add '.sqlite3' to the end
175
+ opts[:database] = "#{opts[:database]}.sqlite3" if opts[:adapter] == 'sqlite3' and opts[:database].class == Symbol
176
+ opts
177
+ end
178
+
197
179
  # Public: Retrieves de default database
198
180
  #
199
181
  # Returns a the content of ENV\['APP_DB'\].
@@ -324,6 +306,8 @@ module Binder
324
306
  #
325
307
  # Returns Nothing.
326
308
  def __create_meta_data_table_for schema
309
+ ActiveRecord::Base.establish_connection(self.connection_data) unless schema.connected?
310
+
327
311
  # Clears the table cache for the schema (remove TableDoesNotExists if a table actually exists)
328
312
  schema.clear_cache!
329
313
 
@@ -0,0 +1,101 @@
1
+ require_relative './command_parser'
2
+ require_relative './core_ext'
3
+
4
+ module Binder
5
+ # Public: A Class to create Command Line Tool commands.
6
+ #
7
+ # Examples
8
+ #
9
+ # Binder::Command.new ARGV
10
+ # # => Will execute any command passed in ARGV
11
+ # # A command is just a ruby Class, subclassing the Binder::Strategy Class.
12
+ # #
13
+ # # When you do :
14
+ # Binder::Command.new "migrate --version 1.1"
15
+ # # => The Binder::Migrate class is instanciated and it's execute method is called.
16
+ class Command
17
+ def initialize args
18
+ raise CommandParser::ParseError.new("No command specified.") if args.empty?
19
+
20
+ # ['--migrate' 'param'] => 'migrate'
21
+ command = args.shift.gsub(/\-/, '')
22
+
23
+ strategy = Binder::const_get(command.capitalize).new
24
+ puts strategy.execute args
25
+ end
26
+ end
27
+
28
+ # Public: A Strategy is a way to declare a specific command.
29
+ # You need to subclass the Binder::Strategy class and declare an `execute` method and a `description` method.
30
+ #
31
+ # Examples
32
+ #
33
+ # class Migrate < Binder::Strategy
34
+ # def execute args
35
+ # # Parse args and do migration stuff
36
+ # "Migration Done." # <= Binder::Command.new automaticaly renders the return value of an `execute` call
37
+ # end
38
+ # #
39
+ # # The description call is mainly used by the `help` command.
40
+ # def description
41
+ # "Easier Migration. Use the " + "--directory".colorize(:orange) + " option to pass in a directory to load."
42
+ # # Yeah, notice the "colorize" String method that helps you write a string in a beatiful color.
43
+ # end
44
+ # end
45
+ class Strategy
46
+ # Public: creates an alias for the command.
47
+ #
48
+ # Examples
49
+ #
50
+ # class Migrate < Binder::Strategy
51
+ # # def execute...
52
+ # # def description...
53
+ # #
54
+ # alias_class :M
55
+ # # => This will create an alias :M class and this the existance of the corresponding "-m" command. (`arb --migrate` or `arb -m`, now)
56
+ # end
57
+ def self.alias_class _alias
58
+ Binder::const_set(_alias, Class.new(self))
59
+ end
60
+
61
+ # Public: returns the String size of the biggest command or option group.
62
+ #
63
+ # Returns a Numeric.
64
+ def justify_size
65
+ if @options.nil?
66
+ Binder::Strategy.subclasses.map(&:to_s).group_by(&:size).max.flatten.first - "Binder".length
67
+ else
68
+ @options.group_by(&:size).max.flatten.first
69
+ end
70
+ end
71
+
72
+ # Public: merge options and aliases "-m, --migrate, -h, --help", etc...
73
+ #
74
+ # Uses an array of commands : ["-h" "-m", "--help", "--migrate"],
75
+ #
76
+ # and concatenate the aliases : ["-m, --migrate", "-h, --help"]
77
+ #
78
+ # Returns an Array of merged aliases.
79
+ def merge_options_aliases
80
+ commands = Binder::Strategy.subclasses.map { |command| command.to_s.gsub!('Binder::', '').downcase }.sort
81
+ previous_cmd = ""
82
+ options = []
83
+
84
+ commands.each_with_index do |cmd, i|
85
+ if cmd.length == 1
86
+ previous_cmd = cmd
87
+ elsif not previous_cmd.empty?
88
+ cmd_prefixes = [previous_cmd.length == 1 ? '-' : '--', cmd.length == 1 ? '-' : '--']
89
+ options << "#{cmd_prefixes.first}#{previous_cmd}, #{cmd_prefixes.last}#{cmd}"
90
+ previous_cmd = ""
91
+ end
92
+ end
93
+ @options = options
94
+ end
95
+
96
+ # Public: provides a default description if not defined.
97
+ #
98
+ # Returns a "No description found" String.
99
+ def description; "No description found" end
100
+ end
101
+ end
@@ -0,0 +1,29 @@
1
+ module CommandParser
2
+ # Public: Parse options and creates an array of hash data for each.
3
+ #
4
+ # args - An Array of options and parameters to parse.
5
+ #
6
+ # Examples
7
+ #
8
+ # CommandParser::parse_options "--directory lib/ migrations/ --adapter MySqlPlug"
9
+ # # => [{ option: 'directory', option_args: ["lib/", "migrations/"] }, { option: 'adapter', option_args: ["MySqlPlug"] }]
10
+ #
11
+ # Returns a parsed array.
12
+ def self.parse_options(args)
13
+ parsed = []
14
+ last_option = ""
15
+ args.each do |chunk|
16
+ if chunk[/^-*/].empty?
17
+ raise CommandParser::ParseError.new("Arguments given but no option has been specified.") if last_option.empty?
18
+ cur_cmd = parsed.last
19
+ cur_cmd[:options_args] ||= []
20
+ cur_cmd[:options_args] << chunk
21
+ else
22
+ parsed << { option: chunk.sub(/^-*/, '') } and last_option = chunk
23
+ end
24
+ end
25
+ parsed
26
+ end
27
+
28
+ class CommandParser::ParseError < StandardError; end
29
+ end
@@ -0,0 +1,4 @@
1
+ require_relative '../command.rb'
2
+ require_relative './migrate'
3
+ require_relative './version'
4
+ require_relative './help'
@@ -0,0 +1,35 @@
1
+ require_relative './version'
2
+
3
+ module Binder
4
+ # Public: [Command] Displays the help for `arb`. Used via the `arb` command.
5
+ class Help < Binder::Strategy
6
+ def execute args
7
+ instructions = []
8
+ help_topics = merge_options_aliases
9
+
10
+ help_topics.each do |topic|
11
+ binder = Module.nesting.last
12
+
13
+ cmd_name = topic.split(" ").last.gsub(/^-*/, '').capitalize
14
+ command = binder.const_get(cmd_name)
15
+ cmd_description = command.new.description
16
+
17
+ instructions << format_instruction(topic.to_s, cmd_description)
18
+ end
19
+
20
+ Version.new.execute(nil) + "\n" +
21
+ instructions.join("\n")
22
+ end
23
+
24
+
25
+ def format_instruction name, description
26
+ " #{name.ljust(justify_size).colorize(:green)} - #{description}"
27
+ end
28
+
29
+ def description
30
+ "Displays the help"
31
+ end
32
+
33
+ alias_class :H
34
+ end
35
+ end
@@ -0,0 +1,77 @@
1
+ require 'active_record_binder'
2
+
3
+ module Binder
4
+ # Public: [Command] Runs your migrations. Used via the `arb` command.
5
+ class Migrate < Binder::Strategy
6
+ def execute args
7
+ commands = CommandParser::parse_options(args)
8
+ commands.each do |c|
9
+ option = c[:option]
10
+ args = c[:options_args]
11
+ self.send(option.to_sym, args)
12
+ end
13
+ "\nDone."
14
+ end
15
+
16
+ def version version
17
+ puts "Migrating toward version #{version.first.to_f.to_s.colorize(:orange)}"
18
+ @version ||= version.first.to_f
19
+ end
20
+ alias :v :version
21
+ alias :to :version
22
+
23
+ def plug args
24
+ args.each do |klass|
25
+ raise MigrationProcessError.new("Plug #{klass.colorize(:red)} not found.") unless Object.const_defined?(klass)
26
+ Object.const_get(klass).migrate @version
27
+ end
28
+ end
29
+ alias :a :plug
30
+ alias :adapter :plug
31
+ alias :adaptor :plug
32
+
33
+ def directory blobs, recursively = false
34
+ blobs.each do |blob|
35
+ blob = File::absolute_path(blob)
36
+ raise CommandParser::ParseError.new("Invalid argument : no such file or directory #{blob}.") unless File::exists?(blob)
37
+
38
+ if File::directory?(blob)
39
+ files = Dir.glob("#{blob}/*")
40
+ files.each do |file|
41
+ if recursively == true && File::directory?(file)
42
+ directory [file], true
43
+ end
44
+ # Requires the file and logs it on stdout
45
+ puts _require_path(file) unless File::directory?(file)
46
+ end
47
+ else
48
+ puts _require_path(blob)
49
+ end
50
+ end
51
+ end
52
+ def _require_path path; "Requiring #{path.colorize(:green)}: #{require(path).to_s.colorize(:orange)}" end
53
+ alias :d :directory
54
+ alias :file :directory
55
+ alias :f :directory
56
+
57
+ def recursive blobs
58
+ directory blobs, true
59
+ end
60
+ alias :r :recursive
61
+
62
+ def description
63
+ indent = ' ' * (justify_size * 2)
64
+
65
+ str = []
66
+ str << "Migrate to the specified version"
67
+ str << "#{indent}" + "-v".colorize(:rose) + ", --version".colorize(:rose) + ", --to".colorize(:rose) + " - Version we want to migrate to"
68
+ str << "#{indent}" + "-d".colorize(:rose) + ", --directory".colorize(:rose) + " - Allow to specify a set of directories holding your migrations"
69
+ str << "#{indent}" + "-r".colorize(:rose) + ", --recursive".colorize(:rose) + " - Same as `--directory` but will search recursively in the directories"
70
+ str << "#{indent}" + "-f".colorize(:rose) + ", --file".colorize(:rose) + " - Allow to specify a set of files holding your migrations"
71
+ str << "#{indent}" + "-a".colorize(:rose) + ", --adapter".colorize(:rose) + ", --plug".colorize(:rose) + " - Specify a Binder class on which to run the migration"
72
+ str.join("\n")
73
+ end
74
+
75
+ alias_class :M
76
+ end
77
+ end