ransack 4.3.0 → 4.4.0

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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/lib/polyamorous/polyamorous.rb +1 -1
  4. data/lib/ransack/adapters/active_record/context.rb +30 -3
  5. data/lib/ransack/context.rb +3 -0
  6. data/lib/ransack/helpers/form_builder.rb +6 -7
  7. data/lib/ransack/helpers/form_helper.rb +86 -20
  8. data/lib/ransack/locale/ja.yml +51 -51
  9. data/lib/ransack/locale/ko.yml +6 -6
  10. data/lib/ransack/locale/uk.yml +72 -0
  11. data/lib/ransack/nodes/condition.rb +36 -6
  12. data/lib/ransack/nodes/grouping.rb +1 -1
  13. data/lib/ransack/nodes/sort.rb +1 -1
  14. data/lib/ransack/nodes/value.rb +9 -1
  15. data/lib/ransack/search.rb +1 -1
  16. data/lib/ransack/version.rb +1 -1
  17. data/lib/ransack.rb +8 -0
  18. data/spec/polyamorous/join_association_spec.rb +0 -1
  19. data/spec/polyamorous/join_dependency_spec.rb +0 -1
  20. data/spec/ransack/adapters/active_record/base_spec.rb +101 -2
  21. data/spec/ransack/adapters/active_record/context_spec.rb +72 -0
  22. data/spec/ransack/helpers/form_builder_spec.rb +0 -2
  23. data/spec/ransack/helpers/form_helper_spec.rb +219 -5
  24. data/spec/ransack/nodes/condition_spec.rb +229 -0
  25. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  26. data/spec/ransack/nodes/value_spec.rb +12 -1
  27. data/spec/ransack/predicate_spec.rb +0 -1
  28. data/spec/ransack/search_spec.rb +107 -1
  29. data/spec/ransack/translate_spec.rb +0 -1
  30. data/spec/spec_helper.rb +2 -3
  31. data/spec/support/schema.rb +30 -0
  32. metadata +41 -87
  33. data/.github/FUNDING.yml +0 -3
  34. data/.github/SECURITY.md +0 -12
  35. data/.github/workflows/codeql.yml +0 -72
  36. data/.github/workflows/cronjob.yml +0 -141
  37. data/.github/workflows/deploy.yml +0 -35
  38. data/.github/workflows/rubocop.yml +0 -20
  39. data/.github/workflows/test-deploy.yml +0 -29
  40. data/.github/workflows/test.yml +0 -183
  41. data/.gitignore +0 -7
  42. data/.nojekyll +0 -0
  43. data/.rubocop.yml +0 -50
  44. data/CHANGELOG.md +0 -1193
  45. data/CONTRIBUTING.md +0 -171
  46. data/Gemfile +0 -58
  47. data/Rakefile +0 -24
  48. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +0 -78
  49. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +0 -75
  50. data/docs/.gitignore +0 -19
  51. data/docs/.nojekyll +0 -0
  52. data/docs/babel.config.js +0 -3
  53. data/docs/blog/2022-03-27-ransack-3.0.0.md +0 -20
  54. data/docs/docs/getting-started/_category_.json +0 -4
  55. data/docs/docs/getting-started/advanced-mode.md +0 -46
  56. data/docs/docs/getting-started/configuration.md +0 -47
  57. data/docs/docs/getting-started/search-matches.md +0 -67
  58. data/docs/docs/getting-started/simple-mode.md +0 -289
  59. data/docs/docs/getting-started/sorting.md +0 -71
  60. data/docs/docs/getting-started/using-predicates.md +0 -282
  61. data/docs/docs/going-further/_category_.json +0 -4
  62. data/docs/docs/going-further/acts-as-taggable-on.md +0 -114
  63. data/docs/docs/going-further/associations.md +0 -70
  64. data/docs/docs/going-further/custom-predicates.md +0 -52
  65. data/docs/docs/going-further/documentation.md +0 -43
  66. data/docs/docs/going-further/exporting-to-csv.md +0 -49
  67. data/docs/docs/going-further/external-guides.md +0 -57
  68. data/docs/docs/going-further/form-customisation.md +0 -63
  69. data/docs/docs/going-further/i18n.md +0 -53
  70. data/docs/docs/going-further/img/create_release.png +0 -0
  71. data/docs/docs/going-further/merging-searches.md +0 -41
  72. data/docs/docs/going-further/other-notes.md +0 -425
  73. data/docs/docs/going-further/polymorphic-search.md +0 -46
  74. data/docs/docs/going-further/ransackers.md +0 -331
  75. data/docs/docs/going-further/release_process.md +0 -36
  76. data/docs/docs/going-further/saving-queries.md +0 -82
  77. data/docs/docs/going-further/searching-postgres.md +0 -57
  78. data/docs/docs/going-further/wiki-contributors.md +0 -82
  79. data/docs/docs/intro.md +0 -99
  80. data/docs/docusaurus.config.js +0 -120
  81. data/docs/package.json +0 -42
  82. data/docs/sidebars.js +0 -31
  83. data/docs/src/components/HomepageFeatures/index.js +0 -64
  84. data/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  85. data/docs/src/css/custom.css +0 -39
  86. data/docs/src/pages/index.module.css +0 -23
  87. data/docs/src/pages/markdown-page.md +0 -7
  88. data/docs/static/.nojekyll +0 -0
  89. data/docs/static/img/docusaurus.png +0 -0
  90. data/docs/static/img/favicon.ico +0 -0
  91. data/docs/static/img/logo.svg +0 -1
  92. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  93. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  94. data/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  95. data/docs/static/img/undraw_docusaurus_react.svg +0 -170
  96. data/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  97. data/docs/static/logo/ransack-h.png +0 -0
  98. data/docs/static/logo/ransack-h.svg +0 -34
  99. data/docs/static/logo/ransack-v.png +0 -0
  100. data/docs/static/logo/ransack-v.svg +0 -34
  101. data/docs/static/logo/ransack.png +0 -0
  102. data/docs/static/logo/ransack.svg +0 -21
  103. data/docs/yarn.lock +0 -8884
  104. data/ransack.gemspec +0 -26
