groovy 0.1.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36ab3d8425ae05fd7b069e75b1cb3f168a8a31605e2bd54d3b44285f715dbc92
4
- data.tar.gz: 7d48ca3d630488d52c0fd60e73ac753d5b1658601f6803566838c563231cce9a
3
+ metadata.gz: b2968c89f9c0019f1984b53b34a1d5c4520e82bede4f56020a7ef490c76897fe
4
+ data.tar.gz: 6de69f1590be6a8bddd69f60bed379e8e59e75dcd2a9a00c5696ef5160b211a5
5
5
  SHA512:
6
- metadata.gz: 2dfafb96a0e3636a7e49c9319a40d7e7217d6c21ed48bc0be6e8a4a61aee9a7ce41f9d3499987a03d16e0dc15868988d6a03049a8aec401b70f96b4c405e47b4
7
- data.tar.gz: 90e22b09f631228277f297c552f42bbdf0bfc9faaccbea13c769b2f68b91cc297c157a1557a101790d0497d2390ba466f4f44f97bf5ac1a3d4f267666b36465e
6
+ metadata.gz: 1b7be573999c72a9a5bb1a5b140388644702430c51a07a315d163c8a6f7db89058416ebcc252351498fd5d3b5e86d2cafeea06653e396bc6ddcf3d4bfaf6e6d3
7
+ data.tar.gz: 8693eb5c4472d634b62ad3be13e57e1c54172374aef7f8792807b107de068457bef293106437521b9200977db95122b524e2182438441fee780e72c15f129f1f
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/example/basic.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'bundler/setup'
2
+ require 'groovy'
3
+
4
+ Groovy.open('./db/products')
5
+
6
+ class Product
7
+ include Groovy::Model
8
+
9
+ schema do |t|
10
+ t.column :name, String
11
+ t.column :price, Integer
12
+ t.timestamps
13
+ end
14
+ end
15
+
16
+ 50_000.times do |i|
17
+ puts "Creating product #{i}" if i % 1000 == 0
18
+ Product.create!(name: "A product with index #{i}", price: 10000 + i)
19
+ end
20
+
21
+ puts Product.count
22
+
23
+ # 50_000 products: 50M
24
+ # 100_000 products: 50M
25
+ # 500_000 products: 62M
data/example/config.ru CHANGED
@@ -1,15 +1,5 @@
1
- require 'bundler/setup'
2
- require 'groovy'
3
- Groovy.init('./db/test')
4
-
5
- class Place
6
- include Groovy::Model
7
-
8
- # table_options type: :hash
9
- column :name, String
10
- column :description, String, searchable: true
11
- timestamps!
12
- end
1
+ require './models'
2
+ require 'groovy/middleware'
13
3
 
14
4
  Place.delete_all
15
5
 
@@ -20,8 +10,9 @@ end
20
10
  Place.last.delete
21
11
 
22
12
  app = Proc.new do |env|
23
- body = Place.all.collect(&:as_json)
13
+ body = Place.all.collect(&:as_json).inspect
24
14
  [200, { 'Content-Type' => 'text/plain' }, [body]]
25
15
  end
26
16
 
17
+ use Groovy::MemoryPoolMiddleware
27
18
  run app
@@ -0,0 +1,47 @@
1
+ require 'bundler/setup'
2
+ require 'groovy'
3
+
4
+ Groovy.open('./db/2019', :current)
5
+ Groovy.open('./db/2020', :next)
6
+
7
+ module Groovy::Model::ClassMethods
8
+ def context_name
9
+ Time.now.year == 2019 ? :current : :next
10
+ end
11
+
12
+ def table
13
+ obj = db_context[table_name]
14
+ schema.sync(db_context) if obj.nil?
15
+ db_context[table_name]
16
+ end
17
+ end
18
+
19
+ class Category
20
+ include Groovy::Model
21
+
22
+ schema do |t|
23
+ t.column :name, String
24
+ t.timestamps
25
+ end
26
+ end
27
+
28
+ class Place
29
+ include Groovy::Model
30
+
31
+ schema do |t|
32
+ t.reference :categories, "Categories", type: :vector
33
+ t.column :name, String
34
+ t.column :description, String, index: true
35
+ t.timestamps
36
+ end
37
+ end
38
+
39
+ class Location
40
+ include Groovy::Model
41
+
42
+ schema do |t|
43
+ t.reference :place, "Places"
44
+ t.column :coords, String
45
+ t.timestamps
46
+ end
47
+ end
data/lib/groovy.rb CHANGED
@@ -2,18 +2,59 @@ require 'groonga'
2
2
  require File.expand_path(File.dirname(__FILE__)) + '/groovy/model'
