thinking-sphinx 3.0.0 → 3.0.1
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.
- data/.travis.yml +6 -3
- data/HISTORY +18 -0
- data/README.textile +31 -8
- data/gemfiles/rails_3_1.gemfile +2 -2
- data/gemfiles/rails_3_2.gemfile +2 -2
- data/lib/thinking/sphinx.rb +1 -0
- data/lib/thinking_sphinx.rb +1 -0
- data/lib/thinking_sphinx/active_record.rb +2 -0
- data/lib/thinking_sphinx/active_record/association.rb +8 -0
- data/lib/thinking_sphinx/active_record/associations.rb +25 -5
- data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +1 -5
- data/lib/thinking_sphinx/active_record/column.rb +12 -0
- data/lib/thinking_sphinx/active_record/database_adapters.rb +7 -0
- data/lib/thinking_sphinx/active_record/filtered_reflection.rb +49 -0
- data/lib/thinking_sphinx/active_record/interpreter.rb +6 -0
- data/lib/thinking_sphinx/active_record/polymorpher.rb +50 -0
- data/lib/thinking_sphinx/active_record/property.rb +6 -0
- data/lib/thinking_sphinx/active_record/property_query.rb +1 -1
- data/lib/thinking_sphinx/active_record/property_sql_presenter.rb +7 -1
- data/lib/thinking_sphinx/active_record/sql_builder.rb +7 -2
- data/lib/thinking_sphinx/active_record/sql_source.rb +5 -1
- data/lib/thinking_sphinx/capistrano.rb +64 -0
- data/lib/thinking_sphinx/configuration.rb +10 -1
- data/lib/thinking_sphinx/connection.rb +20 -0
- data/lib/thinking_sphinx/errors.rb +24 -0
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +4 -1
- data/lib/thinking_sphinx/real_time/transcriber.rb +1 -1
- data/lib/thinking_sphinx/search/batch_inquirer.rb +3 -1
- data/lib/thinking_sphinx/search/glaze.rb +3 -3
- data/spec/acceptance/index_options_spec.rb +5 -0
- data/spec/acceptance/searching_on_fields_spec.rb +1 -0
- data/spec/acceptance/specifying_sql_spec.rb +107 -0
- data/spec/acceptance/sql_deltas_spec.rb +9 -0
- data/spec/acceptance/support/sphinx_controller.rb +1 -0
- data/spec/internal/app/models/event.rb +3 -0
- data/spec/internal/app/models/hardcover.rb +3 -0
- data/spec/internal/db/schema.rb +7 -1
- data/spec/thinking_sphinx/active_record/associations_spec.rb +2 -1
- data/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb +3 -3
- data/spec/thinking_sphinx/active_record/column_spec.rb +23 -0
- data/spec/thinking_sphinx/active_record/database_adapters_spec.rb +18 -0
- data/spec/thinking_sphinx/active_record/filtered_reflection_spec.rb +141 -0
- data/spec/thinking_sphinx/active_record/polymorpher_spec.rb +65 -0
- data/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb +28 -4
- data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +11 -1
- data/spec/thinking_sphinx/configuration_spec.rb +24 -0
- data/spec/thinking_sphinx/connection_spec.rb +82 -0
- data/spec/thinking_sphinx/errors_spec.rb +36 -0
- data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +25 -0
- data/spec/thinking_sphinx/search/glaze_spec.rb +3 -0
- data/thinking-sphinx.gemspec +1 -1
- metadata +25 -3
@@ -12,8 +12,9 @@ class ThinkingSphinx::ActiveRecord::SQLBuilder
|
|
12
12
|
relation = relation.group group_clause
|
13
13
|
relation = relation.order('NULL') if source.type == 'mysql'
|
14
14
|
relation = relation.joins associations.join_values
|
15
|
+
relation = relation.joins custom_joins.collect(&:to_s) if custom_joins.any?
|
15
16
|
|
16
|
-
relation.to_sql
|
17
|
+
relation.to_sql.gsub(/\n/, "\\\n")
|
17
18
|
end
|
18
19
|
|
19
20
|
def sql_query_range
|
@@ -67,12 +68,16 @@ class ThinkingSphinx::ActiveRecord::SQLBuilder
|
|
67
68
|
|
68
69
|
def associations
|
69
70
|
@associations ||= ThinkingSphinx::ActiveRecord::Associations.new(model).tap do |assocs|
|
70
|
-
source.associations.each do |association|
|
71
|
+
source.associations.reject(&:string?).each do |association|
|
71
72
|
assocs.add_join_to association.stack
|
72
73
|
end
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
77
|
+
def custom_joins
|
78
|
+
@custom_joins ||= source.associations.select &:string?
|
79
|
+
end
|
80
|
+
|
76
81
|
def quote_column(column)
|
77
82
|
model.connection.quote_column_name(column)
|
78
83
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class ThinkingSphinx::ActiveRecord::SQLSource < Riddle::Configuration::SQLSource
|
2
2
|
attr_reader :model, :database_settings, :options
|
3
|
-
attr_accessor :fields, :attributes, :associations, :conditions, :groupings
|
3
|
+
attr_accessor :fields, :attributes, :associations, :conditions, :groupings,
|
4
|
+
:polymorphs
|
4
5
|
|
5
6
|
OPTIONS = [:name, :offset, :delta_processor, :delta?, :disable_range?,
|
6
7
|
:group_concat_max_len, :utf8?, :position]
|
@@ -15,6 +16,7 @@ class ThinkingSphinx::ActiveRecord::SQLSource < Riddle::Configuration::SQLSource
|
|
15
16
|
@associations = []
|
16
17
|
@conditions = []
|
17
18
|
@groupings = []
|
19
|
+
@polymorphs = []
|
18
20
|
|
19
21
|
Template.new(self).apply
|
20
22
|
|
@@ -89,6 +91,8 @@ class ThinkingSphinx::ActiveRecord::SQLSource < Riddle::Configuration::SQLSource
|
|
89
91
|
end
|
90
92
|
|
91
93
|
def prepare_for_render
|
94
|
+
polymorphs.each &:morph!
|
95
|
+
|
92
96
|
set_database_settings
|
93
97
|
|
94
98
|
fields.each do |field|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
+
namespace :thinking_sphinx do
|
3
|
+
desc 'Generate the Sphinx configuration file.'
|
4
|
+
task :configure do
|
5
|
+
rake 'thinking_sphinx:configure'
|
6
|
+
end
|
7
|
+
|
8
|
+
desc 'Build Sphinx indexes into the shared path and symlink them into your release.'
|
9
|
+
task :index do
|
10
|
+
rake 'thinking_sphinx:index'
|
11
|
+
end
|
12
|
+
after 'thinking_sphinx:index', 'thinking_sphinx:symlink_indexes'
|
13
|
+
|
14
|
+
desc 'Start the Sphinx search daemon.'
|
15
|
+
task :start do
|
16
|
+
rake 'thinking_sphinx:start'
|
17
|
+
end
|
18
|
+
before 'thinking_sphinx:start', 'thinking_sphinx:configure'
|
19
|
+
|
20
|
+
desc 'Stop the Sphinx search daemon.'
|
21
|
+
task :stop do
|
22
|
+
rake 'thinking_sphinx:stop'
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Restart the Sphinx search daemon.'
|
26
|
+
task :restart do
|
27
|
+
rake 'thinking_sphinx:stop thinking_sphinx:configure thinking_sphinx:start'
|
28
|
+
end
|
29
|
+
|
30
|
+
desc <<-DESC
|
31
|
+
Stop, reindex, and then start the Sphinx search daemon. This task must be executed \
|
32
|
+
if you alter the structure of your indexes.
|
33
|
+
DESC
|
34
|
+
task :rebuild do
|
35
|
+
rake 'thinking_sphinx:stop thinking_sphinx:reindex'
|
36
|
+
end
|
37
|
+
after 'thinking_sphinx:rebuild', 'thinking_sphinx:symlink_indexes'
|
38
|
+
after 'thinking_sphinx:rebuild', 'thinking_sphinx:start'
|
39
|
+
|
40
|
+
desc 'Create the shared folder for sphinx indexes.'
|
41
|
+
task :shared_sphinx_folder do
|
42
|
+
rails_env = fetch(:rails_env, 'production')
|
43
|
+
run "mkdir -p #{shared_path}/db/sphinx/#{rails_env}"
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'Symlink Sphinx indexes from the shared folder to the latest release.'
|
47
|
+
task :symlink_indexes do
|
48
|
+
run "if [ -d #{release_path} ]; then ln -nfs #{shared_path}/db/sphinx #{release_path}/db/sphinx; else ln -nfs #{shared_path}/db/sphinx #{current_path}/db/sphinx; fi;"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Logical flow for deploying an app
|
52
|
+
after 'deploy:cold', 'thinking_sphinx:index'
|
53
|
+
after 'deploy:cold', 'thinking_sphinx:start'
|
54
|
+
after 'deploy:setup', 'thinking_sphinx:shared_sphinx_folder'
|
55
|
+
after 'deploy:finalize_update', 'thinking_sphinx:symlink_indexes'
|
56
|
+
|
57
|
+
def rake(tasks)
|
58
|
+
rails_env = fetch(:rails_env, 'production')
|
59
|
+
rake = fetch(:rake, 'rake')
|
60
|
+
|
61
|
+
run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; if [ -f Rakefile ]; then #{rake} RAILS_ENV=#{rails_env} #{tasks}; fi;"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class ThinkingSphinx::Configuration < Riddle::Configuration
|
2
|
-
attr_accessor :configuration_file, :indices_location
|
2
|
+
attr_accessor :configuration_file, :indices_location, :version
|
3
3
|
attr_reader :index_paths
|
4
4
|
attr_writer :controller, :framework
|
5
5
|
|
@@ -11,6 +11,7 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
11
11
|
@index_paths = [File.join(framework.root, 'app', 'indices')]
|
12
12
|
@indices_location = File.join framework.root, 'db', 'sphinx',
|
13
13
|
framework.environment
|
14
|
+
@version = settings['version'] || '2.0.6'
|
14
15
|
|
15
16
|
searchd.pid_file = File.join framework.root, 'log',
|
16
17
|
"#{framework.environment}.sphinx.pid"
|
@@ -27,6 +28,14 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
27
28
|
Defaults::PORT
|
28
29
|
searchd.workers = 'threads'
|
29
30
|
|
31
|
+
[indexer, searchd].each do |object|
|
32
|
+
settings.each do |key, value|
|
33
|
+
next unless object.class.settings.include?(key.to_sym)
|
34
|
+
|
35
|
+
object.send("#{key}=", value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
30
39
|
@offsets = {}
|
31
40
|
end
|
32
41
|
|
@@ -28,6 +28,26 @@ module ThinkingSphinx::Connection
|
|
28
28
|
)
|
29
29
|
end
|
30
30
|
|
31
|
+
def self.take
|
32
|
+
retries = 0
|
33
|
+
original = nil
|
34
|
+
begin
|
35
|
+
pool.take do |connection|
|
36
|
+
begin
|
37
|
+
yield connection
|
38
|
+
rescue Mysql2::Error => error
|
39
|
+
original = ThinkingSphinx::SphinxError.new_from_mysql error
|
40
|
+
raise original if original.is_a?(ThinkingSphinx::QueryError)
|
41
|
+
raise Innertube::Pool::BadResource
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rescue Innertube::Pool::BadResource
|
45
|
+
retries += 1
|
46
|
+
retry if retries < 3
|
47
|
+
raise original
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
31
51
|
class MRI
|
32
52
|
attr_reader :client
|
33
53
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class ThinkingSphinx::SphinxError < StandardError
|
2
|
+
def self.new_from_mysql(error)
|
3
|
+
case error.message
|
4
|
+
when /parse error/
|
5
|
+
replacement = ThinkingSphinx::ParseError.new(error.message)
|
6
|
+
when /syntax error/
|
7
|
+
replacement = ThinkingSphinx::SyntaxError.new(error.message)
|
8
|
+
else
|
9
|
+
replacement = new(error.message)
|
10
|
+
end
|
11
|
+
|
12
|
+
replacement.set_backtrace error.backtrace
|
13
|
+
replacement
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ThinkingSphinx::QueryError < ThinkingSphinx::SphinxError
|
18
|
+
end
|
19
|
+
|
20
|
+
class ThinkingSphinx::SyntaxError < ThinkingSphinx::QueryError
|
21
|
+
end
|
22
|
+
|
23
|
+
class ThinkingSphinx::ParseError < ThinkingSphinx::QueryError
|
24
|
+
end
|
@@ -40,7 +40,10 @@ class ThinkingSphinx::Middlewares::SphinxQL <
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def class_condition
|
43
|
-
|
43
|
+
class_names = classes_and_descendants.collect(&:name).collect { |name|
|
44
|
+
name[/:/] ? "\"#{name}\"" : name
|
45
|
+
}
|
46
|
+
'(' + class_names.join('|') + ')'
|
44
47
|
end
|
45
48
|
|
46
49
|
def descendants
|
@@ -13,7 +13,7 @@ class ThinkingSphinx::RealTime::Transcriber
|
|
13
13
|
end
|
14
14
|
|
15
15
|
sphinxql = Riddle::Query::Insert.new index.name, columns, values
|
16
|
-
ThinkingSphinx::Connection.
|
16
|
+
ThinkingSphinx::Connection.take do |connection|
|
17
17
|
connection.execute sphinxql.replace!.to_sql
|
18
18
|
end
|
19
19
|
end
|
@@ -22,11 +22,11 @@ class ThinkingSphinx::Search::Glaze < BasicObject
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def method_missing(method, *args, &block)
|
25
|
-
|
25
|
+
pane = @panes.detect { |pane| pane.respond_to?(method) }
|
26
|
+
if @object.respond_to?(method) || pane.nil?
|
26
27
|
@object.send(method, *args, &block)
|
27
28
|
else
|
28
|
-
pane
|
29
|
-
pane.nil? ? super : pane.send(method, *args, &block)
|
29
|
+
pane.send(method, *args, &block)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -92,6 +92,7 @@ describe 'Index options' do
|
|
92
92
|
|
93
93
|
set_property :sql_range_step => 5
|
94
94
|
set_property :disable_range? => true
|
95
|
+
set_property :sql_query_pre => ["DO STUFF"]
|
95
96
|
}
|
96
97
|
index.render
|
97
98
|
end
|
@@ -103,5 +104,9 @@ describe 'Index options' do
|
|
103
104
|
it "allows for source options" do
|
104
105
|
index.sources.first.disable_range?.should be_true
|
105
106
|
end
|
107
|
+
|
108
|
+
it "respects sql_query_pre values" do
|
109
|
+
index.sources.first.sql_query_pre.should == ["DO STUFF"]
|
110
|
+
end
|
106
111
|
end
|
107
112
|
end
|
@@ -50,6 +50,7 @@ describe 'Searching on fields', :live => true do
|
|
50
50
|
File.open(file_path, 'w') { |file| file.print 'Cyberpunk at its best' }
|
51
51
|
|
52
52
|
book = Book.create! :title => 'Accelerando', :blurb_file => file_path.to_s
|
53
|
+
index
|
53
54
|
|
54
55
|
Book.search('cyberpunk').to_a.should == [book]
|
55
56
|
end
|
@@ -36,6 +36,18 @@ describe 'specifying SQL for index definitions' do
|
|
36
36
|
query.should match(/LEFT OUTER JOIN .tags./)
|
37
37
|
end
|
38
38
|
|
39
|
+
it "handles custom join SQL statements" do
|
40
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
|
41
|
+
index.definition_block = Proc.new {
|
42
|
+
indexes title
|
43
|
+
join "INNER JOIN foo ON foo.x = bar.y"
|
44
|
+
}
|
45
|
+
index.render
|
46
|
+
|
47
|
+
query = index.sources.first.sql_query
|
48
|
+
query.should match(/INNER JOIN foo ON foo.x = bar.y/)
|
49
|
+
end
|
50
|
+
|
39
51
|
it "handles GROUP BY clauses" do
|
40
52
|
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
|
41
53
|
index.definition_block = Proc.new {
|
@@ -83,6 +95,66 @@ describe 'specifying SQL for index definitions' do
|
|
83
95
|
query = index.sources.first.sql_query
|
84
96
|
query.should match(/WHERE .+title != 'secret'.+ GROUP BY/)
|
85
97
|
end
|
98
|
+
|
99
|
+
it "escapes new lines in SQL snippets" do
|
100
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
|
101
|
+
index.definition_block = Proc.new {
|
102
|
+
indexes title
|
103
|
+
has <<-SQL, as: :custom_attribute, type: :integer
|
104
|
+
ARRAY_AGG(
|
105
|
+
CONCAT(
|
106
|
+
something
|
107
|
+
)
|
108
|
+
)
|
109
|
+
SQL
|
110
|
+
}
|
111
|
+
index.render
|
112
|
+
|
113
|
+
query = index.sources.first.sql_query
|
114
|
+
query.should match(/\\\n/)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "joins each polymorphic relation" do
|
118
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:event)
|
119
|
+
index.definition_block = Proc.new {
|
120
|
+
indexes eventable.title, :as => :title
|
121
|
+
polymorphs eventable, :to => %w(Article Book)
|
122
|
+
}
|
123
|
+
index.render
|
124
|
+
|
125
|
+
query = index.sources.first.sql_query
|
126
|
+
query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
|
127
|
+
query.should match(/LEFT OUTER JOIN .books. ON .books.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Book'/)
|
128
|
+
query.should match(/articles\..title., books\..title./)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "concatenates references where that have column" do
|
132
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:event)
|
133
|
+
index.definition_block = Proc.new {
|
134
|
+
indexes eventable.title, :as => :title
|
135
|
+
polymorphs eventable, :to => %w(Article User)
|
136
|
+
}
|
137
|
+
index.render
|
138
|
+
|
139
|
+
query = index.sources.first.sql_query
|
140
|
+
query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
|
141
|
+
query.should match(/LEFT OUTER JOIN .users. ON .users.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'User'/)
|
142
|
+
query.should_not match(/articles\..title., users\..title./)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "respects deeper associations through polymorphic joins" do
|
146
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:event)
|
147
|
+
index.definition_block = Proc.new {
|
148
|
+
indexes eventable.user.name, :as => :user_name
|
149
|
+
polymorphs eventable, :to => %w(Article Book)
|
150
|
+
}
|
151
|
+
index.render
|
152
|
+
|
153
|
+
query = index.sources.first.sql_query
|
154
|
+
query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
|
155
|
+
query.should match(/LEFT OUTER JOIN .users. ON .users.\..id. = .articles.\..user_id./)
|
156
|
+
query.should match(/users\..name./)
|
157
|
+
end
|
86
158
|
end
|
87
159
|
|
88
160
|
describe 'separate queries for MVAs' do
|
@@ -247,6 +319,25 @@ describe 'separate queries for MVAs' do
|
|
247
319
|
query.should == 'My Custom SQL Query'
|
248
320
|
range.should == 'And a Range'
|
249
321
|
end
|
322
|
+
|
323
|
+
it "escapes new lines in custom SQL snippets" do
|
324
|
+
index.definition_block = Proc.new {
|
325
|
+
indexes title
|
326
|
+
has <<-SQL, :as => :tag_ids, :source => :query, :type => :integer, :multi => true
|
327
|
+
My Custom
|
328
|
+
SQL Query
|
329
|
+
SQL
|
330
|
+
}
|
331
|
+
index.render
|
332
|
+
|
333
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
334
|
+
attribute[/tag_ids/]
|
335
|
+
}
|
336
|
+
declaration, query = attribute.split(/;\s+/)
|
337
|
+
|
338
|
+
declaration.should == 'uint tag_ids from query'
|
339
|
+
query.should == "My Custom\\\nSQL Query"
|
340
|
+
end
|
250
341
|
end
|
251
342
|
|
252
343
|
describe 'separate queries for field' do
|
@@ -336,4 +427,20 @@ describe 'separate queries for field' do
|
|
336
427
|
query.should == 'My Custom SQL Query'
|
337
428
|
range.should == 'And a Range'
|
338
429
|
end
|
430
|
+
|
431
|
+
it "escapes new lines in custom SQL snippets" do
|
432
|
+
index.definition_block = Proc.new {
|
433
|
+
indexes <<-SQL, :as => :tags, :source => :query
|
434
|
+
My Custom
|
435
|
+
SQL Query
|
436
|
+
SQL
|
437
|
+
}
|
438
|
+
index.render
|
439
|
+
|
440
|
+
field = source.sql_joined_field.detect { |field| field[/tags/] }
|
441
|
+
declaration, query = field.split(/;\s+/)
|
442
|
+
|
443
|
+
declaration.should == 'tags from query'
|
444
|
+
query.should == "My Custom\\\nSQL Query"
|
445
|
+
end
|
339
446
|
end
|
@@ -40,4 +40,13 @@ describe 'SQL delta indexing', :live => true do
|
|
40
40
|
|
41
41
|
Book.search('Harry').should be_empty
|
42
42
|
end
|
43
|
+
|
44
|
+
it "automatically indexes new records of subclasses" do
|
45
|
+
book = Hardcover.create(
|
46
|
+
:title => 'American Gods', :author => 'Neil Gaiman'
|
47
|
+
)
|
48
|
+
sleep 0.25
|
49
|
+
|
50
|
+
Book.search('Gaiman').to_a.should == [book]
|
51
|
+
end
|
43
52
|
end
|
data/spec/internal/db/schema.rb
CHANGED
@@ -22,7 +22,8 @@ ActiveRecord::Schema.define do
|
|
22
22
|
t.string :author
|
23
23
|
t.integer :year
|
24
24
|
t.string :blurb_file
|
25
|
-
t.boolean :delta, :default => true,
|
25
|
+
t.boolean :delta, :default => true, :null => false
|
26
|
+
t.string :type, :default => 'Book', :null => false
|
26
27
|
t.timestamps
|
27
28
|
end
|
28
29
|
|
@@ -42,6 +43,11 @@ ActiveRecord::Schema.define do
|
|
42
43
|
t.timestamps
|
43
44
|
end
|
44
45
|
|
46
|
+
create_table(:events, :force => true) do |t|
|
47
|
+
t.string :eventable_type
|
48
|
+
t.integer :eventable_id
|
49
|
+
end
|
50
|
+
|
45
51
|
create_table(:genres, :force => true) do |t|
|
46
52
|
t.string :name
|
47
53
|
end
|