arql 0.3.29 → 0.3.31
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README-zh_CN.org +1102 -0
- data/README.org +1173 -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/commands/models.rb +39 -9
- 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/ruby-guides-for-java-developer-zh_CN.org +766 -0
- data/simple-active-record-guide-zh_CN.org +83 -0
- data/simple-pry-guides-zh_CN.org +114 -0
- data/sql-log-zh_CN.org +55 -0
- data/sql-log.org +55 -0
- metadata +23 -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
|