schemaless 0.0.3
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.
- 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
|