3
3
 
4
4
  module Groovy
5
- VERSION = '0.1.0'.freeze
6
-
7
- def self.init(db_path, opts = {})
8
- if File.exist?(db_path)
9
- puts "Opening DB"
10
- Groonga::Database.open(db_path)
11
- else
12
- dir = File.dirname(db_path)
13
- puts "Creating DB in #{dir}"
14
- FileUtils.mkdir_p(dir)
15
- Groonga::Database.create(path: db_path)
5
+ VERSION = '0.1.1'.freeze
6
+
7
+ class << self
8
+
9
+ def contexts
10
+ @contexts ||= {}
11
+ end
12
+
13
+ def [](key)
14
+ contexts[key.to_sym]
15
+ end
16
+
17
+ def first_context_name
18
+ contexts.keys.first
19
+ end
20
+
21
+ def open(db_path, name = :default, opts = {})
22
+ raise "Context already defined: #{name}" if contexts[name.to_sym]
23
+ contexts[name.to_sym] = if name == :default
24
+ Groonga::Context.default.tap { |ctx| open_or_create_db(ctx, db_path) }
25
+ else
26
+ init_context(db_path, opts)
27
+ end
28
+ end
29
+
30
+ def logger=(obj)
31
+ @logger = obj
16
32
  end
33
+
34
+ def logger
35
+ @logger ||= Logger.new(STDOUT)
36
+ end
37
+
38
+ private
39
+
40
+ def init_context(db_path, opts)
41
+ Groonga::Context.new(opts).tap do |ctx|
42
+ open_or_create_db(ctx, db_path)
43
+ end
44
+ end
45
+
46
+ def open_or_create_db(ctx, path)
47
+ if File.exist?(path)
48
+ logger.info "Opening DB at #{path}"
49
+ ctx.open_database(path)
50
+ else
51
+ dir = File.dirname(path)
52
+ logger.info "Creating DB in #{dir}"
53
+ FileUtils.mkdir_p(dir)
54
+ ctx.create_database(path)
55
+ end
56
+ end
57
+
17
58
  end
18
59
 
19
- end
60
+ end
@@ -0,0 +1,14 @@
1
+ module Groovy
2
+ class MemoryPoolMiddleware
3
+ def initialize(app, options = {})
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ Groovy.contexts.each { |name, ctx| ctx.push_memory_pool }
9
+ code, headers, body = @app.call(env)
10
+ Groovy.contexts.each { |name, ctx| ctx.pop_memory_pool }
11
+ [code, headers, body]
12
+ end
13
+ end
14
+ end
data/lib/groovy/model.rb CHANGED
@@ -1,6 +1,6 @@
1
- require File.expand_path(File.dirname(__FILE__)) + '/query'
2
- require File.expand_path(File.dirname(__FILE__)) + '/types'
3
1
  require File.expand_path(File.dirname(__FILE__)) + '/schema'
2
+ require File.expand_path(File.dirname(__FILE__)) + '/vector'
3
+ require File.expand_path(File.dirname(__FILE__)) + '/query'
4
4
 
5
5
  class Hash
6
6
  def symbolize_keys
@@ -11,69 +11,52 @@ end unless {}.respond_to?(:symbolize_keys)
11
11
  module Groovy
12
12
  module Model
13
13
 