@@ -99,6 +99,235 @@ module Ransack
99
99
  specify { expect(subject).to eq Condition.extract(Context.for(Person), 'full_name_eq', Person.first.name) }
100
100
  end
101
101
  end
102
+
103
+ context 'with wildcard string values' do
104
+ it 'properly quotes values with wildcards for LIKE predicates' do
105
+ ransack_hash = { name_cont: 'test%' }
106
+ sql = Person.ransack(ransack_hash).result.to_sql
107
+
108
+ # The % should be properly quoted in the SQL
109
+ case ActiveRecord::Base.connection.adapter_name
110
+ when "Mysql2"
111
+ expect(sql).to include("LIKE '%test\\\\%%'")
112
+ expect(sql).not_to include("NOT LIKE '%test\\\\%%'")
113
+ when "PostGIS", "PostgreSQL"
114
+ expect(sql).to include("ILIKE '%test\\%%'")
115
+ expect(sql).not_to include("NOT ILIKE '%test\\%%'")
116
+ else
117
+ expect(sql).to include("LIKE '%test%%'")
118
+ expect(sql).not_to include("NOT LIKE '%test%%'")
119
+ end
120
+ end
121
+
122
+ it 'properly quotes values with wildcards for NOT LIKE predicates' do
123
+ ransack_hash = { name_not_cont: 'test%' }
124
+ sql = Person.ransack(ransack_hash).result.to_sql
125
+
126
+ # The % should be properly quoted in the SQL
127
+ case ActiveRecord::Base.connection.adapter_name
128
+ when "Mysql2"
129
+ expect(sql).to include("NOT LIKE '%test\\\\%%'")
130
+ when "PostGIS", "PostgreSQL"
131
+ expect(sql).to include("NOT ILIKE '%test\\%%'")
132
+ else
133
+ expect(sql).to include("NOT LIKE '%test%%'")
134
+ end
135
+ end
136
+ end
137
+
138
+ context 'with negative conditions on associations' do
139
+ it 'handles not_null predicate with true value correctly' do
140
+ ransack_hash = { comments_id_not_null: true }
141
+ sql = Person.ransack(ransack_hash).result.to_sql
142
+
143
+ # Should generate an IN query with IS NOT NULL condition
144
+ expect(sql).to include('IN (')
145
+ expect(sql).to include('IS NOT NULL')
146
+ expect(sql).not_to include('IS NULL')
147
+ end
148
+
149
+ it 'handles not_null predicate with false value correctly' do
150
+ ransack_hash = { comments_id_not_null: false }
151
+ sql = Person.ransack(ransack_hash).result.to_sql
152
+
153
+ # Should generate a NOT IN query with IS NULL condition
154
+ expect(sql).to include('NOT IN (')
155
+ expect(sql).to include('IS NULL')
156
+ expect(sql).not_to include('IS NOT NULL')
157
+ end
158
+
159
+ it 'handles not_cont predicate correctly' do
160
+ ransack_hash = { comments_body_not_cont: 'test' }
161
+ sql = Person.ransack(ransack_hash).result.to_sql
162
+
163
+ # Should generate a NOT IN query with LIKE condition (not NOT LIKE)
164
+ expect(sql).to include('NOT IN (')
165
+ expect(sql).to include("LIKE '%test%'")
166
+ expect(sql).not_to include("NOT LIKE '%test%'")
167
+ end
168
+ end
169
+
170
+ context 'with nested conditions' do
171
+ it 'correctly identifies non-nested conditions' do
172
+ condition = Condition.extract(
173
+ Context.for(Person), 'name_eq', 'Test'
174
+ )
175
+
176
+ # Create a mock parent table
177
+ parent_table = Person.arel_table
178
+
179
+ # Get the attribute name and make sure it starts with the table name
180
+ attribute = condition.attributes.first
181
+ expect(attribute.name).to eq('name')
182
+ expect(parent_table.name).to eq('people')
183
+
184
+ # The method should return false because 'name' doesn't start with 'people'
185
+ result = condition.send(:not_nested_condition, attribute, parent_table)
186
+ expect(result).to be false
187
+ end
188
+
189
+ it 'correctly identifies truly non-nested conditions when attribute name starts with table name' do
190
+ # Create a condition with an attribute that starts with the table name
191
+ condition = Condition.extract(
192
+ Context.for(Person), 'name_eq', 'Test'
193
+ )
194
+
195
+ # Modify the attribute name to start with the table name for testing purposes
196
+ attribute = condition.attributes.first
197
+ allow(attribute).to receive(:name).and_return('people_name')
198
+
199
+ # Create a parent table
200
+ parent_table = Person.arel_table
201
+
202
+ # Now the method should return true because 'people_name' starts with 'people'
203
+ result = condition.send(:not_nested_condition, attribute, parent_table)
204
+ expect(result).to be true
205
+ end
206
+
207
+ it 'correctly identifies nested conditions' do
208
+ condition = Condition.extract(
209
+ Context.for(Person), 'articles_title_eq', 'Test'
210
+ )
211
+
212
+ # Create a mock table alias
213
+ parent_table = Arel::Nodes::TableAlias.new(
214
+ Article.arel_table,
215
+ Article.arel_table
216
+ )
217
+
218
+ # Access the private method using send
219
+ result = condition.send(:not_nested_condition, condition.attributes.first, parent_table)
220
+
221
+ # Should return false for nested condition
222
+ expect(result).to be false
223
+ end
224
+ end
225
+
226
+ context 'with polymorphic associations and not_in predicate' do
227
+ before do
228
+ # Define test models for polymorphic associations
229
+ class ::TestTask < ActiveRecord::Base
230
+ self.table_name = 'tasks'
231
+ has_many :follows, primary_key: :uid, inverse_of: :followed, foreign_key: :followed_uid, class_name: 'TestFollow'
232
+ has_many :users, through: :follows, source: :follower, source_type: 'TestUser'
233
+
234
+ # Add ransackable_attributes method
235
+ def self.ransackable_attributes(auth_object = nil)
236
+ ["created_at", "id", "name", "uid", "updated_at"]
237
+ end
238
+
239
+ # Add ransackable_associations method
240
+ def self.ransackable_associations(auth_object = nil)
241
+ ["follows", "users"]
242
+ end
243
+ end
244
+
245
+ class ::TestFollow < ActiveRecord::Base
246
+ self.table_name = 'follows'
247
+ belongs_to :follower, polymorphic: true, foreign_key: :follower_uid, primary_key: :uid
248
+ belongs_to :followed, polymorphic: true, foreign_key: :followed_uid, primary_key: :uid
249
+
250
+ # Add ransackable_attributes method
251
+ def self.ransackable_attributes(auth_object = nil)
252
+ ["created_at", "followed_type", "followed_uid", "follower_type", "follower_uid", "id", "updated_at"]
253
+ end
254
+
255
+ # Add ransackable_associations method
256
+ def self.ransackable_associations(auth_object = nil)
257
+ ["followed", "follower"]
258
+ end
259
+ end
260
+
261
+ class ::TestUser < ActiveRecord::Base
262
+ self.table_name = 'users'
263
+ has_many :follows, primary_key: :uid, inverse_of: :follower, foreign_key: :follower_uid, class_name: 'TestFollow'
264
+ has_many :tasks, through: :follows, source: :followed, source_type: 'TestTask'
265
+
266
+ # Add ransackable_attributes method
267
+ def self.ransackable_attributes(auth_object = nil)
268
+ ["created_at", "id", "name", "uid", "updated_at"]
269
+ end
270
+
271
+ # Add ransackable_associations method
272
+ def self.ransackable_associations(auth_object = nil)
273
+ ["follows", "tasks"]
274
+ end
275
+ end
276
+
277
+ # Create tables if they don't exist
278
+ ActiveRecord::Base.connection.create_table(:tasks, force: true) do |t|
279
+ t.string :uid
280
+ t.string :name
281
+ t.timestamps null: false
282
+ end
283
+
284
+ ActiveRecord::Base.connection.create_table(:follows, force: true) do |t|
285
+ t.string :followed_uid, null: false
286
+ t.string :followed_type, null: false
287
+ t.string :follower_uid, null: false
288
+ t.string :follower_type, null: false
289
+ t.timestamps null: false
290
+ t.index [:followed_uid, :followed_type]
291
+ t.index [:follower_uid, :follower_type]
292
+ end
293
+
294
+ ActiveRecord::Base.connection.create_table(:users, force: true) do |t|
295
+ t.string :uid
296
+ t.string :name
297
+ t.timestamps null: false
298
+ end
299
+ end
300
+
301
+ after do
302
+ # Clean up test models and tables
303
+ Object.send(:remove_const, :TestTask)
304
+ Object.send(:remove_const, :TestFollow)
305
+ Object.send(:remove_const, :TestUser)
306
+
307
+ ActiveRecord::Base.connection.drop_table(:tasks, if_exists: true)
308
+ ActiveRecord::Base.connection.drop_table(:follows, if_exists: true)
309
+ ActiveRecord::Base.connection.drop_table(:users, if_exists: true)
310
+ end
311
+
312
+ it 'correctly handles not_in predicate with polymorphic associations' do
313
+ # Create the search
314
+ search = TestTask.ransack(users_uid_not_in: ['uid_example'])
315
+ sql = search.result.to_sql
316
+
317
+ # Verify the SQL contains the expected NOT IN clause
318
+ expect(sql).to include('NOT IN')
319
+ expect(sql).to include("follower_uid")
320
+ expect(sql).to include("followed_uid")
321
+ expect(sql).to include("'uid_example'")
322
+
323
+ # The SQL should include a reference to tasks.uid
324
+ expect(sql).to include("tasks")
325
+ expect(sql).to include("uid")
326
+
327
+ # The SQL should include a reference to follows table
328
+ expect(sql).to include("follows")
329
+ end
330
+ end
102
331
  end
