arql 0.3.29 → 0.3.30

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