14
- SEARCH_TABLE_NAME = 'Terms'
14
+ def self.initialize_from_record(obj)
15
+ model = model_from_table(obj.table.name)
16
+ model.new_from_record(obj)
17
+ end
18
+
19
+ def self.model_from_table(table_name)
20
+ Kernel.const_get(table_name.sub(/ies$/, 'y').sub(/s$/, ''))
21
+ end
15
22
 
16
23
  def self.included(base)
17
24
  base.extend(ClassMethods)
18
25
  base.include(Forwardable)
19
- base.table_name = base.name + 's'
20
-
21
- if base.table.nil?
22
- # abort "Please set up your database before declaring your models."
23
- else
24
- base.extend(PatriciaTrieMethods) if base.table.is_a?(Groonga::PatriciaTrie)
25
- base.class_eval do
26
- attribute_names.each do |col|
27
- add_accessor(col)
28
- end
29
- end # if base.table
30
- end
26
+ base.table_name = base.name.sub(/y$/, 'ie') + 's'
31
27
  end
32
28
 
33
29
  module ClassMethods
34
30
  extend Forwardable
35
- attr_accessor :table_name
31
+ attr_accessor :context_name, :table_name
36
32
 
37
33
  def validatable!
38
34
  include(ActiveModel::Validations)
39
35
  end
40
36
 
41
37
  def table
42
- # raise "Table name not set!" if table_name.nil?
43
- # schema.table
44
- Groonga[table_name]
45
- end
46
-
47
- def table_options(opts)
48
- @table_options = opts
49
- end
50
-
51
- def schema
52
- @schema ||= Schema.new(table_name, @table_options)
38
+ db_context[table_name]
53
39
  end
54
40
 
55
- def column(name, type, options = {})
56
- column_type = Types.map(type)
57
- schema.column(name, column_type, options)
58
- add_accessor(name)
41
+ def attribute_names
42
+ schema.attribute_columns
59
43
  end
60
44
 
61
- def timestamps!(opts = {})
62
- column(:created_at, 'Time')
63
- column(:updated_at, 'Time')
64
- end
45
+ def schema(options = {}, &block)
46
+ @schema ||= begin
47
+ self.context_name = options[:context] || Groovy.first_context_name
48
+ self.table_name = options[:table_name] if options[:table_name]
65
49
 
66
- # def columns
67
- # table.columns # .collect { |x| x.name.split('.').last.to_sym }
68
- # end
69
-
70
- def add_accessor(col)
71
- define_method(col) { self[col] }
72
- define_method("#{col}=") { |val| self[col] = val }
73
- end
50
+ s = Schema.new(db_context, table_name, {})
51
+ yield s if block
52
+ s.sync
74
53
 
75
- def attribute_names
76
- table.columns.select { |col| !col.reference_column? }.map { |x| x.name.split('.').last.to_sym }
54
+ extend(PatriciaTrieMethods) if table.is_a?(Groonga::PatriciaTrie)
55
+ s.attribute_columns.each { |col| add_attr_accessors(col) }
56
+ s.singular_references.each { |col| add_ref_accessors(col) }
57
+ s.plural_references.each { |col| add_vector_accessors(col) }
58
+ s
59
+ end
77
60
  end
78
61
 
79
62
  def scope(name, obj)
@@ -82,25 +65,20 @@ module Groovy
82
65
  end
83
66
  end
84
67
 
85
- def find(id)
86
- if record = table[id] and record.id
87
- new(record.attributes, record)
88
- end
68
+ # called from Query, so needs to be public
69
+ def new_from_record(record)
70
+ new(record.attributes, record)
89
71
  end
90
72
 
91
- def insert(key, attributes = nil)
92
- if table.support_key?
93
- table.add(key, attributes)
94
- else # key is attributes
95
- set_timestamp(key, :created_at)
96
- set_timestamp(key, :updated_at)
97
- table.add(key)
73
+ def find(id)
74
+ if record = table[id] and record.id
75
+ new_from_record(record)
98
76
  end
99
77
  end
100
78
 
101
79
  def create(key, attributes = nil)
102
80
  if record = insert(key, attributes)
103
- new(record.attributes, record)
81
+ new_from_record(record)
104
82
  end
105
83
  end
106
84
 
