searchcraft 0.4.0
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/.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: []
|