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