active-record-binder 1.0.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.
- 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:
|