active-record-binder 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/active_record_binder.rb +337 -0
  2. 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: