arql 0.3.29 → 0.3.30
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 +4 -4
- data/Gemfile.lock +1 -1
- data/README-zh_CN.org +1096 -0
- data/README.org +1170 -0
- data/auto-set-id-before-save-zh_CN.org +35 -0
- data/auto-set-id-before-save.org +33 -0
- data/custom-configurations-zh_CN.org +50 -0
- data/custom-configurations.org +54 -0
- data/define-associations-zh_CN.org +38 -0
- data/define-associations.org +37 -0
- data/fuzzy-field-query-zh_CN.org +165 -0
- data/fuzzy-field-query.org +165 -0
- data/helper-for-datetime-range-query-zh_CN.org +216 -0
- data/helper-for-datetime-range-query.org +218 -0
- data/initializer-structure-zh_CN.org +33 -0
- data/initializer-structure.org +31 -0
- data/lib/arql/cli.rb +1 -1
- data/lib/arql/definition.rb +11 -3
- data/lib/arql/version.rb +1 -1
- data/oss-files-zh_CN.org +161 -0
- data/oss-files.org +163 -0
- data/sql-log-zh_CN.org +55 -0
- data/sql-log.org +55 -0
- metadata +20 -3
- data/README.md +0 -456
@@ -0,0 +1,35 @@
|
|
1
|
+
* 新建对象在保存之前自动设置 ID
|
2
|
+
|
3
|
+
对于非 =auto_increment= 的主键,那么可能希望在保存对象之前,为其设置一个唯一的 ID。可以这样做:
|
4
|
+
|
5
|
+
创建一个文件 =~/.arql.d/auto_gen_id.rb= ,内容如下:
|
6
|
+
|
7
|
+
#+BEGIN_SRC ruby
|
8
|
+
class ::ArqlModel
|
9
|
+
before_create do
|
10
|
+
if id.blank?
|
11
|
+
id_type = self.class.columns_hash['id'].sql_type.scan(/\w+/).first
|
12
|
+
case id_type
|
13
|
+
when 'bigint'
|
14
|
+
self.id = ::Arql::ID.long
|
15
|
+
when 'char'
|
16
|
+
self.id = ::Arql::ID.uuid
|
17
|
+
when 'varchar'
|
18
|
+
self.id = ::Arql::ID.uuid
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
#+END_SRC
|
24
|
+
|
25
|
+
然后在 =~/.arql.d/init.rb= 中引入这个文件:
|
26
|
+
|
27
|
+
#+BEGIN_SRC ruby
|
28
|
+
load(File.absolute_path(File.dirname(__FILE__) + "/auto_set_id.rb"))
|
29
|
+
#+END_SRC
|
30
|
+
|
31
|
+
前提是,你的表的主键是 =id= 字段,且类型是 =bigint= 或 =char= 或 =varchar= 之一。
|
32
|
+
|
33
|
+
如果你的表的主键不是 =id= 字段,那么你需要修改上面的代码。
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
* Set ID automatically for new objects before saving
|
2
|
+
|
3
|
+
For primary keys that are not =auto_increment=, you may want to set a unique ID for the object before saving it. You can do this:
|
4
|
+
|
5
|
+
Create a file =~/.arql.d/auto_gen_id.rb= with the following content:
|
6
|
+
|
7
|
+
#+BEGIN_SRC ruby
|
8
|
+
class ::ArqlModel
|
9
|
+
before_create do
|
10
|
+
if id.blank?
|
11
|
+
id_type = self.class.columns_hash['id'].sql_type.scan(/\w+/).first
|
12
|
+
case id_type
|
13
|
+
when 'bigint'
|
14
|
+
self.id = ::Arql::ID.long
|
15
|
+
when 'char'
|
16
|
+
self.id = ::Arql::ID.uuid
|
17
|
+
when 'varchar'
|
18
|
+
self.id = ::Arql::ID.uuid
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
#+END_SRC
|
24
|
+
|
25
|
+
Then in =~/.arql.d/init.rb=, require this file:
|
26
|
+
|
27
|
+
#+BEGIN_SRC ruby
|
28
|
+
load(File.absolute_path(File.dirname(__FILE__) + "/auto_set_id.rb"))
|
29
|
+
#+END_SRC
|
30
|
+
|
31
|
+
The prerequisite is that the primary key of your table is the =id= field, and the type is one of =bigint=, =char= or =varchar=.
|
32
|
+
|
33
|
+
If the primary key of your table is not the =id= field, you need to modify the code above.
|
@@ -0,0 +1,50 @@
|
|
1
|
+
* 配置文件中的自定义配置项
|
2
|
+
|
3
|
+
你可以在配置文件 (如默认的 =~/.arql.yaml= / =~/.arql.d/init.yaml= ) 中定义自己的配置项,然后在代码中通过 =Arql::App.config["CONF_KEY"]-= 来获取配置项的值。
|
4
|
+
|
5
|
+
例如,假设系统对 BankAccount 表的 =account_no= 字段进行了加密,你可以在配置文件中定义加密的密钥:
|
6
|
+
|
7
|
+
#+BEGIN_SRC yaml
|
8
|
+
dev:
|
9
|
+
<<: *default
|
10
|
+
host: 127.0.0.1
|
11
|
+
port: 3306
|
12
|
+
username: test
|
13
|
+
password: test123456
|
14
|
+
database: devel
|
15
|
+
encrypt_key: "1234567890abcdef"
|
16
|
+
#+END_SRC
|
17
|
+
|
18
|
+
然后你可以在 Initialzier 代码 (=~/.arql.rb= / =~/.arql.d/init.rb=) 中读取配置项的值:
|
19
|
+
|
20
|
+
#+BEGIN_SRC ruby
|
21
|
+
class BankAccount
|
22
|
+
|
23
|
+
def self.encrypt_account_no(account_no)
|
24
|
+
cipher = OpenSSL::Cipher.new('AES-128-ECB')
|
25
|
+
cipher.encrypt
|
26
|
+
cipher.key = Arql::App.config["encrypt_key"]
|
27
|
+
encrypted = cipher.update(account_no) + cipher.final
|
28
|
+
encrypted.unpack('H*').first
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.decrypt_account_no(encrypted_account_no)
|
32
|
+
cipher = OpenSSL::Cipher.new('AES-128-ECB')
|
33
|
+
cipher.decrypt
|
34
|
+
cipher.key = Arql::App.config["encrypt_key"]
|
35
|
+
decrypted = cipher.update([encrypted_account_no].pack('H*')) + cipher.final
|
36
|
+
decrypted
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# 从数据库查询出数据之后,自动解密 account_no 字段
|
41
|
+
after_find do
|
42
|
+
self.password = decrypt_account_no(self.password)
|
43
|
+
end
|
44
|
+
|
45
|
+
# 保存数据之前,自动加密 account_no 字段
|
46
|
+
before_save do
|
47
|
+
self.password = encrypt_account_no(self.password)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
#+END_SRC
|
@@ -0,0 +1,54 @@
|
|
1
|
+
* Additional config items in Configuration File
|
2
|
+
|
3
|
+
You can define your own configuration items in the configuration file (such as the default =~/.arql.yaml= /
|
4
|
+
=~/.arql.d/init.yaml=), and then get the value of the configuration item in the code through
|
5
|
+
=Arql::App.config["CONF_KEY"]=.
|
6
|
+
|
7
|
+
For example, suppose the system encrypts the =account_no= field of the BankAccount table, you can define the
|
8
|
+
encryption key in the configuration file:
|
9
|
+
|
10
|
+
#+BEGIN_SRC yaml
|
11
|
+
dev:
|
12
|
+
<<: *default
|
13
|
+
host: 127.0.0.1
|
14
|
+
port: 3306
|
15
|
+
username: test
|
16
|
+
password: test123456
|
17
|
+
database: devel
|
18
|
+
encrypt_key: "1234567890abcdef"
|
19
|
+
#+END_SRC
|
20
|
+
|
21
|
+
Then you can read the value of the configuration item in the Initialzier code (=~/.arql.rb= /
|
22
|
+
=~/.arql.d/init.rb=):
|
23
|
+
|
24
|
+
#+BEGIN_SRC ruby
|
25
|
+
class BankAccount
|
26
|
+
|
27
|
+
def self.encrypt_account_no(account_no)
|
28
|
+
cipher = OpenSSL::Cipher.new('AES-128-ECB')
|
29
|
+
cipher.encrypt
|
30
|
+
cipher.key = Arql::App.config["encrypt_key"]
|
31
|
+
encrypted = cipher.update(account_no) + cipher.final
|
32
|
+
encrypted.unpack('H*').first
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.decrypt_account_no(encrypted_account_no)
|
36
|
+
cipher = OpenSSL::Cipher.new('AES-128-ECB')
|
37
|
+
cipher.decrypt
|
38
|
+
cipher.key = Arql::App.config["encrypt_key"]
|
39
|
+
decrypted = cipher.update([encrypted_account_no].pack('H*')) + cipher.final
|
40
|
+
decrypted
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# After querying the data from the database, automatically decrypt the account_no field
|
45
|
+
after_find do
|
46
|
+
self.password = decrypt_account_no(self.password)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Before saving the data, automatically encrypt the account_no field
|
50
|
+
before_save do
|
51
|
+
self.password = encrypt_account_no(self.password)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
#+END_SRC
|
@@ -0,0 +1,38 @@
|
|
1
|
+
* 在 Initializer 文件中定义关联关系
|
2
|
+
|
3
|
+
可以在 Initializer 文件中定义关联关系,Arql 启动后会首先根据数据库 Schema 生成模型类,然后加载 Initializer 文件。
|
4
|
+
|
5
|
+
Initializer 文件是一个 Ruby 文件,因此可以在其中定义关联关系,例如:
|
6
|
+
|
7
|
+
#+BEGIN_SRC ruby
|
8
|
+
class Student
|
9
|
+
has_many :courses, foreign_key: :student_id, class_name: 'Course'
|
10
|
+
belongs_to :school, foreign_key: :school_id, class_name: 'School'
|
11
|
+
|
12
|
+
has_and_belongs_to_many :teachers, join_table: 'students_teachers', foreign_key: :student_id, association_foreign_key: :teacher_id, class_name: 'Teacher'
|
13
|
+
end
|
14
|
+
|
15
|
+
class Course
|
16
|
+
belongs_to :student, foreign_key: :student_id, class_name: 'Student'
|
17
|
+
end
|
18
|
+
|
19
|
+
class School
|
20
|
+
has_many :students, foreign_key: :school_id, class_name: 'Student'
|
21
|
+
end
|
22
|
+
|
23
|
+
class Teacher
|
24
|
+
has_and_belongs_to_many :students, join_table: 'students_teachers', foreign_key: :teacher_id, association_foreign_key: :student_id, class_name: 'Student'
|
25
|
+
end
|
26
|
+
#+END_SRC
|
27
|
+
|
28
|
+
1. =has_one= 表明此表是一对一关系的属主
|
29
|
+
2. =belongs_to= 表明此表是一对多或一对一关系的从属方
|
30
|
+
3. =has_and_belongs_to_many= 表明此表是多对多关系的其中一方
|
31
|
+
4. =class_name= 表明此关联关系对应的对方的 Model 类名(Model 类名实际就是表名的 CamelCase 形式)
|
32
|
+
5. =foreign_key= 表明此关联关系中,从属表一侧的关联字段名
|
33
|
+
6. =primary_key= 表明此关联关系中,属主表一侧的关联字段名
|
34
|
+
7. =join_table= 在多对多关系中,表明关联两个表的中间表名
|
35
|
+
8. =association_foreign_key= 在多对多关系中,表明对方 Model 在中间表中的关联字段名
|
36
|
+
|
37
|
+
可以参考: https://guides.rubyonrails.org/association_basics.html
|
38
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
* Define associations in Initializers
|
2
|
+
|
3
|
+
You can define associations in Initializers. Arql will generate model classes based on the database schema when it starts, and then load the Initializer file.
|
4
|
+
|
5
|
+
The Initializer file is a Ruby file, so you can define associations in it, for example:
|
6
|
+
|
7
|
+
#+BEGIN_SRC ruby
|
8
|
+
class Student
|
9
|
+
has_many :courses, foreign_key: :student_id, class_name: 'Course'
|
10
|
+
belongs_to :school, foreign_key: :school_id, class_name: 'School'
|
11
|
+
|
12
|
+
has_and_belongs_to_many :teachers, join_table: 'students_teachers', foreign_key: :student_id, association_foreign_key: :teacher_id, class_name: 'Teacher'
|
13
|
+
end
|
14
|
+
|
15
|
+
class Course
|
16
|
+
belongs_to :student, foreign_key: :student_id, class_name: 'Student'
|
17
|
+
end
|
18
|
+
|
19
|
+
class School
|
20
|
+
has_many :students, foreign_key: :school_id, class_name: 'Student'
|
21
|
+
end
|
22
|
+
|
23
|
+
class Teacher
|
24
|
+
has_and_belongs_to_many :students, join_table: 'students_teachers', foreign_key: :teacher_id, association_foreign_key: :student_id, class_name: 'Student'
|
25
|
+
end
|
26
|
+
#+END_SRC
|
27
|
+
|
28
|
+
1. =has_one= indicates that this table is the owner of a one-to-one relationship
|
29
|
+
2. =belongs_to= indicates that this table is the dependent side of a one-to-many or one-to-one relationship
|
30
|
+
3. =has_and_belongs_to_many= indicates that this table is one of the many-to-many relationships
|
31
|
+
4. =class_name= indicates the Model class name of the corresponding relationship (the Model class name is actually the CamelCase form of the table name)
|
32
|
+
5. =foreign_key= indicates the name of the association field on the dependent side of the association
|
33
|
+
6. =primary_key= indicates the name of the association field on the owner side of the association
|
34
|
+
7. =join_table= in many-to-many relationships, indicates the name of the intermediate table that associates the two tables
|
35
|
+
8. =association_foreign_key= in many-to-many relationships, indicates the association field name of the other Model in the intermediate table
|
36
|
+
|
37
|
+
You can refer to: https://guides.rubyonrails.org/association_basics.html
|
@@ -0,0 +1,165 @@
|
|
1
|
+
* 字段名 Fuzzy 化查询
|
2
|
+
|
3
|
+
有时候系统的字段名非常冗长,我们可以使用 Fuzzy 化查询来简化查询,例如,有一个字段名为 =system_user_nickname= ,我们希望可
|
4
|
+
以使用 =nickname= 来查询。甚至是 =nick= 或者 =nina= 也可以查询到。
|
5
|
+
|
6
|
+
#+BEGIN_SRC ruby
|
7
|
+
user = User.find_by_nick('张三') # select * from users where system_user_nickname = '张三'
|
8
|
+
user = User.find_by_nina('张三') # select * from users where system_user_nickname = '张三'
|
9
|
+
|
10
|
+
name = user.nina # 返回 user.system_user_nickname
|
11
|
+
#+END_SRC
|
12
|
+
|
13
|
+
可以使用如下代码实现:
|
14
|
+
|
15
|
+
创建一个文件 =~/.arql.d/fuzzy_field_query.rb= ,内容如下:
|
16
|
+
|
17
|
+
#+BEGIN_SRC ruby
|
18
|
+
module ActiveRecord
|
19
|
+
module Core
|
20
|
+
def inspect
|
21
|
+
# We check defined?(@attributes) not to issue warnings if the object is
|
22
|
+
# allocated but not initialized.
|
23
|
+
inspection = if defined?(@attributes) && @attributes
|
24
|
+
self.class.attribute_names.collect do |name|
|
25
|
+
if has_attribute?(name)
|
26
|
+
attr = _read_attribute(name)
|
27
|
+
value = if attr.nil?
|
28
|
+
attr.inspect
|
29
|
+
else
|
30
|
+
attr = format_for_inspect(attr)
|
31
|
+
inspection_filter.filter_param(name, attr)
|
32
|
+
end
|
33
|
+
"#{name}: #{value}"
|
34
|
+
elsif has_attribute?(name.upcase)
|
35
|
+
attr = _read_attribute(name.upcase)
|
36
|
+
value = if attr.nil?
|
37
|
+
attr.inspect
|
38
|
+
else
|
39
|
+
attr = format_for_inspect(attr)
|
40
|
+
inspection_filter.filter_param(name.upcase, attr)
|
41
|
+
end
|
42
|
+
"#{name}: #{value}"
|
43
|
+
end
|
44
|
+
end.compact.join(", ")
|
45
|
+
else
|
46
|
+
"not initialized"
|
47
|
+
end
|
48
|
+
|
49
|
+
"#<#{self.class} #{inspection}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
def pretty_print(pp)
|
53
|
+
return super if custom_inspect_method_defined?
|
54
|
+
pp.object_address_group(self) do
|
55
|
+
if defined?(@attributes) && @attributes
|
56
|
+
attr_names = self.class.attribute_names.select { |name| has_attribute?(name) || has_attribute?(name.upcase) }
|
57
|
+
pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
|
58
|
+
pp.breakable " "
|
59
|
+
pp.group(1) do
|
60
|
+
pp.text attr_name
|
61
|
+
pp.text ":"
|
62
|
+
pp.breakable
|
63
|
+
if has_attribute?(attr_name)
|
64
|
+
value = _read_attribute(attr_name)
|
65
|
+
value = inspection_filter.filter_param(attr_name, value) unless value.nil?
|
66
|
+
elsif has_attribute?(attr_name.upcase)
|
67
|
+
value = _read_attribute(attr_name.upcase)
|
68
|
+
value = inspection_filter.filter_param(attr_name.upcase, value) unless value.nil?
|
69
|
+
end
|
70
|
+
pp.pp value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
else
|
74
|
+
pp.breakable " "
|
75
|
+
pp.text "not initialized"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module ClassMethods
|
81
|
+
def find_by(*args) # :nodoc:
|
82
|
+
return super if scope_attributes? || reflect_on_all_aggregations.any? ||
|
83
|
+
columns_hash.key?(inheritance_column) && !base_class?
|
84
|
+
|
85
|
+
hash = args.first
|
86
|
+
|
87
|
+
return super if !(Hash === hash) || hash.values.any? { |v|
|
88
|
+
StatementCache.unsupported_value?(v)
|
89
|
+
}
|
90
|
+
|
91
|
+
# We can't cache Post.find_by(author: david) ...yet
|
92
|
+
return super unless hash.keys.all? { |k|
|
93
|
+
fuzzy_regexp = Regexp.new(k.to_s.chars.join('.*'))
|
94
|
+
columns_hash.has_key?(k.to_s) || columns_hash.keys.any? { |e| e =~ fuzzy_regexp }
|
95
|
+
}
|
96
|
+
|
97
|
+
keys = hash.keys
|
98
|
+
|
99
|
+
columns_keys = columns_hash.keys
|
100
|
+
|
101
|
+
keys.map! do |k|
|
102
|
+
k = k.to_s
|
103
|
+
fuzzy_regexp = Regexp.new(k.chars.join('.*'))
|
104
|
+
columns_keys.find { |ck| ck == k }&.to_sym ||
|
105
|
+
columns_keys.find { |ck| ck.split('_').map(&:first).join().start_with?(k) }&.to_sym ||
|
106
|
+
columns_keys.find { |ck| ck =~ fuzzy_regexp }&.to_sym
|
107
|
+
end
|
108
|
+
|
109
|
+
statement = cached_find_by_statement(keys) { |params|
|
110
|
+
wheres = keys.each_with_object({}) { |param, o|
|
111
|
+
o[param] = params.bind
|
112
|
+
}
|
113
|
+
where(wheres).limit(1)
|
114
|
+
}
|
115
|
+
begin
|
116
|
+
statement.execute(hash.values, connection)&.first
|
117
|
+
rescue TypeError
|
118
|
+
raise ActiveRecord::StatementInvalid
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
module DynamicMatchers
|
125
|
+
class Method
|
126
|
+
def valid?
|
127
|
+
attribute_names.all? do |name|
|
128
|
+
name = name.downcase unless name == name.downcase
|
129
|
+
fuzzy_regexp = Regexp.new(name.to_s.chars.join('.*'))
|
130
|
+
model.columns_hash[name] ||
|
131
|
+
model.reflect_on_aggregation(name.to_sym) ||
|
132
|
+
model.column_names.any? { |e| e =~ fuzzy_regexp }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
module ActiveModel
|
140
|
+
module AttributeMethods
|
141
|
+
def method_missing(method, *args, &block)
|
142
|
+
if respond_to_without_attributes?(method, true)
|
143
|
+
super
|
144
|
+
else
|
145
|
+
match = matched_attribute_method(method.to_s)
|
146
|
+
unless match
|
147
|
+
fuzzy_regexp = Regexp.new(method.to_s.chars.join('.*'))
|
148
|
+
fuzzy_column = attribute_names.find { |ck| ck.split('_').map(&:first).join().start_with?(method.to_s) } ||
|
149
|
+
attribute_names.find { |ck| ck =~ fuzzy_regexp }
|
150
|
+
match = matched_attribute_method(fuzzy_column) if fuzzy_column
|
151
|
+
end
|
152
|
+
match ? attribute_missing(match, *args, &block) : super
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
#+END_SRC
|
159
|
+
|
160
|
+
|
161
|
+
然后在 =~/.arql.d/init.rb= 中引入这个文件:
|
162
|
+
|
163
|
+
#+BEGIN_SRC ruby
|
164
|
+
load(File.absolute_path(File.dirname(__FILE__) + "/fuzzy_field_query.rb"))
|
165
|
+
#+END_SRC
|
@@ -0,0 +1,165 @@
|
|
1
|
+
* Fuzzy Field Query
|
2
|
+
|
3
|
+
Sometimes the field name of the system is very long, we can use Fuzzy Field Query to simplify the query, for example,
|
4
|
+
there is a field name =system_user_nickname=, we hope to use =nickname= to query. Even =nick= or =nina= can also be
|
5
|
+
queried.
|
6
|
+
|
7
|
+
#+BEGIN_SRC ruby
|
8
|
+
user = User.find_by_nick('张三') # select * from users where system_user_nickname = '张三'
|
9
|
+
user = User.find_by_nina('张三') # select * from users where system_user_nickname = '张三'
|
10
|
+
|
11
|
+
name = user.nina # return user.system_user_nickname
|
12
|
+
#+END_SRC
|
13
|
+
|
14
|
+
You can implement it as follows:
|
15
|
+
|
16
|
+
Create a file =~/.arql.d/fuzzy_field_query.rb=, the content is as follows:
|
17
|
+
|
18
|
+
#+BEGIN_SRC ruby
|
19
|
+
module ActiveRecord
|
20
|
+
module Core
|
21
|
+
def inspect
|
22
|
+
# We check defined?(@attributes) not to issue warnings if the object is
|
23
|
+
# allocated but not initialized.
|
24
|
+
inspection = if defined?(@attributes) && @attributes
|
25
|
+
self.class.attribute_names.collect do |name|
|
26
|
+
if has_attribute?(name)
|
27
|
+
attr = _read_attribute(name)
|
28
|
+
value = if attr.nil?
|
29
|
+
attr.inspect
|
30
|
+
else
|
31
|
+
attr = format_for_inspect(attr)
|
32
|
+
inspection_filter.filter_param(name, attr)
|
33
|
+
end
|
34
|
+
"#{name}: #{value}"
|
35
|
+
elsif has_attribute?(name.upcase)
|
36
|
+
attr = _read_attribute(name.upcase)
|
37
|
+
value = if attr.nil?
|
38
|
+
attr.inspect
|
39
|
+
else
|
40
|
+
attr = format_for_inspect(attr)
|
41
|
+
inspection_filter.filter_param(name.upcase, attr)
|
42
|
+
end
|
43
|
+
"#{name}: #{value}"
|
44
|
+
end
|
45
|
+
end.compact.join(", ")
|
46
|
+
else
|
47
|
+
"not initialized"
|
48
|
+
end
|
49
|
+
|
50
|
+
"#<#{self.class} #{inspection}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
def pretty_print(pp)
|
54
|
+
return super if custom_inspect_method_defined?
|
55
|
+
pp.object_address_group(self) do
|
56
|
+
if defined?(@attributes) && @attributes
|
57
|
+
attr_names = self.class.attribute_names.select { |name| has_attribute?(name) || has_attribute?(name.upcase) }
|
58
|
+
pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
|
59
|
+
pp.breakable " "
|
60
|
+
pp.group(1) do
|
61
|
+
pp.text attr_name
|
62
|
+
pp.text ":"
|
63
|
+
pp.breakable
|
64
|
+
if has_attribute?(attr_name)
|
65
|
+
value = _read_attribute(attr_name)
|
66
|
+
value = inspection_filter.filter_param(attr_name, value) unless value.nil?
|
67
|
+
elsif has_attribute?(attr_name.upcase)
|
68
|
+
value = _read_attribute(attr_name.upcase)
|
69
|
+
value = inspection_filter.filter_param(attr_name.upcase, value) unless value.nil?
|
70
|
+
end
|
71
|
+
pp.pp value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
else
|
75
|
+
pp.breakable " "
|
76
|
+
pp.text "not initialized"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module ClassMethods
|
82
|
+
def find_by(*args) # :nodoc:
|
83
|
+
return super if scope_attributes? || reflect_on_all_aggregations.any? ||
|
84
|
+
columns_hash.key?(inheritance_column) && !base_class?
|
85
|
+
|
86
|
+
hash = args.first
|
87
|
+
|
88
|
+
return super if !(Hash === hash) || hash.values.any? { |v|
|
89
|
+
StatementCache.unsupported_value?(v)
|
90
|
+
}
|
91
|
+
|
92
|
+
# We can't cache Post.find_by(author: david) ...yet
|
93
|
+
return super unless hash.keys.all? { |k|
|
94
|
+
fuzzy_regexp = Regexp.new(k.to_s.chars.join('.*'))
|
95
|
+
columns_hash.has_key?(k.to_s) || columns_hash.keys.any? { |e| e =~ fuzzy_regexp }
|
96
|
+
}
|
97
|
+
|
98
|
+
keys = hash.keys
|
99
|
+
|
100
|
+
columns_keys = columns_hash.keys
|
101
|
+
|
102
|
+
keys.map! do |k|
|
103
|
+
k = k.to_s
|
104
|
+
fuzzy_regexp = Regexp.new(k.chars.join('.*'))
|
105
|
+
columns_keys.find { |ck| ck == k }&.to_sym ||
|
106
|
+
columns_keys.find { |ck| ck.split('_').map(&:first).join().start_with?(k) }&.to_sym ||
|
107
|
+
columns_keys.find { |ck| ck =~ fuzzy_regexp }&.to_sym
|
108
|
+
end
|
109
|
+
|
110
|
+
statement = cached_find_by_statement(keys) { |params|
|
111
|
+
wheres = keys.each_with_object({}) { |param, o|
|
112
|
+
o[param] = params.bind
|
113
|
+
}
|
114
|
+
where(wheres).limit(1)
|
115
|
+
}
|
116
|
+
begin
|
117
|
+
statement.execute(hash.values, connection)&.first
|
118
|
+
rescue TypeError
|
119
|
+
raise ActiveRecord::StatementInvalid
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
module DynamicMatchers
|
126
|
+
class Method
|
127
|
+
def valid?
|
128
|
+
attribute_names.all? do |name|
|
129
|
+
name = name.downcase unless name == name.downcase
|
130
|
+
fuzzy_regexp = Regexp.new(name.to_s.chars.join('.*'))
|
131
|
+
model.columns_hash[name] ||
|
132
|
+
model.reflect_on_aggregation(name.to_sym) ||
|
133
|
+
model.column_names.any? { |e| e =~ fuzzy_regexp }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
module ActiveModel
|
141
|
+
module AttributeMethods
|
142
|
+
def method_missing(method, *args, &block)
|
143
|
+
if respond_to_without_attributes?(method, true)
|
144
|
+
super
|
145
|
+
else
|
146
|
+
match = matched_attribute_method(method.to_s)
|
147
|
+
unless match
|
148
|
+
fuzzy_regexp = Regexp.new(method.to_s.chars.join('.*'))
|
149
|
+
fuzzy_column = attribute_names.find { |ck| ck.split('_').map(&:first).join().start_with?(method.to_s) } ||
|
150
|
+
attribute_names.find { |ck| ck =~ fuzzy_regexp }
|
151
|
+
match = matched_attribute_method(fuzzy_column) if fuzzy_column
|
152
|
+
end
|
153
|
+
match ? attribute_missing(match, *args, &block) : super
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#+END_SRC
|
160
|
+
|
161
|
+
Then introduce this file in =~/.arql.d/init.rb=:
|
162
|
+
|
163
|
+
#+BEGIN_SRC ruby
|
164
|
+
load(File.absolute_path(File.dirname(__FILE__) + "/fuzzy_field_query.rb"))
|
165
|
+
#+END_SRC
|