groovy 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []