searchcraft 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.overcommit.yml +61 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +423 -0
- data/Rakefile +14 -0
- data/lib/search_craft.rb +3 -0
- data/lib/searchcraft/annotate.rb +42 -0
- data/lib/searchcraft/builder.rb +218 -0
- data/lib/searchcraft/configuration.rb +12 -0
- data/lib/searchcraft/depends_on.rb +42 -0
- data/lib/searchcraft/dump_schema.rb +19 -0
- data/lib/searchcraft/model.rb +50 -0
- data/lib/searchcraft/railtie.rb +22 -0
- data/lib/searchcraft/version.rb +5 -0
- data/lib/searchcraft/view_hash_store.rb +61 -0
- data/lib/searchcraft.rb +53 -0
- data/lib/tasks/builders.rake +25 -0
- data/lib/tasks/refresh.rake +12 -0
- data/sig/searchcraft.rbs +4 -0
- metadata +152 -0
@@ -0,0 +1,218 @@
|
|
1
|
+
class SearchCraft::Builder
|
2
|
+
extend SearchCraft::Annotate
|
3
|
+
include SearchCraft::DependsOn
|
4
|
+
include SearchCraft::DumpSchema
|
5
|
+
|
6
|
+
# Subclass must implement view_scope or view_select_sql
|
7
|
+
def view_scope
|
8
|
+
raise NotImplementedError, "Subclass must implement view_scope or view_select_sql"
|
9
|
+
end
|
10
|
+
|
11
|
+
# By default, assumes subclass implements view_scope to return
|
12
|
+
# an ActiveRecord::Relation.
|
13
|
+
# Alternately, override view_select_sql to return a SQL string.
|
14
|
+
def view_select_sql
|
15
|
+
@_view_select_sql ||= view_scope.to_sql
|
16
|
+
end
|
17
|
+
|
18
|
+
# Override if a Builder SQL has dependencies, such as extensions or text search config
|
19
|
+
# that are required first.
|
20
|
+
def dependencies_ready?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
# Iterate through subclasses, and invoke recreate_view_if_changed!
|
26
|
+
def rebuild_any_if_changed!
|
27
|
+
SearchCraft::ViewHashStore.setup_table_if_needed!
|
28
|
+
|
29
|
+
sorted_builders = sort_builders_by_dependency
|
30
|
+
|
31
|
+
# If tests, and after rails db:schema:load, the ViewHashStore table is empty.
|
32
|
+
# So just drop any views created from the schema.rb and we'll recreate them.
|
33
|
+
|
34
|
+
unless SearchCraft::ViewHashStore.any?
|
35
|
+
sorted_builders.each { |builder| builder.new.drop_view! }
|
36
|
+
end
|
37
|
+
|
38
|
+
builders_changed = []
|
39
|
+
sorted_builders.each do |builder|
|
40
|
+
changed = builder.new.recreate_view_if_changed!(builders_changed: builders_changed)
|
41
|
+
builders_changed << builder if changed
|
42
|
+
end
|
43
|
+
|
44
|
+
annotate_models!
|
45
|
+
end
|
46
|
+
|
47
|
+
def rebuild_all!
|
48
|
+
end
|
49
|
+
|
50
|
+
def recreate_indexes!
|
51
|
+
sorted_builders = sort_builders_by_dependency
|
52
|
+
sorted_builders.each { |builder| builder.new.recreate_indexes! }
|
53
|
+
end
|
54
|
+
|
55
|
+
def builders_to_rebuild
|
56
|
+
if SearchCraft.config.explicit_builder_class_names
|
57
|
+
SearchCraft.config.explicit_builder_class_names.map(&:constantize)
|
58
|
+
elsif Object.const_defined?(:Rails) && Rails.application
|
59
|
+
find_subclasses_via_rails_eager_load_paths.map(&:constantize)
|
60
|
+
else
|
61
|
+
subclasses
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Looks for subclasses of SearchCraft::Builder in Rails eager load paths
|
66
|
+
# and then any subclasses of those.
|
67
|
+
# Returns an array of class names
|
68
|
+
def find_subclasses_via_rails_eager_load_paths(known_subclass_names: [])
|
69
|
+
subclass_names = []
|
70
|
+
|
71
|
+
potential_superclass_names = known_subclass_names + ["SearchCraft::Builder"]
|
72
|
+
potential_superclass_regex = Regexp.new(potential_superclass_names.join("|"))
|
73
|
+
|
74
|
+
Rails.configuration.eager_load_paths.each do |load_path|
|
75
|
+
Dir.glob("#{load_path}/**/*.rb").each do |file|
|
76
|
+
File.readlines(file).each do |line|
|
77
|
+
if (match = line.match(/class\s+([\w:]+)\s*<\s*#{potential_superclass_regex}/))
|
78
|
+
class_name = match[1]
|
79
|
+
warn "Found #{class_name} in #{file}" unless known_subclass_names.include?(class_name)
|
80
|
+
subclass_names << class_name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
newly_found_subclass_names = subclass_names - known_subclass_names
|
87
|
+
if newly_found_subclass_names.any?
|
88
|
+
return find_subclasses_via_rails_eager_load_paths(known_subclass_names: subclass_names)
|
89
|
+
end
|
90
|
+
|
91
|
+
subclass_names
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Produces the SQL that will create the materialized view
|
96
|
+
def view_sql
|
97
|
+
# remove trailing ; from view_sql
|
98
|
+
inner_sql = view_select_sql.gsub(/;\s*$/, "")
|
99
|
+
"CREATE MATERIALIZED VIEW #{view_name} AS (#{inner_sql}) WITH DATA;"
|
100
|
+
end
|
101
|
+
|
102
|
+
# After materialized view created, do you need indexes on its columns?
|
103
|
+
def view_indexes
|
104
|
+
{}
|
105
|
+
end
|
106
|
+
|
107
|
+
# To indicate if view has changed, we store a hash of the SQL used to create it
|
108
|
+
# TODO: include the indexes SQL too
|
109
|
+
def view_sql_hash
|
110
|
+
Digest::SHA256.hexdigest(view_sql)
|
111
|
+
end
|
112
|
+
|
113
|
+
# If missing or changed, drop and create view
|
114
|
+
# Returns false if no change required
|
115
|
+
def recreate_view_if_changed!(builders_changed: [])
|
116
|
+
if SearchCraft.debug?
|
117
|
+
warn "#{self.class.name}#recreate_view_if_changed!"
|
118
|
+
warn " builders_changed: #{builders_changed.map(&:name).join(", ")}" if builders_changed.any?
|
119
|
+
end
|
120
|
+
return unless dependencies_ready?
|
121
|
+
|
122
|
+
dependencies_changed = (@@dependencies[self.class.name] || []) & builders_changed.map(&:name)
|
123
|
+
return false unless dependencies_changed.any? ||
|
124
|
+
SearchCraft::ViewHashStore.changed?(builder: self)
|
125
|
+
|
126
|
+
if SearchCraft.debug?
|
127
|
+
if !SearchCraft::ViewHashStore.exists?(builder: self)
|
128
|
+
warn "Creating #{view_name} because it doesn't yet exist"
|
129
|
+
elsif dependencies_changed.any?
|
130
|
+
warn "Recreating #{view_name} because dependencies changed: #{dependencies_changed.join(" ")}"
|
131
|
+
else
|
132
|
+
warn "Recreating #{view_name} because SQL changed"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
drop_view!
|
137
|
+
create_view!
|
138
|
+
update_hash_store!
|
139
|
+
dump_schema!
|
140
|
+
|
141
|
+
true
|
142
|
+
end
|
143
|
+
|
144
|
+
def create_view!
|
145
|
+
create_sequence!
|
146
|
+
sql_execute(view_sql)
|
147
|
+
create_indexes!
|
148
|
+
end
|
149
|
+
|
150
|
+
# Finds and drops all indexes and sequences on view, and then drops view
|
151
|
+
def drop_view!
|
152
|
+
sql_execute("DROP MATERIALIZED VIEW IF EXISTS #{view_name} CASCADE;")
|
153
|
+
|
154
|
+
sql_execute("DROP SEQUENCE IF EXISTS #{view_id_sequence_name};")
|
155
|
+
|
156
|
+
SearchCraft::ViewHashStore.reset!(builder: self)
|
157
|
+
end
|
158
|
+
|
159
|
+
def recreate_indexes!
|
160
|
+
drop_indexes!
|
161
|
+
create_indexes!
|
162
|
+
end
|
163
|
+
|
164
|
+
# Pluralized table name of class
|
165
|
+
def view_name
|
166
|
+
base_sql_name
|
167
|
+
end
|
168
|
+
|
169
|
+
protected
|
170
|
+
|
171
|
+
# CREATE SEQUENCE #{view_id_sequence_name} CYCLE;
|
172
|
+
# DROP SEQUENCE #{view_id_sequence_name};
|
173
|
+
def view_id_sequence_name
|
174
|
+
"#{base_sql_name}_seq"
|
175
|
+
end
|
176
|
+
|
177
|
+
# ProductSearchBuilder name becomes product_searches
|
178
|
+
def base_sql_name
|
179
|
+
self.class.name.gsub(/Builder$/, "").tableize.tr("/", "_")
|
180
|
+
end
|
181
|
+
|
182
|
+
def base_idx_name
|
183
|
+
"idx_#{base_sql_name}"
|
184
|
+
end
|
185
|
+
|
186
|
+
def create_sequence!
|
187
|
+
sql_execute("CREATE SEQUENCE #{view_id_sequence_name} CYCLE;")
|
188
|
+
end
|
189
|
+
|
190
|
+
def drop_indexes!
|
191
|
+
simple_view_name = view_name.gsub(/^.+\./, "")
|
192
|
+
indexes = sql_execute("SELECT indexname FROM pg_indexes WHERE tablename = '#{simple_view_name}';")
|
193
|
+
indexes.each do |index|
|
194
|
+
warn "DROP INDEX IF EXISTS #{index["indexname"]};" if SearchCraft.debug?
|
195
|
+
sql_execute("DROP INDEX IF EXISTS #{index["indexname"]};")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def create_indexes!
|
200
|
+
view_indexes.each do |index_name, index_options|
|
201
|
+
columns = index_options[:columns]
|
202
|
+
name = "#{base_idx_name}_#{index_name}"
|
203
|
+
options = index_options.except(:columns).merge({name: name})
|
204
|
+
|
205
|
+
warn "ActiveRecord::Base.connection.add_index(#{view_name.inspect}, #{columns.inspect}, #{options.inspect})" if SearchCraft.debug?
|
206
|
+
ActiveRecord::Base.connection.add_index(view_name, columns, **options)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def update_hash_store!
|
211
|
+
SearchCraft::ViewHashStore.update_for(builder: self)
|
212
|
+
end
|
213
|
+
|
214
|
+
def sql_execute(sql)
|
215
|
+
warn sql if SearchCraft.debug?
|
216
|
+
ActiveRecord::Base.connection.execute(sql)
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SearchCraft::DependsOn
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class_methods do
|
5
|
+
@@dependencies = {}
|
6
|
+
|
7
|
+
def depends_on(*builder_names)
|
8
|
+
@@dependencies[name] = builder_names
|
9
|
+
end
|
10
|
+
|
11
|
+
# TODO: implement .add_index instead of #view_indexes below
|
12
|
+
def add_index(index_name, columns, unique: false, name: nil)
|
13
|
+
@indexes ||= {}
|
14
|
+
# TODO: also get indexes from @@dependencies[name]
|
15
|
+
@indexes[index_name] = {columns: columns, unique: unique, name: name}
|
16
|
+
end
|
17
|
+
|
18
|
+
def sort_builders_by_dependency
|
19
|
+
sorted = []
|
20
|
+
visited = {}
|
21
|
+
|
22
|
+
builders_to_rebuild.each do |builder|
|
23
|
+
visit(builder, visited, sorted)
|
24
|
+
end
|
25
|
+
|
26
|
+
sorted
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit(builder, visited, sorted)
|
30
|
+
return if visited[builder.name]
|
31
|
+
|
32
|
+
dependency_names = @@dependencies[builder.name] || []
|
33
|
+
dependency_names.each do |dependency_name|
|
34
|
+
dependency = Object.const_get(dependency_name)
|
35
|
+
visit(dependency, visited, sorted)
|
36
|
+
end
|
37
|
+
|
38
|
+
visited[builder.name] = true
|
39
|
+
sorted << builder
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SearchCraft::DumpSchema
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
# If in Rails, dump schema.rb after rebuilding views
|
5
|
+
def dump_schema!
|
6
|
+
return unless Rails.env.development?
|
7
|
+
require "active_record/tasks/database_tasks"
|
8
|
+
|
9
|
+
env = Rails.env
|
10
|
+
db_configs = ActiveRecord::Base.configurations.configs_for(env_name: env)
|
11
|
+
db_configs.each do |db_config|
|
12
|
+
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ActiveRecord.schema_format)
|
13
|
+
end
|
14
|
+
rescue ActiveRecord::NoDatabaseError
|
15
|
+
rescue => e
|
16
|
+
warn "Error dumping schema: #{e.message}"
|
17
|
+
pp e.backtrace
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "scenic"
|
2
|
+
|
3
|
+
module SearchCraft::Model
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def read_only?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
self.table_name = name.tableize.tr("/", "_")
|
11
|
+
end
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
def refresh!
|
15
|
+
Scenic.database.refresh_materialized_view(table_name, concurrently: @refresh_concurrently, cascade: false)
|
16
|
+
end
|
17
|
+
|
18
|
+
def refresh_concurrently=(value)
|
19
|
+
@refresh_concurrently = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Maintain a list of classes that include this module
|
24
|
+
@included_classes = []
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# Class method to add a class to the list of included classes
|
28
|
+
def included(base)
|
29
|
+
@included_classes << base
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def included_classes
|
34
|
+
if SearchCraft.config.explicit_model_class_names
|
35
|
+
return SearchCraft.config.explicit_model_class_names.map(&:constantize)
|
36
|
+
end
|
37
|
+
@included_classes
|
38
|
+
end
|
39
|
+
|
40
|
+
# Runs .refresh! on all classes that include SearchCraft::Model
|
41
|
+
# TODO: eager load all classes that include SearchCraft::Model;
|
42
|
+
# perhaps via Builder eager loading?
|
43
|
+
def refresh_all!
|
44
|
+
included_classes.each do |klass|
|
45
|
+
warn "Refreshing materialized view #{klass.table_name}..." unless Rails.env.test?
|
46
|
+
klass.refresh!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "rails/railtie"
|
2
|
+
|
3
|
+
module SearchCraft
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer "searchcraft.reloader_hook" do
|
6
|
+
ActiveSupport::Reloader.to_prepare do
|
7
|
+
next unless SearchCraft.database_ready?
|
8
|
+
next unless SearchCraft.config.autorebuild?
|
9
|
+
next unless SearchCraft.dependencies_ready?
|
10
|
+
|
11
|
+
warn "[#{Rails.env}] running: SearchCraft::Builder.rebuild_any_if_changed!" if SearchCraft.debug?
|
12
|
+
|
13
|
+
SearchCraft::Builder.rebuild_any_if_changed!
|
14
|
+
rescue => e
|
15
|
+
if SearchCraft.debug?
|
16
|
+
puts "Preparing SearchCraft: #{e.message}"
|
17
|
+
puts e.backtrace
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Stores the sha265 hash of the SQL used to create each view.
|
2
|
+
# This allows the Builder to determine if a view needs
|
3
|
+
# to be recreated. We don't want to use ActiveRecord Migrations,
|
4
|
+
# instead want the view to be automatically recreated when
|
5
|
+
# any Builder's #view_select_sql SQL changes.
|
6
|
+
#
|
7
|
+
# The view SQL hashes are stored in a table named
|
8
|
+
# search_craft_view_hash_stores, which is automatically
|
9
|
+
# created by SearchCraft.
|
10
|
+
class SearchCraft::ViewHashStore < ActiveRecord::Base
|
11
|
+
self.table_name = "search_craft_view_hash_stores"
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def update_for(builder:)
|
15
|
+
setup_table_if_needed!
|
16
|
+
view_sql_hash = builder.view_sql_hash
|
17
|
+
view_hash_store = find_or_initialize_by(view_name: builder.view_name)
|
18
|
+
view_hash_store.update!(view_sql_hash: view_sql_hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
def changed?(builder:)
|
22
|
+
setup_table_if_needed!
|
23
|
+
|
24
|
+
view_sql_hash = builder.view_sql_hash
|
25
|
+
view_hash_store = exists?(builder: builder)
|
26
|
+
!view_hash_store || view_hash_store.view_sql_hash != view_sql_hash
|
27
|
+
end
|
28
|
+
|
29
|
+
def exists?(builder:)
|
30
|
+
setup_table_if_needed!
|
31
|
+
connection.schema_cache.data_source_exists?(builder.view_name) &&
|
32
|
+
find_by(view_name: builder.view_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset!(builder:)
|
36
|
+
view_hash_store = find_by(view_name: builder.view_name)
|
37
|
+
view_hash_store&.destroy!
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup_table_if_needed!
|
41
|
+
return if table_exists?
|
42
|
+
|
43
|
+
# TODO: store its own sha265 hash in a table,
|
44
|
+
# so we can detect if it changes during development
|
45
|
+
# and recreate table if table is empty -- just in case
|
46
|
+
|
47
|
+
# Migrate table
|
48
|
+
create_table_sql = <<~SQL
|
49
|
+
CREATE TABLE search_craft_view_hash_stores (
|
50
|
+
id serial primary key,
|
51
|
+
view_name varchar(255) not null,
|
52
|
+
view_sql_hash varchar(255) not null,
|
53
|
+
created_at timestamp not null default now(),
|
54
|
+
updated_at timestamp not null default now()
|
55
|
+
);
|
56
|
+
SQL
|
57
|
+
ActiveRecord::Base.connection.execute(create_table_sql)
|
58
|
+
reset_column_information
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/searchcraft.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SearchCraft
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def configure
|
9
|
+
yield(config)
|
10
|
+
end
|
11
|
+
|
12
|
+
def config
|
13
|
+
@config ||= Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def database_ready?
|
17
|
+
ActiveRecord::Base.connection.table_exists?("schema_migrations")
|
18
|
+
rescue
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def dependencies_ready?
|
23
|
+
config.explicit_builder_class_names.all? do |builder_class_name|
|
24
|
+
builder_class_name.constantize.new.dependencies_ready?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def debug?
|
29
|
+
config.debug
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_tasks
|
33
|
+
return if @tasks_loaded
|
34
|
+
|
35
|
+
Dir[File.join(File.dirname(__FILE__), "tasks", "**/*.rake")].each do |rake|
|
36
|
+
load rake
|
37
|
+
end
|
38
|
+
|
39
|
+
@tasks_loaded = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
require "active_record"
|
44
|
+
|
45
|
+
require_relative "searchcraft/version"
|
46
|
+
require_relative "searchcraft/configuration"
|
47
|
+
require_relative "searchcraft/annotate"
|
48
|
+
require_relative "searchcraft/depends_on"
|
49
|
+
require_relative "searchcraft/dump_schema"
|
50
|
+
require_relative "searchcraft/builder"
|
51
|
+
require_relative "searchcraft/model"
|
52
|
+
require_relative "searchcraft/view_hash_store"
|
53
|
+
require_relative "searchcraft/railtie" if defined?(Rails)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
namespace :searchcraft do
|
2
|
+
desc "Recreates search builders' materialized views if necessary"
|
3
|
+
task rebuild: :environment do
|
4
|
+
puts "Rebuilding search builders' materialized views if necessary"
|
5
|
+
puts Benchmark.measure {
|
6
|
+
SearchCraft::Builder.rebuild_any_if_changed!
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Recreates all materialized views' indices"
|
11
|
+
task recreate_indexes: :environment do
|
12
|
+
puts "Recreating search builders' indices"
|
13
|
+
puts Benchmark.measure {
|
14
|
+
SearchCraft::Builder.recreate_indexes!
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Force recreate all materialize views, including indexes and sequences"
|
19
|
+
task force_recreate: :environment do
|
20
|
+
puts "Force recreateing search builders' materialized views"
|
21
|
+
puts Benchmark.measure {
|
22
|
+
SearchCraft::Builder.rebuild_all!
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
namespace :searchcraft do
|
2
|
+
desc "Refresh searchcraft materialized views"
|
3
|
+
task refresh: :environment do
|
4
|
+
SearchCraft::Builder.rebuild_any_if_changed!
|
5
|
+
require "benchmark"
|
6
|
+
SearchCraft.config.explicit_model_class_names.each do |model_class_name|
|
7
|
+
klass = model_class_name.constantize
|
8
|
+
puts "Refreshing materialized views for #{klass.name}"
|
9
|
+
puts Benchmark.measure { klass.refresh! }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/sig/searchcraft.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: searchcraft
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dr Nic Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-10-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: scenic
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Use your SQL database to power instant search for your Rails app with
|
98
|
+
materalized views
|
99
|
+
email:
|
100
|
+
- drnicwilliams@gmail.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".DS_Store"
|
106
|
+
- ".overcommit.yml"
|
107
|
+
- ".standard.yml"
|
108
|
+
- CHANGELOG.md
|
109
|
+
- CODE_OF_CONDUCT.md
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- lib/search_craft.rb
|
114
|
+
- lib/searchcraft.rb
|
115
|
+
- lib/searchcraft/annotate.rb
|
116
|
+
- lib/searchcraft/builder.rb
|
117
|
+
- lib/searchcraft/configuration.rb
|
118
|
+
- lib/searchcraft/depends_on.rb
|
119
|
+
- lib/searchcraft/dump_schema.rb
|
120
|
+
- lib/searchcraft/model.rb
|
121
|
+
- lib/searchcraft/railtie.rb
|
122
|
+
- lib/searchcraft/version.rb
|
123
|
+
- lib/searchcraft/view_hash_store.rb
|
124
|
+
- lib/tasks/builders.rake
|
125
|
+
- lib/tasks/refresh.rake
|
126
|
+
- sig/searchcraft.rbs
|
127
|
+
homepage: https://github.com/drnic/searchcraft
|
128
|
+
licenses:
|
129
|
+
- MIT
|
130
|
+
metadata:
|
131
|
+
allowed_push_host: https://rubygems.org
|
132
|
+
homepage_uri: https://github.com/drnic/searchcraft
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '3.0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubygems_version: 3.4.20
|
149
|
+
signing_key:
|
150
|
+
specification_version: 4
|
151
|
+
summary: Instant search for Rails and ActiveRecord
|
152
|
+
test_files: []
|