103
332
  end
104
333
  end
@@ -3,7 +3,6 @@ require 'spec_helper'
3
3
  module Ransack
4
4
  module Nodes
5
5
  describe Grouping do
6
-
7
6
  before do
8
7
  @g = 1
9
8
  end
@@ -66,6 +65,7 @@ module Ransack
66
65
  }
67
66
  }
68
67
  end
68
+
69
69
  before { subject.conditions = conditions }
70
70
 
71
71
  it 'expect duplicates to be removed' do
@@ -98,6 +98,7 @@ module Ransack
98
98
  }
99
99
  }
100
100
  end
101
+
101
102
  before { subject.conditions = conditions }
102
103
 
103
104
  it 'expect them to be parsed as different and not as duplicates' do
@@ -105,7 +106,6 @@ module Ransack
105
106
  end
106
107
  end
107
108
  end
108
-
109
109
  end
110
110
  end
111
111
  end
@@ -71,6 +71,18 @@ module Ransack
71
71
  end
72
72
  end
73
73
 
74
+ [[], ["12"], ["101.5"]].each do |value|
75
+ context "with an array value (#{value.inspect})" do
76
+ let(:raw_value) { value }
77
+
78
+ it "should cast to integer as nil" do
79
+ result = subject.cast(:integer)
80
+
81
+ expect(result).to be nil
82
+ end
83
+ end
84
+ end
85
+
74
86
  ["12", "101.5"].each do |value|