@@ -112,36 +90,15 @@ module Groovy
112
90
  schema.rebuild!
113
91
  end
114
92
 
115
- def search_columns
116
- @search_columns ||= []
117
- end
118
-
119
- def searchable_on(*fields)
120
- fields.each { |f| add_index_on(f) }
121
- end
122
-
123
- def add_index_on(field)
124
- ensure_search_table
125
- search_columns.push(field)
126
- Groonga::Schema.change_table(SEARCH_TABLE_NAME) do |table|
127
- table.index([table_name, field].join('.'))
128
- end
129
- end
130
-
131
- def ensure_search_table
132
- return if Groonga[SEARCH_TABLE_NAME]
133
- Groonga::Schema.create_table(SEARCH_TABLE_NAME, {
134
- type: :patricia_trie,
135
- normalizer: :NormalizerAuto,
136
- default_tokenizer: "TokenBigram"
137
- })
138
- end
139
-
140
93
  # def column(name)
141
94
  # Groonga["#{table_name}.#{name}"] # .search, .similar_search, etc
142
95
  # end
143
96
 
144
97
  def similar_search(col, q)
98
+ unless schema.index_columns.include?(col.to_sym)
99
+ raise "Column '#{col}' doesn't have an index set!"
100
+ end
101
+
145
102
  table.select { |r| r[col].similar_search(q) }
146
103
  # table.select("#{col}:#{q}", operator: Groonga::Operation::SIMILAR)
147
104
  end
@@ -153,7 +110,7 @@ module Groovy
153
110
  def_instance_delegators :all, :first, :last
154
111
 
155
112
  def find_by(params)
156
- where(params).first
113
+ where(params).limit(1).first
157
114
  end
158
115
 
159
116
  [:search, :where, :not, :sort_by, :limit, :offset].each do |scope_method|
@@ -172,17 +129,45 @@ module Groovy
172
129
 
173
130
  def_instance_delegators :table, :count, :size
174
131
 
132
+ private
133
+
134
+ def db_context
135
+ Groovy.contexts[context_name] or raise "Context not defined: #{context_name}"
136
+ end
137
+
138
+ def insert(key, attributes = nil)
139
+ if table.support_key?
140
+ table.add(key, attributes)
141
+ else # key is attributes
142
+ set_timestamp(key, :created_at)
143
+ set_timestamp(key, :updated_at)
144
+ table.add(key)
145
+ end
146
+ end
147
+
175
148
  def set_timestamp(obj, key_name)
176
149
  obj[key_name] = Time.now if attribute_names.include?(key_name.to_sym)
177
150
  end
178
151
 
152
+ def add_attr_accessors(col)
153
+ define_method(col) { self[col] }
154
+ define_method("#{col}=") { |val| self[col] = val }
155
+ end
156
+
157
+ def add_ref_accessors(key)
158
+ define_method(key) { get_ref(key) }
159
+ define_method("#{key}=") { |val| set_ref(key, val) }
160
+ end
161
+
162
+ def add_vector_accessors(key)
163
+ define_method(key) { @vectors[key] ||= Vector.new(self, key) }
164
+ define_method("#{key}=") { |items| self.public_send(key).set(items) }
165
+ end
179
166
  end
180
167
 
181
168
  module PatriciaTrieMethods
182
169
  extend Forwardable
183
-
184
- # def_instance_delegators :@table, :scan, :search, :prefix_search, :open_prefix_cursor, :tag_keys
185
- def_instance_delegators :table, :scan, :search, :prefix_search, :open_prefix_cursor, :tag_keys
170
+ def_instance_delegators :table, :scan, :prefix_search, :open_prefix_cursor, :tag_keys
186
171
 
187
172
  def each_with_prefix(prefix, options = {}, &block)
188
173
  table.open_prefix_cursor(prefix, options) do |cursor|
@@ -191,15 +176,22 @@ module Groovy
191
176
  end
192
177
  end
193
178
 
