active-record-binder 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/active_record_binder.rb +337 -0
- metadata +47 -0
@@ -0,0 +1,337 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support/core_ext/string'
|
3
|
+
|
4
|
+
class MigrationVersionError < Exception; end
|
5
|
+
|
6
|
+
# Public: Namespace containing classes to create binder to.
|
7
|
+
# A binder is simply a tool to use for Databases plugs or adaptors building.
|
8
|
+
module Binder
|
9
|
+
|
10
|
+
# Public: Active Record Binder class. You need to inherit from it to create your Plug or Adaptor.
|
11
|
+
#
|
12
|
+
# Examples
|
13
|
+
#
|
14
|
+
# class ARSqlitePlug < Binder::AR
|
15
|
+
# database 'db.sqlite3'
|
16
|
+
# adapter :sqlite3
|
17
|
+
#
|
18
|
+
# # Your methods
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # Or
|
22
|
+
# class ARMySqlPlug < Binder::AR
|
23
|
+
# database 'db'
|
24
|
+
# adapter :mysql
|
25
|
+
# connect_with user: 'Foo', password: 'Bar', host: 'localhost'
|
26
|
+
#
|
27
|
+
# # Your methods
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # You need to initialize the plug by passing it a table to handle
|
31
|
+
# plug = ARSqlitePlug.new :table_for_foo
|
32
|
+
# foo = plug.table # => the TableForFoo class, which inherit from ActiveRecord::Base.
|
33
|
+
# # Foo is an active record object linked to a table, you can use it as you would usually.
|
34
|
+
# foo.first
|
35
|
+
#
|
36
|
+
# # Furthermore, the Binder::AR class, has some interesting class methods :
|
37
|
+
#
|
38
|
+
# Binder::AR::default_database # => ENV['APP_DB'] # This value would be used if no database had been set during the plus creation.
|
39
|
+
# Binder::AR::database # => Returns the current database used by the Binder in general (Would default to ENV['APP_DB'] if not specified)
|
40
|
+
# ARSqlitePlug::database # => :db (Works the same and returns this Plug Class's database parameters)
|
41
|
+
#
|
42
|
+
# # The same goes for the adapters :
|
43
|
+
# Binder::AR::default_adapter # => :sqlite3
|
44
|
+
# ARMySqlPlug::adapter # => :mysql
|
45
|
+
#
|
46
|
+
# # And connection parameters
|
47
|
+
# Binder::AR::connection # => {}
|
48
|
+
# ARMySqlPlug::connection # => { :user => 'Foo', :password => 'Bar', :host => 'localhost' }
|
49
|
+
#
|
50
|
+
class AR
|
51
|
+
attr_reader :table_name, :table
|
52
|
+
|
53
|
+
# Public: Returns a new instance of the binder's class.
|
54
|
+
# It also automaticaly establish a connection with the database throught the specified adapter,
|
55
|
+
# but prevents trying to reconnect if the connection is already established.
|
56
|
+
#
|
57
|
+
# table_name - A String or Symbol representing the table to which we want to be bound.
|
58
|
+
#
|
59
|
+
# Examples
|
60
|
+
#
|
61
|
+
# class Plug < Binder::AR; end
|
62
|
+
# plug = Plug.new :articles
|
63
|
+
# plug.table.find # [<Article#1>, <Article#2>] # Works, the connection has been established.
|
64
|
+
#
|
65
|
+
# Returns an instance of the binder's class.
|
66
|
+
def initialize table_name
|
67
|
+
@table_name = table_name
|
68
|
+
this = self.class
|
69
|
+
|
70
|
+
table = meta_def_ar_class(this.database, this.adapter, this.connection)
|
71
|
+
table.connect unless table.connected?
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
# Private : creates an ActiveRecord::Base subclass matching the table name.
|
76
|
+
#
|
77
|
+
# _database - the database parameters
|
78
|
+
# _adapter - the adapter parameters
|
79
|
+
# _params - the connection parameters
|
80
|
+
#
|
81
|
+
# Returns the class object.
|
82
|
+
def meta_def_ar_class(_database, _adapter, _params)
|
83
|
+
klass = table_name.to_s.classify
|
84
|
+
binder = self.class
|
85
|
+
|
86
|
+
@table =
|
87
|
+
if binder.const_defined? klass
|
88
|
+
binder.const_get klass
|
89
|
+
else
|
90
|
+
binder.const_set(klass,
|
91
|
+
Class.new(ActiveRecord::Base) do # class `TableName` < ActiveRecord::Base
|
92
|
+
singleton_class.send(:define_method, :connect) do # def self.connect
|
93
|
+
opts = { database: _database, adapter: _adapter }.merge(_params)
|
94
|
+
# We ensure we have a string for the adapter
|
95
|
+
opts[:adapter] = opts[:adapter].to_s
|
96
|
+
# If we have a symbol for the database and the adapter is sqlite3, we create a string and add '.sqlite3' to the end
|
97
|
+
opts[:database] = "#{opts[:database]}.sqlite3" if opts[:adapter] == 'sqlite3' and opts[:database].class == Symbol
|
98
|
+
|
99
|
+
ActiveRecord::Base.establish_connection(opts)
|
100
|
+
end # end
|
101
|
+
end) # end
|
102
|
+
end #if
|
103
|
+
end
|
104
|
+
|
105
|
+
class << self
|
106
|
+
# Public: Set the current database
|
107
|
+
#
|
108
|
+
# db - the database, as a String or Symbol. (default: Binder::AR.default_database)
|
109
|
+
#
|
110
|
+
# Examples
|
111
|
+
#
|
112
|
+
# class Plug < Binder::AR
|
113
|
+
# database :foobar
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# Returns the current database.
|
117
|
+
def database db = default_database
|
118
|
+
if db == default_database
|
119
|
+
@database ||= db
|
120
|
+
else
|
121
|
+
@database = db
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Public: Set the current adapter
|
126
|
+
#
|
127
|
+
# adpt - the adapter, as a String or Symbol. (default: Binder::AR.default_adapter)
|
128
|
+
#
|
129
|
+
# Examples
|
130
|
+
#
|
131
|
+
# class Plug < Binder::AR
|
132
|
+
# adapter :foobar
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# Returns the current database.
|
136
|
+
def adapter adpt = default_adapter
|
137
|
+
if adpt == default_adapter
|
138
|
+
@adapter ||= adpt
|
139
|
+
else
|
140
|
+
@adapter = adpt
|
141
|
+
end
|
142
|
+
end
|
143
|
+
alias :adaptor :adapter
|
144
|
+
|
145
|
+
# Public: Set the current connection parameters
|
146
|
+
#
|
147
|
+
# opts - A Hash, containing options to pass to the ActiveRecord::Base.establish_connection call (default: {})
|
148
|
+
# user - A user name as a String.
|
149
|
+
# password - A password as a String.
|
150
|
+
# host - A host String.
|
151
|
+
#
|
152
|
+
# Examples
|
153
|
+
#
|
154
|
+
# class Plug < Binder::AR
|
155
|
+
# adapter :foobar
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# Returns the current database.
|
159
|
+
def connection opts = {}
|
160
|
+
@connection ||= opts
|
161
|
+
end
|
162
|
+
alias :connect_with :connection
|
163
|
+
|
164
|
+
# Public: Retrieves de default database
|
165
|
+
#
|
166
|
+
# Returns a the content of ENV['APP_DB'].
|
167
|
+
def default_database; ENV['APP_DB'] end
|
168
|
+
|
169
|
+
# Public: Retrieves de default database
|
170
|
+
#
|
171
|
+
# Returns a the content of :sqlite3, as a Symbol.
|
172
|
+
def default_adapter; :sqlite3 end
|
173
|
+
|
174
|
+
# Public: Creates a new migration version through subclassing.
|
175
|
+
#
|
176
|
+
# n - A migration version number as Float.
|
177
|
+
#
|
178
|
+
# Examples
|
179
|
+
#
|
180
|
+
# class Plug < Binder::AR; end
|
181
|
+
#
|
182
|
+
# class CreateFooTable < Plug::Version 1.0
|
183
|
+
# def self.up
|
184
|
+
# create_table :foos do |t|
|
185
|
+
# t.string :name
|
186
|
+
# t.timestamps
|
187
|
+
# end
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# def self.down
|
191
|
+
# drop_table :foos
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# class CreateBarTable < Plug::Version 1.1
|
196
|
+
# def self.up
|
197
|
+
# create_table :bars do |t|
|
198
|
+
# t.string :title
|
199
|
+
# t.timestamps
|
200
|
+
# end
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# def self.down
|
204
|
+
# drop_table :bars
|
205
|
+
# end
|
206
|
+
# end
|
207
|
+
#
|
208
|
+
# # You can get the migration version for a pecular migration class.
|
209
|
+
# CreateFooTable.version # => 1.0
|
210
|
+
# CreateBarTable.version # => 1.1
|
211
|
+
#
|
212
|
+
# Returns a new Class, subclassing ActiveRecord::Migration.
|
213
|
+
def Version n
|
214
|
+
@last_version = [n, @last_version.to_f].max # Assign or retrieves the last migration version number
|
215
|
+
# migrations is a pointer to the instance variable
|
216
|
+
migrations = (@migrations ||= [])
|
217
|
+
Class.new(ActiveRecord::Migration) do
|
218
|
+
singleton_class.send(:define_method, :version) do # def self.version
|
219
|
+
n # n
|
220
|
+
end # end
|
221
|
+
|
222
|
+
# Callback invoked whenever a subclass of the current class is created
|
223
|
+
singleton_class.send(:define_method, :inherited) do |s| # def self.inherited subclass
|
224
|
+
migrations << s # migrations << subclass
|
225
|
+
end # end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Public: Executes a migration.
|
230
|
+
#
|
231
|
+
# version - A Float or Numeric indicating the number towards which execute the migration.
|
232
|
+
# If none specified it will migrate to the latest migration.
|
233
|
+
#
|
234
|
+
# Examples
|
235
|
+
#
|
236
|
+
# # (See the Binder::AR::Version examples for the classes definition.)
|
237
|
+
# Plug.migrate 1.0 # Creates table bars.
|
238
|
+
# # => 1.0
|
239
|
+
# Plug.migrate 0 # Drop the table bars.
|
240
|
+
# # => 0.0
|
241
|
+
# Plug.migrate # Creates tables bars and foos.
|
242
|
+
# # => 1.1
|
243
|
+
#
|
244
|
+
# Returns the version we migrated to as a Float.
|
245
|
+
def migrate version = nil
|
246
|
+
if @migrations
|
247
|
+
schema = meta_schema
|
248
|
+
version = __check_migration_version(version)
|
249
|
+
__create_meta_data_table_for(schema)
|
250
|
+
|
251
|
+
s = schema.first || schema.new(version: 0)
|
252
|
+
unless s.version == version
|
253
|
+
@migrations.sort_by { |migration| migration.version }.each do |m|
|
254
|
+
m.migrate(:up) if s.version < m.version and m.version <= version
|
255
|
+
|
256
|
+
if s.version >= m.version and m.version > version
|
257
|
+
m.migrate(:down)
|
258
|
+
else # Handle migrate(0)
|
259
|
+
m.migrate(:down) if s.version >= m.version and version.zero?
|
260
|
+
end
|
261
|
+
end
|
262
|
+
s.update_attribute :version, version
|
263
|
+
end
|
264
|
+
version = s.version
|
265
|
+
end
|
266
|
+
version
|
267
|
+
end
|
268
|
+
|
269
|
+
# Private: Get the Class holding the current migration metadatas.
|
270
|
+
#
|
271
|
+
# Returns an ActiveRecord::Base subclass.
|
272
|
+
def meta_schema
|
273
|
+
klass = :MetaSchema
|
274
|
+
@meta_schema ||=
|
275
|
+
if self.const_defined?(klass)
|
276
|
+
self.const_get(klass)
|
277
|
+
else
|
278
|
+
self.__create_meta_schema_class(klass)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# private class methods
|
283
|
+
|
284
|
+
# Private : Create a table for the current meta schema.
|
285
|
+
#
|
286
|
+
# schema - The schema Class for which we want to create the table.
|
287
|
+
#
|
288
|
+
# Examples
|
289
|
+
#
|
290
|
+
# __create_meta_data_table_for MyPlug # Will create a my_plug_meta_schemas table.
|
291
|
+
#
|
292
|
+
# Returns Nothing.
|
293
|
+
def __create_meta_data_table_for schema
|
294
|
+
# Clears the table cache for the schema (remove TableDoesNotExists if a table actually exists)
|
295
|
+
schema.clear_cache!
|
296
|
+
|
297
|
+
unless schema.table_exists?
|
298
|
+
ActiveRecord::Schema.define do
|
299
|
+
create_table schema.table_name do |t|
|
300
|
+
t.column :version, :float
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Private : checks the migration version number
|
307
|
+
#
|
308
|
+
# Raises MigrationVersionError if the required version does not exist.
|
309
|
+
#
|
310
|
+
# Returns the version as a Float.
|
311
|
+
def __check_migration_version version
|
312
|
+
raise MigrationVersionError if not version.nil? and @last_version < version
|
313
|
+
version ||= @last_version
|
314
|
+
end
|
315
|
+
|
316
|
+
# Private: Creates the meta schema class, and binds it to the current Namespace.
|
317
|
+
#
|
318
|
+
# Examples
|
319
|
+
#
|
320
|
+
# class Plug < Binder::AR; end
|
321
|
+
# __create_meta_schema_class "MetaSchema"
|
322
|
+
# # => Plug::MetaSchema
|
323
|
+
#
|
324
|
+
# Returns a new Class inheriting from ActiveRecord::Base.
|
325
|
+
def __create_meta_schema_class klass
|
326
|
+
binder = self.name
|
327
|
+
self.const_set(klass,
|
328
|
+
Class.new(ActiveRecord::Base) do # class MetaSchema < ActiveRecord::Base
|
329
|
+
singleton_class.send(:define_method, :table_name_prefix) do # def self.table_name_prefix
|
330
|
+
"#{binder.underscore}_" # "foo_binder" # If we have a class FooBinder < Binder::AR
|
331
|
+
end # end
|
332
|
+
end) # end
|
333
|
+
end
|
334
|
+
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active-record-binder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- GabrielDehan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-19 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Ruby library for interfacing with ActiveRecord and create easy elegant
|
15
|
+
migrations.
|
16
|
+
email: dehan.gabriel@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/active_record_binder.rb
|
22
|
+
homepage: https://github.com/gabriel-dehan/ActiveRecordBinder
|
23
|
+
licenses: []
|
24
|
+
post_install_message:
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements: []
|
41
|
+
rubyforge_project:
|
42
|
+
rubygems_version: 1.8.15
|
43
|
+
signing_key:
|
44
|
+
specification_version: 3
|
45
|
+
summary: Ruby library for interfacing with ActiveRecord and create easy elegant migrations.
|
46
|
+
test_files: []
|
47
|
+
has_rdoc:
|