75
87
  context "with a float value (#{value})" do
76
88
  let(:raw_value) { value }
@@ -109,7 +121,6 @@ module Ransack
109
121
  end
110
122
  end
111
123
  end
112
-
113
124
  end
114
125
  end
115
126
  end
@@ -5,7 +5,6 @@ module Ransack
5
5
  FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
6
6
 
7
7
  describe Predicate do
8
-
9
8
  before do
10
9
  @s = Search.new(Person)
11
10
  end
@@ -178,7 +178,6 @@ module Ransack
178
178
  # AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
179
179
  # ) ORDER BY "people"."id" DESC
180
180
 
181
- pending("spec should pass, but I do not know how/where to fix lib code")
182
181
  s = Search.new(Person, published_articles_title_not_eq: 'Test')
183
182
  expect(s.result.to_sql).to include 'default_scope'
184
183
  expect(s.result.to_sql).to include 'published'
@@ -337,18 +336,90 @@ module Ransack
337
336
  end
338
337
  end
339
338
 
339
+ context "ransackable_scope with array arguments" do
340
+ around(:each) do |example|
341
+ Person.define_singleton_method(:domestic) do |countries|
342
+ self.where(name: countries)
343
+ end
344
+
345
+ Person.define_singleton_method(:flexible_scope) do |*args|
346
+ self.where(id: args)
347
+ end
348
+
349
+ Person.define_singleton_method(:two_param_scope) do |param1, param2|
350
+ self.where(name: param1, id: param2)
351
+ end
352
+
353
+ begin
354
+ example.run
355
+ ensure
356
+ Person.singleton_class.undef_method :domestic
357
+ Person.singleton_class.undef_method :flexible_scope
358
+ Person.singleton_class.undef_method :two_param_scope
359
+ end
360
+ end
361
+
362
+ it "handles scopes that take arrays as single arguments (arity 1)" do
363
+ allow(Person).to receive(:ransackable_scopes)
364
+ .and_return(Person.ransackable_scopes + [:domestic])
365
+
366
+ # This should not raise ArgumentError
367
+ expect {
368
+ s = Search.new(Person, domestic: ['US', 'JP'])
369
+ s.result # This triggers the actual scope call
370
+ }.not_to raise_error
371
+
372
+ s = Search.new(Person, domestic: ['US', 'JP'])
373
+ expect(s.instance_variable_get(:@scope_args)["domestic"]).to eq("US")
374
+ end
375
+
376
+ it "handles scopes with flexible arity (negative arity)" do
377
+ allow(Person).to receive(:ransackable_scopes)
378
+ .and_return(Person.ransackable_scopes + [:flexible_scope])
379
+
380
+ expect {
381
+ s = Search.new(Person, flexible_scope: ['US', 'JP'])
382
+ s.result
383
+ }.not_to raise_error
384
+ end
385
+
386
+ it "handles scopes with arity > 1" do
387
+ allow(Person).to receive(:ransackable_scopes)
388
+ .and_return(Person.ransackable_scopes + [:two_param_scope])
389
+
390
+ expect {
391
+ s = Search.new(Person, two_param_scope: ['param1', 'param2'])
392
+ s.result
393
+ }.not_to raise_error
394
+ end
395
+
396
+ it "still supports the workaround with nested arrays" do
397
+ allow(Person).to receive(:ransackable_scopes)
398
+ .and_return(Person.ransackable_scopes + [:domestic])
399
+
400
+ # The workaround from the issue should still work
401
+ expect {
402
+ s = Search.new(Person, domestic: [['US', 'JP']])
403
+ s.result
404
+ }.not_to raise_error
405
+ end
406
+ end
340
407
  end
