findable 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/findable/associations/utils.rb +1 -1
- data/lib/findable/base.rb +25 -13
- data/lib/findable/collection.rb +5 -0
- data/lib/findable/query.rb +66 -5
- data/lib/findable/schema.rb +14 -1
- data/lib/findable/schema/conversion.rb +5 -5
- data/lib/findable/seed.rb +60 -26
- data/lib/findable/version.rb +1 -1
- data/lib/generators/findable/templates/seeds.rb +1 -1
- data/spec/findable/base_spec.rb +4 -4
- data/spec/findable/query_spec.rb +1 -1
- data/spec/findable/schema/conversion_spec.rb +11 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79d36740a44c4a4a702069dab24e53b533e8db00
|
4
|
+
data.tar.gz: 2908163cf00a3546837f8a0fa5b3e20bad62b044
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2761e6fbd194a70df6ecab268d507a4b689520b537c3210e83f9d1a59d1528a6df5085fcd23d9c5b129f60a3d8037135fa310e2a7c861f9a70be1a0a3fccd709
|
7
|
+
data.tar.gz: fdef311c4c618ee709ca488157753480ba2b2fc2bf61d6e1060d904d8aa9c9a1253c18d14d3e20fa3705a14581af019bd19e14950c2d024e73ec554b97d0c90b
|
data/lib/findable/base.rb
CHANGED
@@ -5,7 +5,6 @@ require "findable/inspection"
|
|
5
5
|
module Findable
|
6
6
|
class Base
|
7
7
|
include ActiveModel::Model
|
8
|
-
include ActiveModel::AttributeMethods
|
9
8
|
include Associations
|
10
9
|
include Schema
|
11
10
|
include Inspection
|
@@ -39,8 +38,14 @@ module Findable
|
|
39
38
|
def find_by(conditions)
|
40
39
|
if conditions.is_a?(Hash)
|
41
40
|
conditions.symbolize_keys!
|
42
|
-
if
|
43
|
-
|
41
|
+
if index = conditions.keys.detect {|key| key.in?(indexes) }
|
42
|
+
value = conditions.delete(index)
|
43
|
+
if index == :id
|
44
|
+
records = find_by_ids(value)
|
45
|
+
else
|
46
|
+
records = find_by_index(index, value)
|
47
|
+
end
|
48
|
+
|
44
49
|
case
|
45
50
|
when records.empty? then nil
|
46
51
|
when conditions.empty? then records.first
|
@@ -63,8 +68,14 @@ module Findable
|
|
63
68
|
|
64
69
|
def where(conditions)
|
65
70
|
conditions.symbolize_keys!
|
66
|
-
if
|
67
|
-
|
71
|
+
if index = conditions.keys.detect {|key| key.in?(indexes) }
|
72
|
+
value = conditions.delete(index)
|
73
|
+
if index == :id
|
74
|
+
records = find_by_ids(value)
|
75
|
+
else
|
76
|
+
records = find_by_index(index, value)
|
77
|
+
end
|
78
|
+
|
68
79
|
if conditions.empty?
|
69
80
|
collection!(records)
|
70
81
|
else
|
@@ -84,10 +95,17 @@ module Findable
|
|
84
95
|
end
|
85
96
|
alias_method :create!, :create
|
86
97
|
|
98
|
+
## Extension
|
99
|
+
|
100
|
+
def ordered_find(*_ids)
|
101
|
+
_ids.flatten!
|
102
|
+
find(_ids).ordered_find(_ids)
|
103
|
+
end
|
104
|
+
|
87
105
|
## Query APIs
|
88
106
|
|
89
|
-
delegate :find_by_ids, :insert, to: :query
|
90
|
-
delegate :count, :ids, :delete_all, to: :query
|
107
|
+
delegate :find_by_ids, :find_by_index, :insert, to: :query
|
108
|
+
delegate :count, :ids, :delete, :delete_all, to: :query
|
91
109
|
alias_method :destroy_all, :delete_all
|
92
110
|
|
93
111
|
def exists?(obj)
|
@@ -98,12 +116,6 @@ module Findable
|
|
98
116
|
end
|
99
117
|
end
|
100
118
|
|
101
|
-
def delete(obj)
|
102
|
-
if _id = id_from(obj)
|
103
|
-
query.delete(_id)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
119
|
def query
|
108
120
|
@_query ||= Query.new(self)
|
109
121
|
end
|
data/lib/findable/collection.rb
CHANGED
data/lib/findable/query.rb
CHANGED
@@ -31,6 +31,12 @@ module Findable
|
|
31
31
|
@serializer.deserialize(redis.hmget(data_key, *Array(ids)), model)
|
32
32
|
end
|
33
33
|
|
34
|
+
def find_by_index(index, value)
|
35
|
+
if ids = ids_from_index([index, value].join(":"))
|
36
|
+
find_by_ids(ids)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
34
40
|
def exists?(id)
|
35
41
|
redis.hexists(data_key, id)
|
36
42
|
end
|
@@ -43,28 +49,51 @@ module Findable
|
|
43
49
|
object.id,
|
44
50
|
@serializer.serialize(object.attributes)
|
45
51
|
)
|
52
|
+
update_index(object)
|
46
53
|
end
|
47
54
|
object
|
48
55
|
end
|
49
56
|
|
50
57
|
def import(hashes)
|
51
58
|
lock do
|
52
|
-
|
59
|
+
indexes = Hash.new {|h, k| h[k] = [] }
|
60
|
+
values = hashes.each_with_object([]) do |hash, obj|
|
61
|
+
hash = hash.with_indifferent_access
|
53
62
|
hash["id"] = auto_incremented_id(hash["id"])
|
54
63
|
obj << hash["id"]
|
55
64
|
obj << @serializer.serialize(hash)
|
65
|
+
|
66
|
+
if model.index_defined?
|
67
|
+
model.indexes.each_with_object([]) do |name, obj|
|
68
|
+
next if name == :id
|
69
|
+
indexes[[name, hash[name]].join(":")] << hash["id"]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
redis.hmset(data_key, *values)
|
74
|
+
if indexes.present?
|
75
|
+
attrs = indexes.map {|k, v| [k, @serializer.serialize(v)] }.flatten
|
76
|
+
redis.hmset(index_key, *attrs)
|
56
77
|
end
|
57
|
-
redis.hmset(data_key, *auto_incremented)
|
58
78
|
end
|
59
79
|
end
|
60
80
|
|
61
|
-
def delete(
|
62
|
-
|
81
|
+
def delete(object)
|
82
|
+
if model.index_defined?
|
83
|
+
model.indexes.each do |name|
|
84
|
+
next if name == :id
|
85
|
+
if value = object.public_send("#{name}_was") || object.public_send(name)
|
86
|
+
redis.hdel(index_key, value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
redis.hdel(data_key, object.id)
|
63
92
|
end
|
64
93
|
|
65
94
|
def delete_all
|
66
95
|
redis.multi do
|
67
|
-
[data_key, info_key].each {|key| redis.del(key) }
|
96
|
+
[data_key, info_key, index_key].each {|key| redis.del(key) }
|
68
97
|
end
|
69
98
|
end
|
70
99
|
|
@@ -73,6 +102,32 @@ module Findable
|
|
73
102
|
Lock.new(lock_key, thread_key).lock { yield }
|
74
103
|
end
|
75
104
|
|
105
|
+
def update_index(object)
|
106
|
+
if model.index_defined?
|
107
|
+
indexes = model.indexes.each_with_object([]) {|name, obj|
|
108
|
+
next if name == :id || object.public_send("#{name}_changed?")
|
109
|
+
|
110
|
+
if old_value = object.public_send("#{name}_was")
|
111
|
+
old_index_key = [name, old_value].join(":")
|
112
|
+
|
113
|
+
if (old_ids = ids_from_index(old_index_key)).present?
|
114
|
+
new_ids = old_ids.reject {|id| id == object.id }
|
115
|
+
if new_ids.empty?
|
116
|
+
redis.hdel(index_key, old_index_key)
|
117
|
+
else
|
118
|
+
obj << old_index_key
|
119
|
+
obj << @serializer.serialize(new_ids)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
obj << [name, object.public_send(name)].join(":")
|
125
|
+
obj << object.id
|
126
|
+
}
|
127
|
+
redis.hmset(index_key, *indexes)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
76
131
|
private
|
77
132
|
def auto_incremented_id(id)
|
78
133
|
if id.present?
|
@@ -86,5 +141,11 @@ module Findable
|
|
86
141
|
redis.hincrby(info_key, AUTO_INCREMENT_KEY, 1)
|
87
142
|
end
|
88
143
|
end
|
144
|
+
|
145
|
+
def ids_from_index(index_name)
|
146
|
+
if ids = redis.hget(index_key, index_name)
|
147
|
+
@serializer.deserialize(ids)
|
148
|
+
end
|
149
|
+
end
|
89
150
|
end
|
90
151
|
end
|
data/lib/findable/schema.rb
CHANGED
@@ -5,6 +5,9 @@ module Findable
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
+
include ActiveModel::AttributeMethods
|
9
|
+
include ActiveModel::Dirty
|
10
|
+
|
8
11
|
attribute_method_suffix "="
|
9
12
|
attribute_method_suffix "?"
|
10
13
|
end
|
@@ -14,19 +17,29 @@ module Findable
|
|
14
17
|
@_column_names ||= [:id]
|
15
18
|
end
|
16
19
|
|
20
|
+
def indexes
|
21
|
+
@_indexes ||= [:id]
|
22
|
+
end
|
23
|
+
|
24
|
+
def index_defined?
|
25
|
+
indexes.size > 1
|
26
|
+
end
|
27
|
+
|
17
28
|
def define_field(*args)
|
18
29
|
options = args.extract_options!
|
19
30
|
name = args.first
|
20
31
|
if !public_method_defined?(name) || options.present?
|
21
32
|
define_attribute_methods name
|
22
|
-
conversion = Findable::Schema::Conversion.
|
33
|
+
conversion = Findable::Schema::Conversion.to(options[:type])
|
23
34
|
define_method(name) { conversion.call(attributes[name.to_sym]) }
|
35
|
+
indexes << name.to_sym if options[:index]
|
24
36
|
column_names << name.to_sym
|
25
37
|
end
|
26
38
|
end
|
27
39
|
end
|
28
40
|
|
29
41
|
def attribute=(attr, value)
|
42
|
+
public_send("#{attr}_will_change!")
|
30
43
|
attributes[attr] = value
|
31
44
|
end
|
32
45
|
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module Findable
|
2
2
|
module Schema
|
3
|
-
|
3
|
+
module Conversion
|
4
4
|
class << self
|
5
5
|
FALSE_VALUE = ["false", "0"]
|
6
6
|
|
7
|
-
def
|
7
|
+
def to(type)
|
8
8
|
return types[:default] if type.nil?
|
9
|
-
types[type] || add_type!(type)
|
9
|
+
types[type.to_sym] || add_type!(type)
|
10
10
|
end
|
11
11
|
|
12
12
|
def types
|
@@ -16,8 +16,8 @@ module Findable
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def add_type!(type)
|
19
|
-
return type if type.
|
20
|
-
types[type.to_sym] = method(type)
|
19
|
+
return type if type.is_a?(Proc)
|
20
|
+
types[type.to_sym] = method(type).to_proc
|
21
21
|
end
|
22
22
|
|
23
23
|
def clear_types
|
data/lib/findable/seed.rb
CHANGED
@@ -1,14 +1,50 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "csv"
|
3
|
+
|
1
4
|
module Findable
|
2
|
-
class
|
5
|
+
class UnknownSeedDir < FindableError
|
6
|
+
def initialize(path)
|
7
|
+
if path
|
8
|
+
super("Couldn't find #{path}")
|
9
|
+
else
|
10
|
+
super("There is no configuration")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Seed < Struct.new(:seed_files, :model_name)
|
3
16
|
class << self
|
4
17
|
def target_files(seed_dir: nil, seed_files: nil)
|
5
|
-
target_dir = pathname(seed_dir
|
6
|
-
raise
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
18
|
+
target_dir = pathname(seed_dir || Findable.config.seed_dir)
|
19
|
+
raise UnknownSeedDir.new(target_dir) unless target_dir.try(:exist?)
|
20
|
+
|
21
|
+
seed_files = seed_files.map!(&:to_s) if seed_files
|
22
|
+
_model_name = method(:model_name).to_proc.curry.call(target_dir)
|
23
|
+
_selected = Proc.new do |seed|
|
24
|
+
seed_files.present? ? seed.table_name.in?(seed_files) : true
|
25
|
+
end
|
26
|
+
|
27
|
+
Pathname.glob(target_dir.join("**", "*"))
|
28
|
+
.select(&:file?)
|
29
|
+
.group_by(&_model_name)
|
30
|
+
.map {|name, files| new(files, name) }
|
31
|
+
.select(&_selected)
|
32
|
+
end
|
33
|
+
|
34
|
+
def model_name(seed_dir, seed_file)
|
35
|
+
if seed_dir != seed_file.dirname && seed_file.basename.to_s.match(/^data/)
|
36
|
+
from_seed_dir(seed_dir, seed_file.dirname).to_s.classify
|
37
|
+
else
|
38
|
+
from_seed_dir(seed_dir, without_ext(seed_file)).to_s.classify
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def from_seed_dir(seed_dir, seed_file)
|
43
|
+
pathname(seed_file).relative_path_from(seed_dir)
|
44
|
+
end
|
45
|
+
|
46
|
+
def without_ext(seed_file)
|
47
|
+
pathname(seed_file).dirname.join(pathname(seed_file).basename(".*"))
|
12
48
|
end
|
13
49
|
|
14
50
|
def pathname(path)
|
@@ -20,37 +56,35 @@ module Findable
|
|
20
56
|
end
|
21
57
|
end
|
22
58
|
|
23
|
-
def
|
24
|
-
@
|
25
|
-
@_seed_dir = seed_dir
|
26
|
-
end
|
27
|
-
|
28
|
-
def basename
|
29
|
-
@_basename ||= @_full_path.basename(".*").to_s
|
59
|
+
def model
|
60
|
+
@_model_class ||= model_name.constantize
|
30
61
|
end
|
31
62
|
|
32
|
-
def
|
33
|
-
|
63
|
+
def load_files
|
64
|
+
seed_files.sort_by(&:to_s).inject([]) do |data, file|
|
65
|
+
data | case file.extname
|
66
|
+
when ".yml" then load_yaml(file)
|
67
|
+
when ".csv" then load_csv(file)
|
68
|
+
else
|
69
|
+
raise UnknownFormat
|
70
|
+
end
|
71
|
+
end
|
34
72
|
end
|
35
73
|
|
36
74
|
def bootstrap!
|
37
75
|
model.query.lock do
|
38
76
|
model.delete_all
|
39
|
-
model.query.import
|
77
|
+
model.query.import load_files
|
40
78
|
end
|
41
79
|
end
|
42
80
|
|
43
81
|
private
|
44
|
-
def
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
def without_ext(path)
|
49
|
-
pathname(path).dirname.join(pathname(path).basename(".*"))
|
82
|
+
def load_yaml(seed_file)
|
83
|
+
YAML.load_file(seed_file).values
|
50
84
|
end
|
51
85
|
|
52
|
-
def
|
53
|
-
|
86
|
+
def load_csv(seed_file)
|
87
|
+
CSV.table(seed_file).map(&:to_h)
|
54
88
|
end
|
55
89
|
end
|
56
90
|
end
|
data/lib/findable/version.rb
CHANGED
data/spec/findable/base_spec.rb
CHANGED
@@ -125,12 +125,12 @@ describe Findable::Base do
|
|
125
125
|
|
126
126
|
# Private instance methods
|
127
127
|
describe "#attribute=" do
|
128
|
-
before { instance.send(:attribute=, :
|
129
|
-
it { expect(instance.attributes[:
|
128
|
+
before { instance.send(:attribute=, :name, "value") }
|
129
|
+
it { expect(instance.attributes[:name]).to eq("value") }
|
130
130
|
end
|
131
131
|
|
132
132
|
describe "#attribute?" do
|
133
|
-
before { instance.send(:attribute=, :
|
134
|
-
it { expect(instance.send(:attribute?, :
|
133
|
+
before { instance.send(:attribute=, :name, "value") }
|
134
|
+
it { expect(instance.send(:attribute?, :name)).to be_truthy }
|
135
135
|
end
|
136
136
|
end
|
data/spec/findable/query_spec.rb
CHANGED
@@ -4,17 +4,17 @@ describe Findable::Schema::Conversion do
|
|
4
4
|
after(:each) { conversion.clear_types }
|
5
5
|
let(:conversion) { Findable::Schema::Conversion }
|
6
6
|
|
7
|
-
describe ".
|
8
|
-
it { expect(conversion.
|
9
|
-
it { expect(conversion.
|
10
|
-
it { expect(conversion.
|
11
|
-
it { expect(conversion.
|
12
|
-
it { expect(conversion.
|
13
|
-
it { expect(conversion.
|
14
|
-
it { expect(conversion.
|
15
|
-
it { expect(conversion.
|
16
|
-
it { expect(conversion.
|
17
|
-
it { expect(conversion.
|
7
|
+
describe ".to" do
|
8
|
+
it { expect(conversion.to(nil)).to eq(conversion.types[:default]) }
|
9
|
+
it { expect(conversion.to(:integer)).to eq(conversion.types[:integer]) }
|
10
|
+
it { expect(conversion.to(:float)).to eq(conversion.types[:float]) }
|
11
|
+
it { expect(conversion.to(:decimal)).to eq(conversion.types[:decimal]) }
|
12
|
+
it { expect(conversion.to(:string)).to eq(conversion.types[:string]) }
|
13
|
+
it { expect(conversion.to(:boolean)).to eq(conversion.types[:boolean]) }
|
14
|
+
it { expect(conversion.to(:date)).to eq(conversion.types[:date]) }
|
15
|
+
it { expect(conversion.to(:datetime)).to eq(conversion.types[:datetime]) }
|
16
|
+
it { expect(conversion.to(:symbol)).to eq(conversion.types[:symbol]) }
|
17
|
+
it { expect(conversion.to(:inquiry)).to eq(conversion.types[:inquiry]) }
|
18
18
|
end
|
19
19
|
|
20
20
|
describe ".types" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: findable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- i2bskn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|