194
- attr_reader :attributes, :record, :changes
179
+ attr_reader :attributes, :refs, :record, :changes
180
+
181
+ def initialize(attributes = nil, record = nil)
182
+ @attributes = (attributes || {}).symbolize_keys.slice(*self.class.attribute_names)
183
+ @refs, @vectors = {}, {}
184
+
185
+ if @record = record
186
+ # TODO: lazy load this
187
+ self.class.schema.singular_references.each { |col| set_ref(col, record.public_send(col)) }
188
+ end
195
189
 
196
- def initialize(attributes, record = nil)
197
- @attributes = attributes.symbolize_keys.slice(*self.class.attribute_names)
198
- @record = record
199
190
  @changes = {}
200
191
  end
201
192
 
202
193
  def key
194
+ return unless record
203
195
  record.respond_to?(:_key) ? record._key : record.id
204
196
  end
205
197
 
@@ -208,9 +200,13 @@ module Groovy
208
200
  end
209
201
 
210
202
  def []=(key, val)
211
- raise "Invalid attribute: #{key}" unless attributes.has_key?(key.to_sym)
212
- changes[key.to_sym] = [self[key], val]
213
- attributes[key.to_sym] = val
203
+ return set_ref(key, val) if val.respond_to?(:record) || val.is_a?(Groonga::Record)
204
+
205
+ unless self.class.attribute_names.include?(key.to_sym)
206
+ raise "Invalid attribute: #{key}"
207
+ end
208
+
209
+ set_attribute(key, val)
214
210
  end
215
211
 
216
212
  def increment(values, do_save = true)
@@ -244,8 +240,8 @@ module Groovy
244
240
  def reload
245
241
  raise "Not persisted" if key.nil?
246
242
  record = self.class.table[key]
247
- @attributes = record.attributes.symbolize_keys
248
- @changes = {}
243
+ attributes = record.attributes.symbolize_keys
244
+ changes = {}
249
245
  self
250
246
  end
251
247
 
@@ -255,6 +251,24 @@ module Groovy
255
251
 
256
252
  private
257
253
 
254
+ def set_attribute(key, val)
255
+ changes[key.to_sym] = [self[key], val] if changes # nil when initializing
256
+ attributes[key.to_sym] = val
257
+ end
258
+
259
+ def get_ref(name)
260
+ @refs[name]
261
+ end
262
+
263
+ def set_ref(name, obj)
264
+ unless obj.nil? || obj.respond_to?(:record)
265
+ obj = Model.initialize_from_record(obj)
266
+ end
267
+
268
+ @refs[name] = obj
269
+ set_attribute(name, obj.nil? ? nil : obj.key)
270
+ end
271
+
258
272
  def create
259
273
  @record = self.class.insert(attributes)
260
274
  self
@@ -262,12 +276,12 @@ module Groovy
262
276
 
263
277
  def update
264
278
  raise "Not persisted" unless key
265
- changes.merge.each do |key, values|
279
+ changes.each do |key, values|
266
280
  # puts "Updating #{key} from #{values[0]} to #{values[1]}"
267
281
  record[key] = values.last
268
282
  end
269
283
  self.class.set_timestamp(record, :updated_at)
270
- @changes = {}
284
+ changes = {}
271
285
  self
272
286
  end
273
287
 
data/lib/groovy/query.rb CHANGED
@@ -7,17 +7,6 @@ module Groovy
7
7
 
8
8
  attr_reader :parameters, :sorting
9
9
 
10
- # options:
11
- # - "operator"
12
- # - "result"
13
- # - "name"
14
- # - "syntax"
15
- # - "allow_pragma"
16
- # - "allow_column"
17
- # - "allow_update"
18
- # - "allow_leading_not"
19
- # - "default_column"
20
-
21
10
  def initialize(model, table, options = {})
22
11
  @model, @table, @options = model, table, options
23
12
  @parameters = options.delete(:parameters) || []
data/lib/groovy/schema.rb CHANGED
@@ -1,122 +1,161 @@
1
+ require File.expand_path(File.dirname(__FILE__)) + '/types'
2
+
1
3
  module Groovy
2
4
  class Schema
3
- attr_reader :table, :table_name
4
5
 