341
408
 
342
409
  describe '#result' do
343
410
  let(:people_name_field) {
344
411
  "#{quote_table_name("people")}.#{quote_column_name("name")}"
345
412
  }
413
+ let(:people_temperament_field) {
414
+ "#{quote_table_name("people")}.#{quote_column_name("temperament")}"
415
+ }
346
416
  let(:children_people_name_field) {
347
417
  "#{quote_table_name("children_people")}.#{quote_column_name("name")}"
348
418
  }
349
419
  let(:notable_type_field) {
350
420
  "#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
351
421
  }
422
+
352
423
  it 'evaluates conditions contextually' do
353
424
  s = Search.new(Person, children_name_eq: 'Ernie')
354
425
  expect(s.result).to be_an ActiveRecord::Relation
@@ -356,6 +427,36 @@ module Ransack
356
427
  children_people_name_field} = 'Ernie'/
357
428
  end
358
429
 
430
+ context 'when evaluating enums' do
431
+ before do
432
+ Person.take.update_attribute(:temperament, 'choleric')
433
+ end
434
+
435
+ it 'evaluates enum key correctly' do
436
+ s = Search.new(Person, temperament_eq: 'choleric')
437
+
438
+ expect(s.result.to_sql).not_to match /#{
439
+ people_temperament_field} = 0/
440
+
441
+ expect(s.result.to_sql).to match /#{
442
+ people_temperament_field} = #{Person.temperaments[:choleric]}/
443
+
444
+ expect(s.result).not_to be_empty
445
+ end
446
+
447
+ it 'evaluates enum value correctly' do
448
+ s = Search.new(Person, temperament_eq: Person.temperaments[:choleric])
449
+
450
+ expect(s.result.to_sql).not_to match /#{
451
+ people_temperament_field} = 0/
452
+
453
+ expect(s.result.to_sql).to match /#{
454
+ people_temperament_field} = #{Person.temperaments[:choleric]}/
455
+
456
+ expect(s.result).not_to be_empty
457
+ end
458
+ end
459
+
359
460
  it 'use appropriate table alias' do
