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.
@@ -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