5
- def initialize(table_name, opts = {})
6
- @table_name, @opts = table_name, opts
7
- @columns = {}
6
+ SEARCH_TABLE_NAME = 'Terms'.freeze
7
+ COLUMN_DEFAULTS = {
8
+ compress: :zstandard
9
+ }.freeze
10
+
11
+ attr_reader :index_columns
12
+
13
+ def initialize(context, table_name, opts = {})
14
+ @context, @table_name, @opts = context, table_name, opts || {}
15
+ @spec, @index_columns = {}, []
8
16
  end
9
17
 
10
18
  def table
11
- @table ||= Groonga[table_name]
19
+ @table ||= context[table_name]
12
20
  end
13
21
 
14
- def rebuild!
15
- remove_table! if table
16
- create_table!
17
- @columns.each do |name, spec|
18
- check_and_add_column(name, spec[:type], spec[:options])
19
- end
20
- puts "done rebuilding"
22
+ def search_table
23
+ @search_table ||= context[SEARCH_TABLE_NAME]
21
24
  end
22
25
 
23
- def ensure_created!
24
- create_table! if table.nil?
26
+ def column_names
27
+ get_names table.columns
28
+ end
29
+
30
+ def singular_references
31
+ get_names table.columns.select(&:reference_column?).reject(&:vector?)
32
+ end
33
+
34
+ def plural_references
35
+ get_names table.columns.select(&:vector?)
36
+ end
37
+
38
+ def attribute_columns
39
+ get_names table.columns.select(&:column?)
40
+ end
41
+
42
+ def rebuild!
43
+ log("Rebuilding!")
44
+ # remove_table! if table
45
+ remove_columns_not_in([])
46
+ sync
25
47
  end
26
48
 
27
49
  def column(name, type, options = {})
50
+ groonga_type = Types.map(type)
51
+ @index_columns.push(name) if options.delete(:index)
52
+ @spec[name.to_sym] = { type: groonga_type, args: [COLUMN_DEFAULTS.merge(options)] }
53
+ end
54
+
55
+ def reference(name, table_name = nil, options = {})
56
+ table_name = "#{name}s" if table_name.nil?
57
+ @spec[name.to_sym] = { type: :reference, args: [table_name, options] }
58
+ end
59
+
60
+ def timestamps(opts = {})
61
+ column(:created_at, 'Time')
62
+ column(:updated_at, 'Time')
63
+ end
64
+
65
+ def sync(db_context = nil)
66
+ switch_to_context(db_context) if db_context
67
+
28
68
  ensure_created!
29
- @columns[name] = { type: type, options: options }
30
- check_and_add_column(name, type, options)
69
+ remove_columns_not_in(@spec.keys)
70
+ @spec.each do |col, spec|
71
+ check_and_add_column(col, spec[:type], spec[:args])
72
+ end
73
+ @index_columns.each do |col|
74
+ add_index_on(col)
75
+ end
76
+ end
77
+
78
+ private
79
+ attr_reader :context, :table_name
80
+
81
+ def switch_to_context(db_context)
82
+ log("Switching context to #{db_context}")
83
+ @context = db_context
84
+ @table = @search_table = nil # clear cached vars
85
+ end
86
+
87
+ def add_index_on(col)
88
+ ensure_search_table!
89
+ return false if search_table.have_column?([table_name, col].join('_'))
90
+
91
+ log "Adding index on #{col}"
92
+ Groonga::Schema.change_table(SEARCH_TABLE_NAME, context: context) do |table|
93
+ table.index([table_name, col].join('.'))
94
+ end
95
+ end
96
+
97
+ def ensure_search_table!
98
+ return if search_table
99
+ opts = (@opts[:search_table] || {}).merge({
100
+ type: :patricia_trie,
101
+ normalizer: :NormalizerAuto,
102
+ default_tokenizer: "TokenBigram"
103
+ })
104
+ log("Creating search table with options: #{opts.inspect}")
105
+ Groonga::Schema.create_table(SEARCH_TABLE_NAME, opts.merge(context: context))
106
+ end
107
+
108
+ def remove_columns_not_in(list)
109
+ column_names.each do |col|
110
+ remove_column(col) unless list.include?(col)
111
+ end
112
+ end
113
+
114
+ def ensure_created!
115
+ create_table! if table.nil?
31
116
  end