360
461
  s = Search.new(Person, {
361
462
  name_eq: "person_name_query",
@@ -479,6 +580,11 @@ module Ransack
479
580
  @s = Search.new(Person)
480
581
  end
481
582
 
583
+ it 'doesn\'t creates sorts' do
584
+ @s.sorts = ''
585
+ expect(@s.sorts.size).to eq(0)
586
+ end
587
+
482
588
  it 'creates sorts based on a single attribute/direction' do
483
589
  @s.sorts = 'id desc'
484
590
  expect(@s.sorts.size).to eq(1)
@@ -2,7 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  module Ransack
4
4
  describe Translate do
5
-
6
5
  describe '.attribute' do
7
6
  it 'translate namespaced attribute like AR does' do
8
7
  ar_translation = ::Namespace::Article.human_attribute_name(:title)
data/spec/spec_helper.rb CHANGED
@@ -1,13 +1,12 @@
1
- require 'machinist/active_record'
2
- require 'polyamorous/polyamorous'
1
+ require 'ransack'
3
2
  require 'sham'
4
3
  require 'faker'
5
- require 'ransack'
6
4
  require 'action_controller'
7
5
  require 'ransack/helpers'
8
6
  require 'pry'
9
7
  require 'simplecov'
10
8
  require 'byebug'
9
+ require 'machinist/active_record'
11
10
 
12
11
  SimpleCov.start
13
12
  I18n.enforce_available_locales = false
@@ -81,6 +81,8 @@ class Person < ApplicationRecord
81
81
  scope :sort_by_reverse_name_asc, lambda { order(Arel.sql("REVERSE(name) ASC")) }
82
82
  scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
83
83
 
84
+ enum :temperament, { sanguine: 1, choleric: 2, melancholic: 3, phlegmatic: 4 }
85
+
84
86
  alias_attribute :full_name, :name
85
87
 
86
88
  ransack_alias :term, :name_or_email
@@ -138,6 +140,17 @@ class Person < ApplicationRecord
138
140
  Arel.sql(query)
139
141
  end
140
142
 
143
+ ransacker :article_tags, formatter: proc { |id|
144
+ if Tag.exists?(id)
145
+ joins(articles: :tags)
146
+ .where(tags: { id: id })
147
+ .distinct
148
+ .select(:id).arel
149
+ end
150
+ } do |parent|
151
+ parent.table[:id]
152
+ end
153
+
141
154
  def self.ransackable_attributes(auth_object = nil)
142
155
  if auth_object == :admin
143
156
  authorizable_ransackable_attributes - ['only_sort']
@@ -163,6 +176,7 @@ class Article < ApplicationRecord
163
176
  has_many :comments
164
177
  has_and_belongs_to_many :tags
165
178
  has_many :notes, as: :notable
179
+ has_many :recent_notes, as: :notable
166
180
 
167
181
  alias_attribute :content, :body
168
182
 
@@ -232,18 +246,28 @@ end
232
246
  class Comment < ApplicationRecord
233
247
  belongs_to :article
234
248
  belongs_to :person
249
+ has_and_belongs_to_many :tags
235
250
 
236
251
  default_scope { where(disabled: false) }
237
252
  end
238
253
 
239
254
  class Tag < ApplicationRecord
240
255
  has_and_belongs_to_many :articles
256
+ has_and_belongs_to_many :comments
241
257
  end
242
258
 
243
259
  class Note < ApplicationRecord
244
260
  belongs_to :notable, polymorphic: true
245
261
  end
246
262
 
263
+ class RecentNote < ApplicationRecord
264
+ DEFAULT_NOTABLE_ID = 1
265
+ self.table_name = "notes"
266
+ default_scope { where(notable_id: DEFAULT_NOTABLE_ID) }
267
+
268
+ belongs_to :notable, polymorphic: true
269
+ end
270
+
247
271
  class Account < ApplicationRecord
248
272
  belongs_to :agent_account, class_name: "Account"
249
273
  belongs_to :trade_account, class_name: "Account"
@@ -278,6 +302,7 @@ module Schema
278
302
  t.string :new_start
279
303
  t.string :stop_end
280
304
  t.integer :salary
305
+ t.integer :temperament
281
306
  t.date :life_start
282
307
  t.boolean :awesome, default: false
283
308
  t.boolean :terms_and_conditions, default: false
@@ -310,6 +335,11 @@ module Schema
310
335
  t.integer :tag_id
311
336
  end
312
337
 
338
+ create_table :comments_tags, force: true, id: false do |t|
339
+ t.integer :comment_id
340
+ t.integer :tag_id
341
+ end
342
+
313
343
  create_table :notes, force: true do |t|
314
344
  t.integer :notable_id
315
345
  t.string :notable_type