schemaless 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +93 -0
- data/Rakefile +18 -0
- data/lib/generators/schemaless/migrations/USAGE +8 -0
- data/lib/generators/schemaless/migrations/migrations_generator.rb +108 -0
- data/lib/generators/schemaless/migrations/templates/change_table.rb +34 -0
- data/lib/generators/schemaless/migrations/templates/create_table.rb +18 -0
- data/lib/generators/schemaless/setup/setup_generator.rb +17 -0
- data/lib/generators/schemaless/setup/templates/schemaless.rb +3 -0
- data/lib/schemaless/ar/fields.rb +76 -0
- data/lib/schemaless/ar/indexes.rb +46 -0
- data/lib/schemaless/ar/stubs.rb +15 -0
- data/lib/schemaless/field.rb +94 -0
- data/lib/schemaless/index.rb +59 -0
- data/lib/schemaless/railtie.rb +23 -0
- data/lib/schemaless/table.rb +95 -0
- data/lib/schemaless/version.rb +4 -0
- data/lib/schemaless/worker.rb +47 -0
- data/lib/schemaless.rb +21 -0
- data/lib/tasks/schemaless_tasks.rake +23 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/bike.rb +18 -0
- data/spec/dummy/app/models/place.rb +8 -0
- data/spec/dummy/app/models/rider.rb +8 -0
- data/spec/dummy/app/models/user.rb +21 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +28 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +78 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20141202223732_create_bikes.rb +16 -0
- data/spec/dummy/db/migrate/20141202223750_create_places.rb +9 -0
- data/spec/dummy/db/migrate/20141202223751_create_riders.rb +10 -0
- data/spec/dummy/db/migrate/20141202223752_create_users.rb +8 -0
- data/spec/dummy/db/migrate/20141202223753_create_user_skills.rb +8 -0
- data/spec/dummy/db/migrate/20141202223754_create_user_extras.rb +8 -0
- data/spec/dummy/db/production.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +213 -0
- data/spec/dummy/log/test.log +185888 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/generators/schemaless/migrations/migration_generator_spec.rb +14 -0
- data/spec/locales/en.yml +15 -0
- data/spec/locales/pt.yml +27 -0
- data/spec/schemaless/ar/fields_spec.rb +45 -0
- data/spec/schemaless/ar/indexes_spec.rb +43 -0
- data/spec/schemaless/field_spec.rb +29 -0
- data/spec/schemaless/index_spec.rb +9 -0
- data/spec/schemaless/table_spec.rb +25 -0
- data/spec/schemaless/worker_spec.rb +57 -0
- data/spec/schemaless_spec.rb +15 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/support/001_create_testing_structure.rb +36 -0
- metadata +309 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cc3e152ecd0dded8e8316950f59c6616276694e4
|
4
|
+
data.tar.gz: a4232d47f7faec46ac0886141b215c0eaac023ad
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 131f65209c376f9976dbdd973ddf87bfda7ab27f6e29e16303c0f38dc7a3c564dfc077573953a4499a64f9a1b4ace3103e1f2dfe715f1786d5e867e1ffbddcba
|
7
|
+
data.tar.gz: 6ac58bc7b86799d04557a64cb2f3f7bdc4e2722354a1a6cbd122b4d6cbef967e273b84c69558d47f0d08c76a810e1364f74a4a6337b0839bf2ae6adb37825c9f
|
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
Schemaless
|
2
|
+
==========
|
3
|
+
|
4
|
+
**On ActiveRecord | PostgreSQL**
|
5
|
+
|
6
|
+
**Experimental, bugged code**, please use to submit bugs/requests.
|
7
|
+
Needs more test code and scenarios also.
|
8
|
+
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class Bike < ActiveRecord::Base
|
12
|
+
|
13
|
+
# field column name, column type, null: limit: precision: scale:
|
14
|
+
field :name # omit for String
|
15
|
+
field :cylinders, Integer # or Float, :decimal, :currency
|
16
|
+
field :bought_at, Date # Time, :date, :time, :timestamp
|
17
|
+
|
18
|
+
# Adds 'default: to AR
|
19
|
+
field :three_wheeler, :boolean, default: false
|
20
|
+
|
21
|
+
# Adds Array/Hash in PG
|
22
|
+
field :tags, Hash # hstore
|
23
|
+
field :tags, Array # hstore
|
24
|
+
|
25
|
+
# index columns, name: unique: orders: also supported.
|
26
|
+
index :name
|
27
|
+
index [:name, :cylinders]
|
28
|
+
...
|
29
|
+
end
|
30
|
+
|
31
|
+
```
|
32
|
+
|
33
|
+
# Why?
|
34
|
+
|
35
|
+
* Schema is **defined in code**, not in the DB.
|
36
|
+
(Plus no need for annotate models)
|
37
|
+
|
38
|
+
* It's easier and **less time** consuming.
|
39
|
+
Honestly, you never forgot that field and had to re-run a migration?
|
40
|
+
|
41
|
+
* There's **no footprint** in production mode.
|
42
|
+
No code executed in production mode.
|
43
|
+
|
44
|
+
|
45
|
+
# How?
|
46
|
+
|
47
|
+
Just include the gem and run:
|
48
|
+
|
49
|
+
rails g schemaless:setup
|
50
|
+
|
51
|
+
|
52
|
+
## DEVELOPMENT | Realtime mode
|
53
|
+
|
54
|
+
This mode runs only in development mode.
|
55
|
+
|
56
|
+
It's when you are working, happy, without worry about schemas!
|
57
|
+
And also now DB fields are nicely described in the model.rb.
|
58
|
+
Logic is all in the ruby file now. It's like NoSQL: fun!
|
59
|
+
|
60
|
+
|
61
|
+
## PRODUCTION | Migrate servers
|
62
|
+
|
63
|
+
Schemaless only stubs the #fields and #index methods in production.
|
64
|
+
There's nothing running/using resources in production.
|
65
|
+
|
66
|
+
To commit your changes you have two options:
|
67
|
+
|
68
|
+
## Safe mode
|
69
|
+
|
70
|
+
In safe mode there's no change in the production perspective:
|
71
|
+
|
72
|
+
rails g schemaless:migrations
|
73
|
+
|
74
|
+
You'll find a migration per changed table waiting for you in git.
|
75
|
+
You simply review'em, and actually, there's no need to test them,
|
76
|
+
as your test suite is using them.
|
77
|
+
|
78
|
+
|
79
|
+
## Wild mode
|
80
|
+
|
81
|
+
Schemaphobia? Very dangerous idea?
|
82
|
+
|
83
|
+
rake schemaless:run
|
84
|
+
|
85
|
+
Here we don't use any migration **as files**.
|
86
|
+
On the production servers a rake task ensures everything is up to date.
|
87
|
+
Tests also use the schema on the models.
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
---
|
92
|
+
|
93
|
+
Have fun!
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
|
7
|
+
APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
|
8
|
+
# load 'rails/tasks/engine.rake'
|
9
|
+
|
10
|
+
Bundler::GemHelper.install_tasks
|
11
|
+
|
12
|
+
# Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
13
|
+
|
14
|
+
# RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
15
|
+
RSpec::Core::RakeTask.new
|
16
|
+
RuboCop::RakeTask.new
|
17
|
+
|
18
|
+
task default: [:spec, :rubocop]
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Schemaless
|
5
|
+
# Generates migrations for schemaless safe mode
|
6
|
+
class MigrationsGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
9
|
+
argument :models, type: :array, default: [] # , banner: 'path'
|
10
|
+
desc 'Schemaless migration files generator!'
|
11
|
+
|
12
|
+
def create_migration_files
|
13
|
+
Rails.application.eager_load!
|
14
|
+
all_tables = Schemaless::Worker.all_tables
|
15
|
+
if models.empty?
|
16
|
+
tables = all_tables
|
17
|
+
else
|
18
|
+
tables = all_tables.select { |t| models.include?(t.model.to_s.downcase) }
|
19
|
+
end
|
20
|
+
tables.each do |table|
|
21
|
+
puts "Generating migrations for #{table}"
|
22
|
+
create_migration_for table
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
attr_reader :table_name, :fields, :indexes, :migration_action, :join_tables
|
29
|
+
|
30
|
+
def build_file_name(name = [])
|
31
|
+
if @table.fields.add.any?
|
32
|
+
name << :add
|
33
|
+
name << (@table.fields.add.size > 3 ? :many : @table.fields.add)
|
34
|
+
name << (@table.fields.remove.any? ? :and : :to)
|
35
|
+
end
|
36
|
+
if @table.fields.remove.any?
|
37
|
+
name << :remove
|
38
|
+
name << (@table.fields.remove.size > 3 ? :many : @table.fields.remove)
|
39
|
+
name << :from
|
40
|
+
end
|
41
|
+
name << table_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def file_name
|
45
|
+
return @file_name if @file_name
|
46
|
+
return "create_#{@table_name}" unless @table.exists?
|
47
|
+
@file_name = build_file_name.flatten.join('_')
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_migration_for table
|
51
|
+
@table = table
|
52
|
+
@fields = @table.fields
|
53
|
+
@indexes = @table.indexes
|
54
|
+
@table_name = @table.name
|
55
|
+
|
56
|
+
template = file_name =~ /^create_(.+)/ ? 'create' : 'change'
|
57
|
+
migration_template "#{template}_table.rb", "db/migrate/#{file_name}.rb"
|
58
|
+
|
59
|
+
# case file_name
|
60
|
+
# when /join_table/
|
61
|
+
# if attributes.length == 2
|
62
|
+
# @migration_action = 'join'
|
63
|
+
# @join_tables = if pluralize_table_names?
|
64
|
+
# attributes.map(&:plural_name)
|
65
|
+
# else
|
66
|
+
# attributes.map(&:singular_name)
|
67
|
+
# end
|
68
|
+
|
69
|
+
# set_index_names
|
70
|
+
# end
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_index_names
|
74
|
+
attributes.each_with_index do |att, i|
|
75
|
+
att.index_name = [att, attributes[i - 1]].map { |a| index_name_for(a) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def index_name_for(attribute)
|
80
|
+
if attribute.foreign_key?
|
81
|
+
attribute.name
|
82
|
+
else
|
83
|
+
attribute.name.singularize.foreign_key
|
84
|
+
end.to_sym
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# def attributes_with_index
|
90
|
+
# attributes.select { |a| !a.reference? && a.has_index? }
|
91
|
+
# end
|
92
|
+
|
93
|
+
def old_migrations
|
94
|
+
ActiveRecord::Migrator.get_all_versions
|
95
|
+
end
|
96
|
+
|
97
|
+
# def validate_file_name!
|
98
|
+
# unless file_name =~ /^[_a-z0-9]+$/
|
99
|
+
# fail IllegalMigrationNameError, file_name
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
|
103
|
+
def self.next_migration_number(dirname)
|
104
|
+
next_migration_number = current_migration_number(dirname) + 1
|
105
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
<%- fields.add.each do |field| -%>
|
4
|
+
<%- if field.reference? -%>
|
5
|
+
add_reference :<%= table_name %>, :<%= field.name %><%= field.opts_text %>
|
6
|
+
<%- unless field.polymorphic? -%>
|
7
|
+
add_foreign_key :<%= table_name %>, :<%= field.name.pluralize %>
|
8
|
+
<%- end -%>
|
9
|
+
<%- end -%>
|
10
|
+
add_column :<%= table_name %>, :<%= field.name %>, :<%= field.type %><%= field.opts_text %>
|
11
|
+
<%- end -%>
|
12
|
+
<%- fields.change.each do |field| -%>
|
13
|
+
change_column :<%= table_name %>, :<%= field.name %>, :<%= field.type %><%= field.opts_text %>
|
14
|
+
<%- end -%>
|
15
|
+
|
16
|
+
<%- indexes.remove do |index| -%>
|
17
|
+
remove_index :<%= table_name %>, <%= index.fields_text %>
|
18
|
+
<%- end -%>
|
19
|
+
<%- fields.remove.each do |field| -%>
|
20
|
+
remove_column :<%= table_name %>, :<%= field.name %>
|
21
|
+
<%- end -%>
|
22
|
+
<%- indexes.add.each do |index| -%>
|
23
|
+
add_index :<%= table_name %>, <%= index.fields_text %><%= index.opts_text %>
|
24
|
+
<%- end -%>
|
25
|
+
end
|
26
|
+
<%- if migration_action == 'join' -%>
|
27
|
+
create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t|
|
28
|
+
<%- fields.each do |field| -%>
|
29
|
+
<%= '# ' unless field.has_index? -%>t.index <%= field.index_name %><%= field.opts_text %>
|
30
|
+
<%- end -%>
|
31
|
+
end
|
32
|
+
end
|
33
|
+
<%- end -%>
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :<%= table_name %> do |t|
|
4
|
+
<% fields.add.each do |field| -%>
|
5
|
+
t.<%= field.type %> :<%= field.name %><%= field.opts_text %>
|
6
|
+
<% end -%>
|
7
|
+
<% if options[:timestamps] %>
|
8
|
+
t.timestamps null: false
|
9
|
+
<% end -%>
|
10
|
+
end
|
11
|
+
<% indexes.add.each do |index| -%>
|
12
|
+
add_index :<%= table_name %>, <%= index.fields_text %><%= index.opts_text %>
|
13
|
+
<% end -%>
|
14
|
+
<% fields.add.select(&:reference?).reject(&:polymorphic?).each do |field| -%>
|
15
|
+
add_foreign_key :<%= table_name %>, :<%= field.name.pluralize %>
|
16
|
+
<% end -%>
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Schemaless
|
2
|
+
# Generates schemaless setup for rails app
|
3
|
+
class SetupGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
argument :attributes, type: :array, default: [], banner: 'path'
|
6
|
+
desc 'Schemaless config files generator!'
|
7
|
+
|
8
|
+
def create_config_file
|
9
|
+
application 'end'
|
10
|
+
application ' Schemaless::Worker.run!'
|
11
|
+
application ' Rails.application.eager_load!'
|
12
|
+
application 'config.to_prepare do'
|
13
|
+
# initializer | application
|
14
|
+
# copy 'schemaless.rb'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
|
4
|
+
module Schemaless
|
5
|
+
#
|
6
|
+
# Extend ActiveRecord for #field
|
7
|
+
#
|
8
|
+
module Fields
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
# Schemaless ActiveRecord fields
|
12
|
+
module ClassMethods
|
13
|
+
AR_OPTS = ::Schemaless::Field::VALID_OPTS - [:index, :type]
|
14
|
+
|
15
|
+
def schemaless_fields
|
16
|
+
@schemaless_fields ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# field(*)
|
21
|
+
#
|
22
|
+
# Gets all fields in the model in the schamless_fields array.
|
23
|
+
#
|
24
|
+
# field :name # Defaults String
|
25
|
+
# field :cylinders, Integer # Use Class or :symbols
|
26
|
+
# field :type, index: true, default: nil, limit: 5
|
27
|
+
#
|
28
|
+
#
|
29
|
+
def field(*params)
|
30
|
+
config = params.extract_options!
|
31
|
+
config.assert_valid_keys(*::Schemaless::Field::VALID_OPTS)
|
32
|
+
type = config.delete(:type)
|
33
|
+
type ||= params.size > 1 ? params.pop : :string # TBD
|
34
|
+
name = params.join
|
35
|
+
schemaless_fields <<
|
36
|
+
::Schemaless::Field.new(name, type, config)
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Create the belongs_to foreign keys
|
41
|
+
#
|
42
|
+
def belongs_to(*params)
|
43
|
+
config = params.extract_options!
|
44
|
+
name = "#{params.join}_id"
|
45
|
+
schemaless_fields <<
|
46
|
+
::Schemaless::Field.new(name, :integer, config)
|
47
|
+
super(*params)
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Helper for timestamps
|
52
|
+
#
|
53
|
+
def timestamps(*params)
|
54
|
+
config = params.extract_options!
|
55
|
+
type = config[:type] || :timestamp
|
56
|
+
schemaless_fields << ::Schemaless::Field.new(:created_at, type, null: false)
|
57
|
+
schemaless_fields << ::Schemaless::Field.new(:updated_at, type, null: true)
|
58
|
+
return unless config[:paranoid]
|
59
|
+
schemaless_fields << ::Schemaless::Field.new(:deleted_at, type, null: true)
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Get all fields in a schemaless way
|
64
|
+
#
|
65
|
+
def current_attributes
|
66
|
+
columns_hash.map do |k, v|
|
67
|
+
next if v.primary
|
68
|
+
opts = AR_OPTS.reduce({}) do |a, e|
|
69
|
+
v.send(e) ? a.merge(e => v.send(e)) : a
|
70
|
+
end
|
71
|
+
::Schemaless::Field.new(k, v.type, opts)
|
72
|
+
end.reject!(&:nil?)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
|
4
|
+
module Schemaless
|
5
|
+
#
|
6
|
+
# Extend ActiveRecord for #index
|
7
|
+
#
|
8
|
+
module Indexes
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
# Schemaless ActiveRecord indexes
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def schemaless_indexes
|
15
|
+
@schemaless_indexes ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# index(*)
|
20
|
+
#
|
21
|
+
# index :name
|
22
|
+
# index [:one, :other]
|
23
|
+
# index :name, unique: true
|
24
|
+
#
|
25
|
+
#
|
26
|
+
def index(*params)
|
27
|
+
config = params.extract_options!
|
28
|
+
name = config.delete(:name)
|
29
|
+
schemaless_indexes <<
|
30
|
+
::Schemaless::Index.new(params, name, config)
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Get all indexes in a schemaless way
|
35
|
+
#
|
36
|
+
def current_indexes
|
37
|
+
::ActiveRecord::Base.connection.indexes(table_name).map do |i|
|
38
|
+
opts = ::Schemaless::Index::VALID_OPTS.reduce({}) do |a, e|
|
39
|
+
i.send(e) ? a.merge(e => i.send(e)) : a
|
40
|
+
end
|
41
|
+
::Schemaless::Index.new(i.columns, i.name, opts)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end # ActiveRecord
|
46
|
+
end # Schemaless
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Schemaless
|
4
|
+
# Stubs for production mode
|
5
|
+
module Stubs
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Schemaless ActiveRecord Stub
|
9
|
+
module ClassMethods
|
10
|
+
def field(*) end
|
11
|
+
|
12
|
+
def index(*) end
|
13
|
+
end
|
14
|
+
end # Stubs
|
15
|
+
end # Schemaless
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Schemaless
|
2
|
+
#
|
3
|
+
# Fields
|
4
|
+
#
|
5
|
+
class Field
|
6
|
+
attr_accessor :name, :type, :opts, :default, :index
|
7
|
+
VALID_OPTS = [:type, :limit, :precision, :scale, :null, :default, :index]
|
8
|
+
|
9
|
+
#
|
10
|
+
# Field - name, type, opts: [:default, :null, :unique]
|
11
|
+
#
|
12
|
+
def initialize(name, type, opts = {})
|
13
|
+
fail InvalidArgument, 'opts must be a hash' unless opts.is_a?(Hash)
|
14
|
+
@name = name.to_s
|
15
|
+
@type = map_field type
|
16
|
+
@opts = opts.select { |_k, v| v.present? }
|
17
|
+
@opts[:null] = true unless @opts[:null].present?
|
18
|
+
@opts[:limit] = 255 unless @opts[:limit].present? || @type != :string
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
name == other.name # && type == other.type
|
23
|
+
end
|
24
|
+
|
25
|
+
def reference?
|
26
|
+
end
|
27
|
+
|
28
|
+
def opts_text
|
29
|
+
txt = opts.map { |k, v| "#{k}: #{v}" }.join(', ')
|
30
|
+
txt.empty? ? '' : ", #{txt}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
name
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Add Fields
|
39
|
+
#
|
40
|
+
def add!(table)
|
41
|
+
puts "++ Adding '#{self}' to '#{table}'"
|
42
|
+
return if Schemaless.sandbox
|
43
|
+
::ActiveRecord::Migration.add_column(table.name, name, type, opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Delete Fields
|
48
|
+
#
|
49
|
+
def remove!(table)
|
50
|
+
puts "-- Removing '#{self}' from '#{table}'"
|
51
|
+
return if Schemaless.sandbox
|
52
|
+
::ActiveRecord::Migration.remove_column(table.name, name)
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Change Fields
|
57
|
+
#
|
58
|
+
def change!(table)
|
59
|
+
return if Schemaless.sandbox
|
60
|
+
# ::ActiveRecord::Migration.change_column(table, name)
|
61
|
+
# ::ActiveRecord::Migration.change_column_null(table, name)
|
62
|
+
# ::ActiveRecord::Migration.change_column_default(table, name)
|
63
|
+
::ActiveRecord::Migration.change_column(table.name, name, type, opts)
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# binary # boolean
|
68
|
+
# date # datetime
|
69
|
+
# time # timestamp
|
70
|
+
# integer # primary_key # references
|
71
|
+
# decimal # float
|
72
|
+
# string # text
|
73
|
+
# hstore
|
74
|
+
# json
|
75
|
+
# array
|
76
|
+
# cidr_address
|
77
|
+
# ip_address
|
78
|
+
# mac_address
|
79
|
+
#
|
80
|
+
def map_field(field) # rubocop:disable Metrics/MethodLength
|
81
|
+
return field if field.is_a?(Symbol)
|
82
|
+
case field.to_s
|
83
|
+
when /Integer|Fixnum|Numeric/ then :integer
|
84
|
+
when /BigDecimal|Rational/ then :decimal
|
85
|
+
when /Float/ then :float
|
86
|
+
when /DateTime/ then :datetime
|
87
|
+
when /Date/ then :date
|
88
|
+
when /Time/ then :timestamp
|
89
|
+
else
|
90
|
+
:string
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Schemaless
|
2
|
+
#
|
3
|
+
# Stores indexes
|
4
|
+
#
|
5
|
+
class Index
|
6
|
+
attr_accessor :fields, :name, :opts
|
7
|
+
VALID_OPTS = [:unique, :orders]
|
8
|
+
|
9
|
+
def initialize(fields, name = nil, opts = {})
|
10
|
+
@fields = [fields].flatten
|
11
|
+
@fields = @fields.first if @fields.size == 1
|
12
|
+
@name = name || opts[:name]
|
13
|
+
@opts = opts.select { |_k, v| v.present? }
|
14
|
+
@opts.merge!(name: @name) if @name
|
15
|
+
end
|
16
|
+
|
17
|
+
def opts_text
|
18
|
+
txt = opts.map { |k, v| "#{k}: #{v}" }.join(', ')
|
19
|
+
txt.empty? ? '' : ", #{txt}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def fields_text
|
23
|
+
@fields.inspect
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
name
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
name == other.name
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Add Indexes
|
36
|
+
#
|
37
|
+
def add!(table)
|
38
|
+
return if Schemaless.sandbox
|
39
|
+
::ActiveRecord::Migration.add_index(table.name, fields, opts)
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Delete Indexes
|
44
|
+
#
|
45
|
+
def remove!(table)
|
46
|
+
return if Schemaless.sandbox
|
47
|
+
key = name ? { name: name } : { column: fields }
|
48
|
+
::ActiveRecord::Migration.remove_index(table.name, key)
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Change Indexes
|
53
|
+
#
|
54
|
+
def change!(table)
|
55
|
+
return if Schemaless.sandbox
|
56
|
+
::ActiveRecord::Migration.change_index(table.name, name, type, opts)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Rails 3 initialization
|
2
|
+
module Schemaless
|
3
|
+
require 'rails'
|
4
|
+
# Rails include
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer 'schemaless.insert_into_active_record' do
|
7
|
+
ActiveSupport.on_load :active_record do
|
8
|
+
if Rails.env =~ /production/
|
9
|
+
require 'schemaless/ar/stubs'
|
10
|
+
::ActiveRecord::Base.send :include, Schemaless::Stubs
|
11
|
+
else
|
12
|
+
require 'schemaless/ar/fields'
|
13
|
+
require 'schemaless/ar/indexes'
|
14
|
+
::ActiveRecord::Base.send :include, Schemaless::Fields
|
15
|
+
::ActiveRecord::Base.send :include, Schemaless::Indexes
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
rake_tasks do
|
20
|
+
load 'tasks/schemaless_tasks.rake'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|