32
117
 
33
118
  def check_and_add_column(name, type, options)
34
119
  add_column(name, type, options) unless has_column?(name, type)
35
120
  end
36
121
 
37
- def has_column?(name, type)
122
+ def has_column?(name, type = nil)
38
123
  table.columns.any? do |col|
39
- col.name == "#{table_name}.#{name}"
124
+ col.name == "#{table_name}.#{name}" # && (type.nil? or col.type == type)
125
+ end
126
+ end
127
+
128
+ def add_column(name, type, args = [])
129
+ log "Adding column #{name} with type #{type}, args: #{args.inspect}"
130
+ Groonga::Schema.change_table(table_name, context: context) do |table|
131
+ table.public_send(type, name, *args)
40
132
  end
41
133
  end
42
134
 
43
- def add_column(name, type, options = {})
44
- # puts "Adding column #{name}"
45
- Groonga::Schema.change_table(table_name) do |table|
46
- table.public_send(type, name, options)
135
+ def remove_column(name)
136
+ log "Removing column #{name}"
137
+ Groonga::Schema.change_table(table_name, context: context) do |table|
138
+ table.remove_column(name)
47
139
  end
48
140
  end
49
141
 
50
142
  def create_table!
51
- # puts "creating table!"
52
- Groonga::Schema.create_table(table_name, @opts)
143
+ log "Creating table!"
144
+ Groonga::Schema.create_table(table_name, context: context)
53
145
  end
54
146
 
55
147
  def remove_table!
56
- # puts "removing table!"
57
- Groonga::Schema.remove_table(table_name)
148
+ log "Removing table!"
149
+ Groonga::Schema.remove_table(table_name, context: context)
58
150
  @table = nil
59
151
  end
60
- end
61
- end
62
-
63
- def load_schema!
64
- Groonga::Schema.create_table("Sources")
65
- Groonga::Schema.create_table("Topics")
66
- Groonga::Schema.create_table("Tags", type: :patricia_trie)
67
- Groonga::Schema.create_table("Links", type: :hash)
68
- Groonga::Schema.create_table("Tweeters", type: :hash)
69
- Groonga::Schema.create_table("Mentions")
70
- Groonga::Schema.create_table("Subscribers")
71
-
72
- Groonga::Schema.change_table("Sources") do |table|
73
- table.short_text("name")
74
- table.short_text("twitter_user_name")
75
- end
76
-
77
- Groonga::Schema.change_table("Topics") do |table|
78
- table.reference("most_mentioned", "Links")
79
- table.uint32("mentions_count")
80
- table.uint32("links_count")
81
- end
82
-
83
- Groonga::Schema.change_table("Links") do |table|
84
- table.reference("source", "Sources")
85
- table.reference("topic", "Topics")
86
- table.reference("tags", "Tags", type: :vector)
87
- # table.short_text("url")
88
- table.short_text("title")
89
- table.short_text("image")
90
- table.short_text("description") # less than 4K bytes
91
- table.uint32("mentions_count")
92
- table.uint32("hits_count")
93
- table.boolean("posted")
94
- table.boolean("removed")
95
- table.short_text("snapshot")
96
- table.uint32("posted_tweet_id")
97
- end
98
-
99
- Groonga::Schema.change_table("Tweeters") do |table|
100
- table.short_text("name")
101
- table.short_text("twitter_name")
102
- table.uint32("mentions_count")
103
- end
104
152
 
105
- Groonga::Schema.change_table("Mentions") do |table|
106
- table.reference("source", "Sources")
107
- table.reference("link", "Links")
108
- table.reference("tweeter", "Tweeters")
109
- table.uint32("tweet_id")
110
- table.short_text("content")
111
- table.time("created_at")
112
- end
153
+ def get_names(columns)
154
+ columns.map { |col| col.name.split('.').last.to_sym }
155
+ end
113
156
 
