activerecord-import 1.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.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +32 -0
  3. data/.rubocop.yml +49 -0
  4. data/.rubocop_todo.yml +36 -0
  5. data/.travis.yml +74 -0
  6. data/Brewfile +3 -0
  7. data/CHANGELOG.md +430 -0
  8. data/Gemfile +59 -0
  9. data/LICENSE +56 -0
  10. data/README.markdown +619 -0
  11. data/Rakefile +68 -0
  12. data/activerecord-import.gemspec +23 -0
  13. data/benchmarks/README +32 -0
  14. data/benchmarks/benchmark.rb +68 -0
  15. data/benchmarks/lib/base.rb +138 -0
  16. data/benchmarks/lib/cli_parser.rb +107 -0
  17. data/benchmarks/lib/float.rb +15 -0
  18. data/benchmarks/lib/mysql2_benchmark.rb +19 -0
  19. data/benchmarks/lib/output_to_csv.rb +19 -0
  20. data/benchmarks/lib/output_to_html.rb +64 -0
  21. data/benchmarks/models/test_innodb.rb +3 -0
  22. data/benchmarks/models/test_memory.rb +3 -0
  23. data/benchmarks/models/test_myisam.rb +3 -0
  24. data/benchmarks/schema/mysql_schema.rb +16 -0
  25. data/gemfiles/3.2.gemfile +2 -0
  26. data/gemfiles/4.0.gemfile +2 -0
  27. data/gemfiles/4.1.gemfile +2 -0
  28. data/gemfiles/4.2.gemfile +2 -0
  29. data/gemfiles/5.0.gemfile +2 -0
  30. data/gemfiles/5.1.gemfile +2 -0
  31. data/gemfiles/5.2.gemfile +2 -0
  32. data/gemfiles/6.0.gemfile +1 -0
  33. data/gemfiles/6.1.gemfile +1 -0
  34. data/lib/activerecord-import.rb +6 -0
  35. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +9 -0
  36. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -0
  37. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +6 -0
  38. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +6 -0
  39. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +6 -0
  40. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +6 -0
  41. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +7 -0
  42. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +6 -0
  43. data/lib/activerecord-import/adapters/abstract_adapter.rb +66 -0
  44. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +5 -0
  45. data/lib/activerecord-import/adapters/mysql2_adapter.rb +5 -0
  46. data/lib/activerecord-import/adapters/mysql_adapter.rb +129 -0
  47. data/lib/activerecord-import/adapters/postgresql_adapter.rb +217 -0
  48. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +180 -0
  49. data/lib/activerecord-import/base.rb +43 -0
  50. data/lib/activerecord-import/import.rb +1059 -0
  51. data/lib/activerecord-import/mysql2.rb +7 -0
  52. data/lib/activerecord-import/postgresql.rb +7 -0
  53. data/lib/activerecord-import/sqlite3.rb +7 -0
  54. data/lib/activerecord-import/synchronize.rb +66 -0
  55. data/lib/activerecord-import/value_sets_parser.rb +77 -0
  56. data/lib/activerecord-import/version.rb +5 -0
  57. data/test/adapters/jdbcmysql.rb +1 -0
  58. data/test/adapters/jdbcpostgresql.rb +1 -0
  59. data/test/adapters/jdbcsqlite3.rb +1 -0
  60. data/test/adapters/makara_postgis.rb +1 -0
  61. data/test/adapters/mysql2.rb +1 -0
  62. data/test/adapters/mysql2_makara.rb +1 -0
  63. data/test/adapters/mysql2spatial.rb +1 -0
  64. data/test/adapters/postgis.rb +1 -0
  65. data/test/adapters/postgresql.rb +1 -0
  66. data/test/adapters/postgresql_makara.rb +1 -0
  67. data/test/adapters/seamless_database_pool.rb +1 -0
  68. data/test/adapters/spatialite.rb +1 -0
  69. data/test/adapters/sqlite3.rb +1 -0
  70. data/test/database.yml.sample +52 -0
  71. data/test/import_test.rb +903 -0
  72. data/test/jdbcmysql/import_test.rb +5 -0
  73. data/test/jdbcpostgresql/import_test.rb +4 -0
  74. data/test/jdbcsqlite3/import_test.rb +4 -0
  75. data/test/makara_postgis/import_test.rb +8 -0
  76. data/test/models/account.rb +3 -0
  77. data/test/models/alarm.rb +2 -0
  78. data/test/models/bike_maker.rb +7 -0
  79. data/test/models/book.rb +9 -0
  80. data/test/models/car.rb +3 -0
  81. data/test/models/chapter.rb +4 -0
  82. data/test/models/dictionary.rb +4 -0
  83. data/test/models/discount.rb +3 -0
  84. data/test/models/end_note.rb +4 -0
  85. data/test/models/group.rb +3 -0
  86. data/test/models/promotion.rb +3 -0
  87. data/test/models/question.rb +3 -0
  88. data/test/models/rule.rb +3 -0
  89. data/test/models/tag.rb +4 -0
  90. data/test/models/topic.rb +23 -0
  91. data/test/models/user.rb +3 -0
  92. data/test/models/user_token.rb +4 -0
  93. data/test/models/vendor.rb +7 -0
  94. data/test/models/widget.rb +24 -0
  95. data/test/mysql2/import_test.rb +5 -0
  96. data/test/mysql2_makara/import_test.rb +6 -0
  97. data/test/mysqlspatial2/import_test.rb +6 -0
  98. data/test/postgis/import_test.rb +8 -0
  99. data/test/postgresql/import_test.rb +4 -0
  100. data/test/schema/generic_schema.rb +194 -0
  101. data/test/schema/jdbcpostgresql_schema.rb +1 -0
  102. data/test/schema/mysql2_schema.rb +19 -0
  103. data/test/schema/postgis_schema.rb +1 -0
  104. data/test/schema/postgresql_schema.rb +47 -0
  105. data/test/schema/sqlite3_schema.rb +13 -0
  106. data/test/schema/version.rb +10 -0
  107. data/test/sqlite3/import_test.rb +4 -0
  108. data/test/support/active_support/test_case_extensions.rb +75 -0
  109. data/test/support/assertions.rb +73 -0
  110. data/test/support/factories.rb +64 -0
  111. data/test/support/generate.rb +29 -0
  112. data/test/support/mysql/import_examples.rb +98 -0
  113. data/test/support/postgresql/import_examples.rb +563 -0
  114. data/test/support/shared_examples/on_duplicate_key_ignore.rb +43 -0
  115. data/test/support/shared_examples/on_duplicate_key_update.rb +368 -0
  116. data/test/support/shared_examples/recursive_import.rb +216 -0
  117. data/test/support/sqlite3/import_examples.rb +231 -0
  118. data/test/synchronize_test.rb +41 -0
  119. data/test/test_helper.rb +75 -0
  120. data/test/travis/database.yml +66 -0
  121. data/test/value_sets_bytes_parser_test.rb +104 -0
  122. data/test/value_sets_records_parser_test.rb +32 -0
  123. metadata +259 -0
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
3
+ require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
4
+
5
+ should_support_mysql_import_functionality
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
+
4
+ should_support_postgresql_import_functionality
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/sqlite3/import_examples')
3
+
4
+ should_support_sqlite3_import_functionality
@@ -0,0 +1,8 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
+
4
+ should_support_postgresql_import_functionality
5
+
6
+ if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
7
+ should_support_postgresql_upsert_functionality
8
+ end
@@ -0,0 +1,3 @@
1
+ class Account < ActiveRecord::Base
2
+ self.locking_column = :lock
3
+ end
@@ -0,0 +1,2 @@
1
+ class Alarm < ActiveRecord::Base
2
+ end
@@ -0,0 +1,7 @@
1
+ module Bike
2
+ def self.table_name_prefix
3
+ 'bike_'
4
+ end
5
+ class Maker < ActiveRecord::Base
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ class Book < ActiveRecord::Base
2
+ belongs_to :topic, inverse_of: :books
3
+ belongs_to :tag, foreign_key: [:tag_id, :parent_id]
4
+
5
+ has_many :chapters, inverse_of: :book
6
+ has_many :discounts, as: :discountable
7
+ has_many :end_notes, inverse_of: :book
8
+ enum status: [:draft, :published] if ENV['AR_VERSION'].to_f >= 4.1
9
+ end
@@ -0,0 +1,3 @@
1
+ class Car < ActiveRecord::Base
2
+ self.primary_key = :Name
3
+ end
@@ -0,0 +1,4 @@
1
+ class Chapter < ActiveRecord::Base
2
+ belongs_to :book, inverse_of: :chapters
3
+ validates :title, presence: true
4
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'book'
2
+
3
+ class Dictionary < Book
4
+ end
@@ -0,0 +1,3 @@
1
+ class Discount < ActiveRecord::Base
2
+ belongs_to :discountable, polymorphic: true
3
+ end
@@ -0,0 +1,4 @@
1
+ class EndNote < ActiveRecord::Base
2
+ belongs_to :book, inverse_of: :end_notes
3
+ validates :note, presence: true
4
+ end
@@ -0,0 +1,3 @@
1
+ class Group < ActiveRecord::Base
2
+ self.table_name = 'group'
3
+ end
@@ -0,0 +1,3 @@
1
+ class Promotion < ActiveRecord::Base
2
+ self.primary_key = :promotion_id
3
+ end
@@ -0,0 +1,3 @@
1
+ class Question < ActiveRecord::Base
2
+ has_one :rule
3
+ end
@@ -0,0 +1,3 @@
1
+ class Rule < ActiveRecord::Base
2
+ belongs_to :question
3
+ end
@@ -0,0 +1,4 @@
1
+ class Tag < ActiveRecord::Base
2
+ self.primary_keys = :tag_id, :publisher_id unless ENV["SKIP_COMPOSITE_PK"]
3
+ has_many :books, inverse_of: :tag
4
+ end
@@ -0,0 +1,23 @@
1
+ class Topic < ActiveRecord::Base
2
+ validates_presence_of :author_name
3
+ validates :title, numericality: { only_integer: true }, on: :context_test
4
+ validates :title, uniqueness: true
5
+ validates :content, uniqueness: true
6
+ validates :word_count, numericality: { greater_than: 0 }, if: :content?
7
+
8
+ validate -> { errors.add(:title, :validate_failed) if title == 'validate_failed' }
9
+ before_validation -> { errors.add(:title, :invalid) if title == 'invalid' }
10
+
11
+ has_many :books, inverse_of: :topic
12
+ belongs_to :parent, class_name: "Topic"
13
+
14
+ composed_of :description, mapping: [%w(title title), %w(author_name author_name)], allow_nil: true, class_name: "TopicDescription"
15
+
16
+ default_scope { where(approved: true) }
17
+
18
+ private
19
+
20
+ def word_count
21
+ @word_count ||= content.to_s.scan(/\w+/).count
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :user_tokens, primary_key: :name, foreign_key: :user_name
3
+ end
@@ -0,0 +1,4 @@
1
+ class UserToken < ActiveRecord::Base
2
+ belongs_to :user, primary_key: :name, foreign_key: :user_name
3
+ validates :user, presence: true
4
+ end
@@ -0,0 +1,7 @@
1
+ class Vendor < ActiveRecord::Base
2
+ store :preferences, accessors: [:color], coder: JSON
3
+
4
+ store_accessor :data, :size
5
+ store_accessor :config, :contact
6
+ store_accessor :settings, :charge_code
7
+ end
@@ -0,0 +1,24 @@
1
+ class CustomCoder
2
+ def load(value)
3
+ if value.nil?
4
+ {}
5
+ else
6
+ YAML.load(value)
7
+ end
8
+ end
9
+
10
+ def dump(value)
11
+ YAML.dump(value)
12
+ end
13
+ end
14
+
15
+ class Widget < ActiveRecord::Base
16
+ self.primary_key = :w_id
17
+
18
+ default_scope -> { where(active: true) }
19
+
20
+ serialize :data, Hash
21
+ serialize :json_data, JSON
22
+ serialize :unspecified_data
23
+ serialize :custom_data, CustomCoder.new
24
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
3
+ require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
4
+
5
+ should_support_mysql_import_functionality
@@ -0,0 +1,6 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
5
+
6
+ should_support_mysql_import_functionality
@@ -0,0 +1,6 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
5
+
6
+ should_support_mysql_import_functionality
@@ -0,0 +1,8 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
+
4
+ should_support_postgresql_import_functionality
5
+
6
+ if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
7
+ should_support_postgresql_upsert_functionality
8
+ end
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
+
4
+ should_support_postgresql_import_functionality
@@ -0,0 +1,194 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table :schema_info, force: :cascade do |t|
3
+ t.integer :version, unique: true
4
+ end
5
+ SchemaInfo.create version: SchemaInfo::VERSION
6
+
7
+ create_table :group, force: :cascade do |t|
8
+ t.string :order
9
+ t.timestamps null: true
10
+ end
11
+
12
+ create_table :topics, force: :cascade do |t|
13
+ t.string :title, null: false
14
+ t.string :author_name
15
+ t.string :author_email_address
16
+ t.datetime :written_on
17
+ t.time :bonus_time
18
+ t.datetime :last_read
19
+ t.text :content
20
+ t.boolean :approved, default: '1'
21
+ t.integer :replies_count
22
+ t.integer :parent_id
23
+ t.string :type
24
+ t.datetime :created_at
25
+ t.datetime :created_on
26
+ t.datetime :updated_at
27
+ t.datetime :updated_on
28
+ end
29
+
30
+ create_table :projects, force: :cascade do |t|
31
+ t.string :name
32
+ t.string :type
33
+ end
34
+
35
+ create_table :developers, force: :cascade do |t|
36
+ t.string :name
37
+ t.integer :salary, default: '70000'
38
+ t.datetime :created_at
39
+ t.integer :team_id
40
+ t.datetime :updated_at
41
+ end
42
+
43
+ create_table :addresses, force: :cascade do |t|
44
+ t.string :address
45
+ t.string :city
46
+ t.string :state
47
+ t.string :zip
48
+ t.integer :developer_id
49
+ end
50
+
51
+ create_table :teams, force: :cascade do |t|
52
+ t.string :name
53
+ end
54
+
55
+ create_table :books, force: :cascade do |t|
56
+ t.string :title, null: false
57
+ t.string :publisher, null: false, default: 'Default Publisher'
58
+ t.string :author_name, null: false
59
+ t.datetime :created_at
60
+ t.datetime :created_on
61
+ t.datetime :updated_at
62
+ t.datetime :updated_on
63
+ t.date :publish_date
64
+ t.integer :topic_id
65
+ t.integer :tag_id
66
+ t.integer :publisher_id
67
+ t.boolean :for_sale, default: true
68
+ t.integer :status, default: 0
69
+ t.string :type
70
+ end
71
+
72
+ create_table :chapters, force: :cascade do |t|
73
+ t.string :title
74
+ t.integer :book_id, null: false
75
+ t.datetime :created_at
76
+ t.datetime :updated_at
77
+ end
78
+
79
+ create_table :end_notes, primary_key: :end_note_id, force: :cascade do |t|
80
+ t.string :note
81
+ t.integer :book_id, null: false
82
+ t.datetime :created_at
83
+ t.datetime :updated_at
84
+ end
85
+
86
+ create_table :languages, force: :cascade do |t|
87
+ t.string :name
88
+ t.integer :developer_id
89
+ end
90
+
91
+ create_table :shopping_carts, force: :cascade do |t|
92
+ t.string :name, null: true
93
+ t.datetime :created_at
94
+ t.datetime :updated_at
95
+ end
96
+
97
+ create_table :cart_items, force: :cascade do |t|
98
+ t.string :shopping_cart_id, null: false
99
+ t.string :book_id, null: false
100
+ t.integer :copies, default: 1
101
+ t.datetime :created_at
102
+ t.datetime :updated_at
103
+ end
104
+
105
+ add_index :cart_items, [:shopping_cart_id, :book_id], unique: true, name: 'uk_shopping_cart_books'
106
+
107
+ create_table :animals, force: :cascade do |t|
108
+ t.string :name, null: false
109
+ t.string :size, default: nil
110
+ t.datetime :created_at
111
+ t.datetime :updated_at
112
+ end
113
+
114
+ add_index :animals, [:name], unique: true, name: 'uk_animals'
115
+
116
+ create_table :widgets, id: false, force: :cascade do |t|
117
+ t.integer :w_id, primary_key: true
118
+ t.boolean :active, default: false
119
+ t.text :data
120
+ t.text :json_data
121
+ t.text :unspecified_data
122
+ t.text :custom_data
123
+ end
124
+
125
+ create_table :promotions, primary_key: :promotion_id, force: :cascade do |t|
126
+ t.string :code
127
+ t.string :description
128
+ t.decimal :discount
129
+ end
130
+
131
+ add_index :promotions, [:code], unique: true, name: 'uk_code'
132
+
133
+ create_table :discounts, force: :cascade do |t|
134
+ t.decimal :amount
135
+ t.integer :discountable_id
136
+ t.string :discountable_type
137
+ end
138
+
139
+ create_table :rules, id: false, force: :cascade do |t|
140
+ t.integer :id
141
+ t.string :condition_text
142
+ t.integer :question_id
143
+ end
144
+
145
+ create_table :questions, force: :cascade do |t|
146
+ t.string :body
147
+ end
148
+
149
+ create_table :vendors, force: :cascade do |t|
150
+ t.string :name, null: true
151
+ t.text :preferences
152
+ t.text :data
153
+ t.text :config
154
+ t.text :settings
155
+ end
156
+
157
+ create_table :cars, id: false, force: :cascade do |t|
158
+ t.string :Name, null: true
159
+ t.string :Features
160
+ end
161
+
162
+ create_table :users, force: :cascade do |t|
163
+ t.string :name, null: false
164
+ t.integer :lock_version, null: false, default: 0
165
+ end
166
+
167
+ create_table :user_tokens, force: :cascade do |t|
168
+ t.string :user_name, null: false
169
+ t.string :token, null: false
170
+ end
171
+
172
+ create_table :accounts, force: :cascade do |t|
173
+ t.string :name, null: false
174
+ t.integer :lock, null: false, default: 0
175
+ end
176
+
177
+ create_table :bike_makers, force: :cascade do |t|
178
+ t.string :name, null: false
179
+ t.integer :lock_version, null: false, default: 0
180
+ end
181
+
182
+ add_index :cars, :Name, unique: true
183
+
184
+ unless ENV["SKIP_COMPOSITE_PK"]
185
+ execute %(
186
+ CREATE TABLE IF NOT EXISTS tags (
187
+ tag_id INT NOT NULL,
188
+ publisher_id INT NOT NULL,
189
+ tag VARCHAR(50),
190
+ PRIMARY KEY (tag_id, publisher_id)
191
+ );
192
+ ).split.join(' ').strip
193
+ end
194
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
@@ -0,0 +1,19 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table :books, force: :cascade do |t|
3
+ t.string :title, null: false
4
+ t.virtual :upper_title, type: :string, as: "upper(`title`)" if t.respond_to?(:virtual)
5
+ t.string :publisher, null: false, default: 'Default Publisher'
6
+ t.string :author_name, null: false
7
+ t.datetime :created_at
8
+ t.datetime :created_on
9
+ t.datetime :updated_at
10
+ t.datetime :updated_on
11
+ t.date :publish_date
12
+ t.integer :topic_id
13
+ t.integer :tag_id
14
+ t.integer :publisher_id
15
+ t.boolean :for_sale, default: true
16
+ t.integer :status, default: 0
17
+ t.string :type
18
+ end
19
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
@@ -0,0 +1,47 @@
1
+ ActiveRecord::Schema.define do
2
+ execute('CREATE extension IF NOT EXISTS "hstore";')
3
+ execute('CREATE extension IF NOT EXISTS "pgcrypto";')
4
+ execute('CREATE extension IF NOT EXISTS "uuid-ossp";')
5
+
6
+ create_table :vendors, id: :uuid, force: :cascade do |t|
7
+ t.string :name, null: true
8
+ t.text :preferences
9
+
10
+ if t.respond_to?(:json)
11
+ t.json :pure_json_data
12
+ t.json :data
13
+ else
14
+ t.text :data
15
+ end
16
+
17
+ if t.respond_to?(:hstore)
18
+ t.hstore :config
19
+ else
20
+ t.text :config
21
+ end
22
+
23
+ if t.respond_to?(:jsonb)
24
+ t.jsonb :pure_jsonb_data
25
+ t.jsonb :settings
26
+ t.jsonb :json_data, null: false, default: {}
27
+ else
28
+ t.text :settings
29
+ t.text :json_data
30
+ end
31
+
32
+ t.datetime :created_at
33
+ t.datetime :updated_at
34
+ end
35
+
36
+ create_table :alarms, force: true do |t|
37
+ t.column :device_id, :integer, null: false
38
+ t.column :alarm_type, :integer, null: false
39
+ t.column :status, :integer, null: false
40
+ t.column :metadata, :text
41
+ t.column :secret_key, :binary
42
+ t.datetime :created_at
43
+ t.datetime :updated_at
44
+ end
45
+
46
+ add_index :alarms, [:device_id, :alarm_type], unique: true, where: 'status <> 0'
47
+ end