active-record-binder 1.1.0 → 1.2.0

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