114
- Groonga::Schema.change_table("Subscribers") do |table|
115
- table.short_text("name")
116
- table.short_text("email")
117
- table.short_text("timezone")
118
- table.short_text("token")
119
- table.uint32("received_count")
120
- table.time("last_received_at")
157
+ def log(str)
158
+ puts "[#{table_name}] #{str}" if ENV['DEBUG']
159
+ end
121
160
  end
122
- end
161
+ end
@@ -0,0 +1,52 @@
1
+ module Groovy
2
+ class Vector
3
+ include Enumerable
4
+
5
+ def initialize(obj, key)
6
+ @obj, @key = obj, key
7
+ end
8
+
9
+ def count
10
+ records.count
11
+ end
12
+
13
+ def each(&block)
14
+ items.each { |r| block.call(r) }
15
+ end
16
+
17
+ def clear
18
+ set([])
19
+ end
20
+
21
+ def set(items)
22
+ raise "Please save parent record first" unless obj.record
23
+ obj.record[key] = items
24
+ end
25
+
26
+ def push(item)
27
+ raise "Please save parent record first" unless obj.record
28
+ obj.record[key] = obj.record[key].concat([item.record])
29
+ end
30
+
31
+ def remove(item)
32
+ raise "Please save parent record first" unless obj.record
33
+ recs = obj.record[key].delete_if { |r| r == item.record }
34
+ obj.record[key] = recs
35
+ end
36
+
37
+ alias_method :<<, :push
38
+
39
+ private
40
+ attr_reader :obj, :key
41
+
42
+ def items
43
+ return [] unless obj.record
44
+ records.map { |r| Model.initialize_from_record(r) }
45
+ end
46
+
47
+ def records
48
+ obj.record[key]
49
+ end
50
+
51
+ end
52
+ end
data/spec/groovy_spec.rb CHANGED
@@ -1,3 +1,61 @@
1
1
  require './lib/groovy'
2
2
  require 'rspec/mocks'
3
3
 
4
+ describe Groovy::Model do
5
+
6
+ describe '.schema' do
7
+ end
8
+
9
+ describe '.scope' do
10
+ end
11
+
12
+ describe '.create' do
13
+ end
14
+
15
+ describe '.find' do
16
+ end
17
+
18
+ describe '.find_by' do
19
+ end
20
+
21
+ describe '.delete_all' do
22
+ end
23
+
24
+ describe '#[]' do
25
+ end
26
+
27
+ describe '#[]=' do
28
+ end
29
+
30
+ describe '#increment' do
31
+ end
32
+
33
+ describe '#dirty' do
34
+ end
35
+
36
+ describe '#update_attributes' do
37
+ end
38
+
39
+ describe '#save' do
40
+ end
41
+
42
+ describe '#delete' do
43
+ end
44
+
45
+ describe '#reload' do
46
+ end
47
+
48
+ describe 'attributes accessors' do
49
+
50
+ end
51
+
52
+ describe 'singular refs' do
53
+
54
+ end
55
+
56
+ describe 'plural refs' do
57
+
58
+ end
59
+
60
+ end
61
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: groovy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomás Pollak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-02 00:00:00.000000000 Z
11
+ date: 2019-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -47,15 +47,20 @@ extra_rdoc_files: []
47
47
  files:
48
48
  - ".gitignore"
49
49
  - Gemfile
50
+ - Rakefile
50
51
  - example/Gemfile
51
52
  - example/Gemfile.lock
53
+ - example/basic.rb
52
54
  - example/config.ru
55
+ - example/relations.rb
53
56
  - groovy.gemspec
54
57
  - lib/groovy.rb
58
+ - lib/groovy/middleware.rb
55
59
  - lib/groovy/model.rb
56
60
  - lib/groovy/query.rb
57
61
  - lib/groovy/schema.rb
58
62
  - lib/groovy/types.rb
63
+ - lib/groovy/vector.rb
59
64
  - spec/groovy_spec.rb
60
65
  homepage: https://github.com/tomas/groovy
61
66
  licenses: []