pry-byetypo 1.0.2 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7faf45157b7a6b110d22c13122b713a51f25b9e9c31d928f4d5511a2e9ffbcb3
4
- data.tar.gz: 4f462974bcc85139f06652b8d7ca28374fea6fbff9096388054197917f4bb104
3
+ metadata.gz: dd76efc826578597fe9cecb0ec6357a275d8cb8c37aaa1e238681f0235287027
4
+ data.tar.gz: 11eeb72895ab4150815e61f932a7aa8d13dc2ae6d6b3564c81d3ff9624c8ec89
5
5
  SHA512:
6
- metadata.gz: 2156f5efe4854c4ac4298a314eb48006d629f289a59e6d58d4078c0373f8e4db776d93b3247fd61fba01536e9109b26ad3881ec46f1bca6f09c4c8c5194ea1c7
7
- data.tar.gz: 44b4e2b0c73133bead16a8ed5f7660de909862fe3b2b7b156bcc0f4eb9ca4d7054e8eac6360766851dbe0cfa4ee74d25c5b065169509979ff36a3ebe14cefa21
6
+ metadata.gz: 9f55811aaaeeeb06521f50108d8fab819b00c461b2fd635519ad3559baf409ee256e241d5315a39ec2e238aaae6e330f2d84bcd0cfbc1b3b9f04e9b26d8b9d39
7
+ data.tar.gz: 954ecd0a20d6fb34df1592cf36f8c2dd5d893d21f0060a7ccba7a4769ead0800917d6ee08aa8c294b8937e8eaa450c77fe7ac3704f4da3e2ea4466257e306552
data/Gemfile.lock CHANGED
@@ -1,10 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pry-byetypo (1.0.2)
4
+ pry-byetypo (1.2.0)
5
5
  colorize (~> 1.1.0)
6
6
  pry (>= 0.13, < 0.15)
7
- rails (~> 7.0)
8
7
 
9
8
  GEM
10
9
  remote: https://rubygems.org/
@@ -146,7 +145,7 @@ GEM
146
145
  psych (5.1.2)
147
146
  stringio
148
147
  racc (1.7.3)
149
- rack (3.0.8)
148
+ rack (3.0.9)
150
149
  rack-session (2.0.0)
151
150
  rack (>= 3.0.0)
152
151
  rack-test (2.1.0)
@@ -253,6 +252,7 @@ PLATFORMS
253
252
  DEPENDENCIES
254
253
  byebug
255
254
  pry-byetypo!
255
+ rails (~> 7.0)
256
256
  rake (~> 13.0)
257
257
  rspec (~> 3.0)
258
258
  standard (~> 1.3)
data/README.md CHANGED
@@ -1,29 +1,32 @@
1
- # PRY-BYETYPO
1
+ # PRY-BYETYPO 👋
2
2
 
3
3
  Autocorrects typos in your Pry console.
4
4
 
5
- This small Pry plugin captures exceptions that could be due to typos and deduces the correct command based on your database information.
5
+ This small Pry plugin captures exceptions that may arise from typos and deduces the correct command based on your database information and session history.
6
6
 
7
7
  #### Before
8
8
 
9
9
  ```ruby
10
- [1] pry(main)> Usert.last
11
- NameError: uninitialized constant Usert
12
- from (pry):3:in `__pry__'
10
+ [1] pry(main)> result = 1
11
+ => 1
12
+ [2] pry(main)> resilt
13
+ NameError: undefined local variable or method `resilt' for main:Object
14
+ from (pry):2:in `__pry__'
13
15
  ```
14
16
 
15
17
  #### After
16
18
 
17
19
  ```ruby
18
- [1] pry(main)> Usert.last
19
- I, [2024-01-13T20:00:16.280710 #694] ERROR -- : NameError: uninitialized constant Usert
20
- I, [2024-01-13T20:00:16.281237 #694] INFO -- : Running: User.last
21
- 2024-01-13 20:00:16.345175 D [694:9200 log_subscriber.rb:130] ActiveRecord::Base -- User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
22
- => #<User id: 1, email: "yo@email.com">
20
+ [1] pry(main)> result = 1
21
+ => 1
22
+ [2] pry(main)> resilt
23
+ E, [2024-01-31T17:11:16.344161 #3739] ERROR -- : undefined local variable or method `resilt' for main:Object
24
+ I, [2024-01-31T17:11:16.344503 #3739] INFO -- : Running: result
25
+ => 1
23
26
  ```
24
27
 
25
28
  > [!NOTE]
26
- > So far, this plugin is not framework agnostic, and it requires Rails 7 or later.
29
+ > Currently, this plugin is not truly ORM-agnostic; to fully benefit from it, ActiveRecord is required.
27
30
 
28
31
  ## Installation
29
32
 
@@ -46,24 +49,59 @@ gem install pry-byetypo
46
49
  2. Start your daily work.
47
50
  3. Let `pry-byetypo` remove frictions. 🚀
48
51
 
49
- ## Under the hood
50
-
51
- ### 1. Byetypo dictionary
52
+ ## Byetypo dictionary
52
53
 
53
- When you open a new Pry console, the gem will generate a `byetypo_dictionary.pstore` file containing three pieces of information:
54
+ When you open a new Pry console, the gem will generate a `byetypo_dictionary.pstore` file containing four pieces of information:
54
55
 
55
56
  - A list of the ActiveRecord models in your application (e.g. `User`, `Account`).
56
57
  - A list of the ActiveRecord associations in your application (e.g. `user`, `users`, `account`, `accounts`).
58
+ - Unique identifiers for each active Pry instance, populated with the variable history of the current session. (e.g. `result = 1` will store `result`)
57
59
  - A timestamp representing the last time the `byetypo_dictionary` was updated. (by default updated every week).
58
60
 
59
61
  This file is generated at the root of your application by default. If you want to update its location, you can configure the path by adding a `BYETYPO_STORE_PATH` entry in your `.env` file.
60
62
 
61
- ### 2. Captured exceptions
63
+ ## Captured exceptions
64
+
65
+ ### NameError - undefined local variable or method
66
+
67
+ This error occurs when you mispelled a variable in your REPL. The gem will catch that exception and will try find the closest matches. If so, it will run the command with the (potential) corrected variable.
68
+
69
+ ##### Before
70
+
71
+ ```ruby
72
+ [1] pry(main)> result = 1
73
+ => 1
74
+ [2] pry(main)> resilt
75
+ NameError: undefined local variable or method `resilt' for main:Object
76
+ from (pry):2:in `__pry__'
77
+ ```
78
+
79
+ ##### After
80
+
81
+ ```ruby
82
+ [1] pry(main)> result = 1
83
+ => 1
84
+ [2] pry(main)> resilt
85
+ E, [2024-01-31T17:11:16.344161 #3739] ERROR -- : undefined local variable or method `resilt' for main:Object
86
+ I, [2024-01-31T17:11:16.344503 #3739] INFO -- : Running: result
87
+ => 1
88
+ ```
62
89
 
63
- #### NameError
90
+ ### NameError - uninitialized constant
64
91
 
65
92
  This error occurs when you mispelled a model in your REPL. The gem will catch that exception and will try find the closest matches. If so, it will run the command with the (potential) corrected model.
66
93
 
94
+ ##### Before
95
+
96
+ ```ruby
97
+ [2] pry(main)> Usert.last
98
+ NameError: uninitialized constant Usert
99
+ from (pry):2:in `__pry__'
100
+ [3] pry(main)>
101
+ ```
102
+
103
+ ##### After
104
+
67
105
  ```ruby
68
106
  [1] pry(main)> Usert.last
69
107
  I, [2024-01-13T20:00:16.280710 #694] ERROR -- : uninitialized constant Usert
@@ -71,19 +109,19 @@ I, [2024-01-13T20:00:16.281237 #694] INFO -- : Running: User.last
71
109
  => #<User id: 1, email: "yo@email.com">
72
110
  ```
73
111
 
74
- #### ActiveRecord::ConfigurationError
112
+ ### ActiveRecord::ConfigurationError
75
113
 
76
114
  Raised when association is being configured improperly or user tries to use offset and limit together with `ActiveRecord::Base.has_many` or `ActiveRecord::Base.has_and_belongs_to_many` associations.
115
+ This plugin will look into the `byetypo_dictionary` file to find the closest match and run the correct query.
77
116
 
78
- eg:
117
+ ##### Before
79
118
 
80
119
  ```ruby
81
120
  [6] pry(main)> User.joins(:group).where(groups: { name: "Landlord" }).last
82
121
  ActiveRecord::ConfigurationError: Can't join 'User' to association named 'group'; perhaps you misspelled it?
83
122
  ```
84
123
 
85
- This plugin will look into the `byetypo_dictionary` file to find the closest match and run the correct query.
86
-
124
+ ##### After
87
125
 
88
126
  ```ruby
89
127
  [1] pry(main)> User.joins(:group).where(groups: { name: "Landlord" })
@@ -93,9 +131,12 @@ I, [2024-01-13T22:45:16.297972 #1079] INFO -- : Running: User.joins(:groups).wh
93
131
  => []
94
132
  ```
95
133
 
96
- #### ActiveRecord::StatementInvalid
134
+ ### ActiveRecord::StatementInvalid
97
135
 
98
136
  The query attempts to reference columns or conditions related to a table, but the table is not properly included in the FROM clause.
137
+ This plugin will look into the `byetypo_dictionary` file to find the closest match and run the correct query.
138
+
139
+ ##### Before
99
140
 
100
141
  ```ruby
101
142
  [1] pry(main)> User.joins(:groups).where(grous: { name: "Landlord" }).last
@@ -103,7 +144,7 @@ ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause
103
144
  LINE 1: ..."group_id" WHERE "users"."deleted_at" IS NULL AND "grous"."n...
104
145
  ```
105
146
 
106
- This plugin will look into the `byetypo_dictionary` file to find the closest match and run the correct query.
147
+ ##### After
107
148
 
108
149
  ```ruby
109
150
  1] pry(main)> User.joins(:groups).where(grous: { name: "Landlord" }).last
@@ -116,11 +157,11 @@ I, [2024-01-14T23:50:49.273177 #1248] INFO -- : Running: User.joins(:groups).wh
116
157
 
117
158
  Pry-byetypo is linked to your development database. During initialization, it will attempt to establish a connection to retrieve the tables available in your project. It will fetch the information for the development environment from the `database.yml` file.
118
159
 
119
- ### Unreadable database URL (URI::InvalidURIError)
160
+ #### Unreadable database URL (URI::InvalidURIError)
120
161
 
121
162
  If the database connection string is not readable, the gem will be unable to establish a connection. If you encounter such an issue, make sure to add a `DATABASE_URL` variable to your `.env` file with the easily readable URL of your database.
122
163
 
123
- ### Unreadable connection pool (ActiveRecord::ConnectionTimeoutError)
164
+ #### Unreadable connection pool (ActiveRecord::ConnectionTimeoutError)
124
165
 
125
166
  If the number of connections in your pool is not readable, you may encounter an `ActiveRecord::ConnectionTimeoutError`. If you experience this issue, make sure to add a `DATABASE_POOL` variable to your `.env` file.
126
167
 
@@ -0,0 +1,7 @@
1
+ module Constants
2
+ module Errors
3
+ UNINITIALIZED_CONSTANT = "uninitialized constant".freeze
4
+ UNDEFINED_VARIABLE = "undefined local variable".freeze
5
+ UNDEFINED_TABLE = "UndefinedTable".freeze
6
+ end
7
+ end
@@ -7,10 +7,6 @@ module Exceptions
7
7
  class Base < ExceptionsBase
8
8
  private
9
9
 
10
- def corrected_word
11
- @corrected_word ||= spell_checker(associations_dictionary).correct(unknown_from_exception).first
12
- end
13
-
14
10
  def corrected_cmd
15
11
  @corrected_cmd ||= last_cmd.gsub(/\b#{unknown_from_exception}\b/, corrected_word)
16
12
  end
@@ -19,8 +15,8 @@ module Exceptions
19
15
  exception.to_s.match(exception_regexp)[1]
20
16
  end
21
17
 
22
- def associations_dictionary
23
- @associations_dictionary ||= store.transaction { |s| s["associations"] }
18
+ def dictionary
19
+ store.transaction { |s| s["associations"] }
24
20
  end
25
21
  end
26
22
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Exceptions
6
+ module ActiveRecord
7
+ class UninitializedConstant < Base
8
+ private
9
+
10
+ def unknown_from_exception
11
+ exception.to_s.split.last
12
+ end
13
+
14
+ def corrected_cmd
15
+ @corrected_cmd ||= last_cmd.gsub(unknown_from_exception, corrected_word)
16
+ end
17
+
18
+ def dictionary
19
+ @ar_models_dictionary ||= store.transaction { |s| s["active_record_models"] }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,17 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "logger"
4
+ require "colorize"
3
5
  require_relative "../base"
4
6
  require_relative "../setup/store"
5
- require "colorize"
6
7
 
7
8
  class ExceptionsBase < Base
8
9
  include Setup::Store
9
10
 
10
11
  def call
11
- logger.error(exception.to_s.colorize(color: :light_red, mode: :bold))
12
- logger.info("Running: #{corrected_cmd}".colorize(color: :green, mode: :bold))
13
-
14
- pry.eval(corrected_cmd)
12
+ if corrected_word
13
+ logger.error(exception.to_s.colorize(color: :light_red, mode: :bold))
14
+ logger.info("Running: #{corrected_cmd}".colorize(color: :green, mode: :bold))
15
+
16
+ pry.eval(corrected_cmd)
17
+ else
18
+ Pry::ExceptionHandler.handle_exception(output, exception, pry)
19
+ end
15
20
  end
16
21
 
17
22
  private
@@ -24,7 +29,11 @@ class ExceptionsBase < Base
24
29
  @pry = pry
25
30
  end
26
31
 
27
- def spell_checker(dictionary)
32
+ def corrected_word
33
+ @corrected_word ||= spell_checker.correct(unknown_from_exception).first
34
+ end
35
+
36
+ def spell_checker
28
37
  DidYouMean::SpellChecker.new(dictionary: dictionary)
29
38
  end
30
39
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../exceptions_base"
4
+ require_relative "../../constants/errors"
5
+ require_relative "../active_record/uninitialized_constant"
6
+ require_relative "undefined_variable"
7
+
8
+ module Exceptions
9
+ module NameError
10
+ class Handler < ExceptionsBase
11
+ attr_reader :exception, :output, :pry
12
+
13
+ def initialize(output, exception, pry)
14
+ @output = output
15
+ @exception = exception
16
+ @pry = pry
17
+ end
18
+
19
+ # FIXME: https://github.com/rubocop/rubocop-performance/issues/438
20
+ # rubocop:disable Performance/ConstantRegexp
21
+ def call
22
+ case exception.message
23
+ in /#{Constants::Errors::UNINITIALIZED_CONSTANT}/
24
+ ActiveRecord::UninitializedConstant.call(output, exception, pry)
25
+ in /#{Constants::Errors::UNDEFINED_VARIABLE}/
26
+ UndefinedVariable.call(output, exception, pry)
27
+ else
28
+ Pry::ExceptionHandler.handle_exception(output, exception, pry)
29
+ end
30
+ end
31
+ # rubocop:enable Performance/ConstantRegexp
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../exceptions_base"
4
+
5
+ module Exceptions
6
+ module NameError
7
+ class UndefinedVariable < ExceptionsBase
8
+ private
9
+
10
+ def corrected_cmd
11
+ @corrected_cmd ||= last_cmd.gsub(/\b#{unknown_from_exception}\b/, corrected_word)
12
+ end
13
+
14
+ def unknown_from_exception
15
+ exception.to_s.match(exception_regexp)[1]
16
+ end
17
+
18
+ def dictionary
19
+ store.transaction { |s| s[pry_instance_uid] }
20
+ end
21
+
22
+ def exception_regexp
23
+ /`(\w+)'/
24
+ end
25
+
26
+ # Use the current binding identifier as pry instance uid.
27
+ def pry_instance_uid
28
+ pry.current_binding.to_s
29
+ end
30
+ end
31
+ end
32
+ end
@@ -3,14 +3,12 @@
3
3
  require_relative "base"
4
4
  require_relative "exceptions/active_record/statement_invalid"
5
5
  require_relative "exceptions/active_record/configuration_error"
6
- require_relative "exceptions/name_error"
6
+ require_relative "exceptions/name_error/handler"
7
+ require_relative "constants/errors"
7
8
 
8
9
  class ExceptionsHandler < Base
9
10
  attr_reader :exception, :output, :pry
10
11
 
11
- UNINITIALIZED_CONSTANT = "uninitialized constant"
12
- UNDEFINED_TABLE = "UndefinedTable"
13
-
14
12
  def initialize(output, exception, pry)
15
13
  @output = output
16
14
  @exception = exception
@@ -19,20 +17,15 @@ class ExceptionsHandler < Base
19
17
 
20
18
  def call
21
19
  case exception
22
- in NameError => error
23
- # eg: Usert.last
20
+ in NameError
24
21
  # NameError is a Superclass for all undefined statement.
25
- # In this context We only need to one including an `uninitialized constant` error.
26
- return pry_exception_handler unless error.message.include?(UNINITIALIZED_CONSTANT)
27
- Exceptions::NameError.call(output, exception, pry)
28
- in ActiveRecord::StatementInvalid => error
29
- # eg: User.joins(:groups).where(grous: { name: "Landlord" }).last
22
+ Exceptions::NameError::Handler.call(output, exception, pry)
23
+ in ActiveRecord::StatementInvalid
30
24
  # ActiveRecord::StatementInvalid is a Superclass for all database execution errors.
31
25
  # We only need to one including an `UndefinedTable` error.
32
- return pry_exception_handler unless error.message.include?(UNDEFINED_TABLE)
26
+ return pry_exception_handler unless exception.message.include?(Constants::Errors::UNDEFINED_TABLE)
33
27
  Exceptions::ActiveRecord::StatementInvalid.call(output, exception, pry)
34
28
  in ActiveRecord::ConfigurationError
35
- # eg: User.joins(:group).where(groups: { name: "Landlord" }).last
36
29
  Exceptions::ActiveRecord::ConfigurationError.call(output, exception, pry)
37
30
  else
38
31
  pry_exception_handler
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+ require_relative "../setup/store"
5
+
6
+ module Session
7
+ class ClearHistory < Base
8
+ include Setup::Store
9
+
10
+ attr_reader :pry
11
+
12
+ def initialize(pry)
13
+ @pry = pry
14
+ end
15
+
16
+ def call
17
+ pry_instance_to_remove = pry.push_initial_binding.join
18
+ store.transaction { store.delete(pry_instance_to_remove) }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+ require_relative "../setup/store"
5
+
6
+ module Session
7
+ class PopulateHistory < Base
8
+ include Setup::Store
9
+
10
+ attr_reader :binding
11
+
12
+ def initialize(binding)
13
+ @binding = binding
14
+ end
15
+
16
+ def call
17
+ return unless is_assignement_variables?
18
+
19
+ store.transaction do
20
+ store.abort unless variables_to_store
21
+
22
+ store[pry_instance_uid].push(*variables_to_store)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # Unique instance identifier (e.g., a Pry opened tab)
29
+ def pry_instance_uid
30
+ binding.binding_stack.join
31
+ end
32
+
33
+ def variables_to_store
34
+ @variables_to_store ||= last_cmd.split("=").first.strip.split(",")
35
+ end
36
+
37
+ def last_cmd
38
+ binding.eval_string.strip
39
+ end
40
+
41
+ # Returns true if the last command seems to be an assignment of variables, false otherwise.
42
+ #
43
+ # Examples
44
+ #
45
+ # is_assignment_variables?("user_last, user_first = User.last, User.first")
46
+ # # => true
47
+ #
48
+ # is_assignment_variables?("user_last = User.last")
49
+ # # => true
50
+ #
51
+ # is_assignment_variables?("user_last")
52
+ # # => false
53
+ def is_assignement_variables?
54
+ last_cmd.include?("=")
55
+ end
56
+ end
57
+ end
@@ -1,73 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "checks/database_url"
4
- require_relative "checks/database_pool"
3
+ require_relative "../base"
5
4
  require_relative "store"
6
-
5
+ require_relative "dictionary/active_record"
7
6
  require "pstore"
8
7
 
9
8
  module Setup
10
- class ApplicationDictionary
9
+ class ApplicationDictionary < Base
11
10
  include Store
12
11
 
13
- def initialize
14
- establish_db_connection
12
+ def initialize(binding)
13
+ @binding = binding
14
+ end
15
+
16
+ def call
17
+ Setup::Dictionary::ActiveRecord.initialize! if defined?(ActiveRecord)
15
18
  populate_store
16
19
  end
17
20
 
18
21
  private
19
22
 
20
- SEVEN_DAYS = 604800
23
+ attr_reader :binding
21
24
 
22
25
  def populate_store
23
- store.transaction do
24
- store.abort unless staled_store?
25
-
26
- store["active_record_models"] = populate_active_record_models_dictionary
27
- store["associations"] = populate_associations
28
- store["synced_at"] = Time.now
29
- end
30
- end
31
-
32
- def establish_db_connection
33
- configuration_checks
34
- ActiveRecord::Base.establish_connection(development_database_config)
35
- end
36
-
37
- def populate_active_record_models_dictionary
38
- Zeitwerk::Loader.eager_load_all
39
- ActiveRecord::Base.descendants.map { |model| model.name }
40
- end
41
-
42
- def populate_associations
43
- table_names = ActiveRecord::Base.connection.tables
44
- singularize_table_names = table_names.map { |a| a.chop }
45
- table_names.zip(singularize_table_names).flatten
46
- end
47
-
48
- def configuration_checks
49
- Setup::Checks::DatabaseUrl.check(development_database_config, logger)
50
- Setup::Checks::DatabasePool.check(development_database_config, logger)
51
- end
52
-
53
- def database_config
54
- YAML.safe_load(File.read("./config/database.yml"), aliases: true)
55
- end
56
-
57
- def development_database_config
58
- @development_database_config ||= database_config["development"]
59
- end
60
-
61
- # By default we update the store every week.
62
- # TODO: Make it configurable
63
- def staled_store?
64
- return true if store["synced_at"].nil?
65
-
66
- (store["synced_at"] + SEVEN_DAYS) <= Time.now
26
+ # Create a table with unique instance identifier information to store variables history.
27
+ store.transaction { store[pry_instance_uid] = [] }
67
28
  end
68
29
 
69
- def logger
70
- @logger = Logger.new($stdout)
30
+ # Use the binding identifier as pry instance uid.
31
+ def pry_instance_uid
32
+ binding.to_s
71
33
  end
72
34
  end
73
35
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../checks/database_url"
4
+ require_relative "../checks/database_pool"
5
+ require_relative "../store"
6
+
7
+ require "pstore"
8
+
9
+ module Setup
10
+ module Dictionary
11
+ class ActiveRecord
12
+ class << self
13
+ include Store
14
+
15
+ def initialize!
16
+ establish_db_connection
17
+ populate_store
18
+ end
19
+
20
+ private
21
+
22
+ def populate_store
23
+ store.transaction do
24
+ store.commit unless staled_store?
25
+
26
+ store["active_record_models"] = populate_active_record_models_dictionary
27
+ store["associations"] = populate_associations
28
+ store["synced_at"] = Time.now
29
+ end
30
+ end
31
+
32
+ def establish_db_connection
33
+ configuration_checks
34
+ ::ActiveRecord::Base.establish_connection(development_database_config)
35
+ end
36
+
37
+ def populate_active_record_models_dictionary
38
+ Zeitwerk::Loader.eager_load_all
39
+ ::ActiveRecord::Base.descendants.map { |model| format_active_record_model(model) }.flatten
40
+ end
41
+
42
+ def populate_associations
43
+ table_names = ::ActiveRecord::Base.connection.tables
44
+ singularize_table_names = table_names.map { |a| a.chop }
45
+ table_names.zip(singularize_table_names).flatten
46
+ end
47
+
48
+ # This method takes an ActiveRecord model as an argument and formats its name and module information.
49
+ # If the model is within a module namespace, it returns an array containing the model's name and an array of its modules.
50
+ # If the model is not within a module, it returns just the model's name as a string.
51
+ def format_active_record_model(model)
52
+ modules = model.name.split("::")
53
+ return model.name, modules if modules.count > 1
54
+
55
+ model.name
56
+ end
57
+
58
+ def development_database_config
59
+ @development_database_config ||= database_config["development"]
60
+ end
61
+
62
+ def configuration_checks
63
+ Setup::Checks::DatabaseUrl.check(development_database_config, logger)
64
+ Setup::Checks::DatabasePool.check(development_database_config, logger)
65
+ end
66
+
67
+ def database_config
68
+ YAML.safe_load(File.read(database_config_path), aliases: true)
69
+ end
70
+
71
+ def logger
72
+ @logger = Logger.new($stdout)
73
+ end
74
+
75
+ def database_config_path
76
+ ENV["DB_CONFIG_PATH"] || "./config/database.yml"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -1,6 +1,7 @@
1
1
  module Setup
2
2
  module Store
3
3
  DEFAULT_STORE_PATH = "byetypo_dictionary.pstore"
4
+ SEVEN_DAYS = 604800
4
5
 
5
6
  def store
6
7
  @store ||= PStore.new(store_path)
@@ -9,5 +10,13 @@ module Setup
9
10
  def store_path
10
11
  ENV["BYETYPO_STORE_PATH"] || DEFAULT_STORE_PATH
11
12
  end
13
+
14
+ # By default we update the store every week.
15
+ # TODO: Make it configurable
16
+ def staled_store?
17
+ return true if store["synced_at"].nil?
18
+
19
+ (store["synced_at"] + SEVEN_DAYS) <= Time.now
20
+ end
12
21
  end
13
22
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  class Pry
4
4
  module Byetypo
5
- VERSION = "1.0.2"
5
+ VERSION = "1.2.0"
6
6
  end
7
7
  end
data/lib/pry-byetypo.rb CHANGED
@@ -1,15 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "pry"
4
- require "zeitwerk"
5
4
 
6
- require_relative "pry-byetypo/version"
7
5
  require_relative "pry-byetypo/setup/application_dictionary"
6
+ require_relative "pry-byetypo/session/clear_history"
8
7
  require_relative "pry-byetypo/exceptions_handler"
8
+ require_relative "pry-byetypo/session/populate_history"
9
+ require_relative "pry-byetypo/version"
9
10
 
10
11
  module Pry::Byetypo
11
- Pry.config.hooks.add_hook(:before_session, :eager_loading) do |output, exception, pry|
12
- Setup::ApplicationDictionary.new
12
+ Pry.config.hooks.add_hook(:before_session, :create_dictionary) do |_output, binding, _pry|
13
+ Setup::ApplicationDictionary.call(binding)
14
+ end
15
+
16
+ Pry.config.hooks.add_hook(:after_read, :populate_session_history) do |_output, binding, _pry|
17
+ Session::PopulateHistory.call(binding)
18
+ end
19
+
20
+ Pry.config.hooks.add_hook(:after_session, :clear_session_history) do |_output, _binding, pry|
21
+ Session::ClearHistory.call(pry)
13
22
  end
14
23
 
15
24
  # TODO: Adds max_attempts
data/pry-byetypo.gemspec CHANGED
@@ -5,14 +5,14 @@ require_relative "lib/pry-byetypo/version"
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = "pry-byetypo"
7
7
  gem.version = Pry::Byetypo::VERSION
8
- gem.authors = ["morissetcl"]
8
+ gem.authors = ["Clément Morisset"]
9
9
  gem.email = ["morissetcl87@gmail.com"]
10
10
 
11
11
  gem.summary = "Autocorrects typos in your Pry console"
12
- gem.description = "This small Pry plugin captures exceptions that could be due to typos and deduces the correct command based on your database information."
12
+ gem.description = "This Pry plugin captures exceptions that could be due to typos and deduces the correct command based on your database information."
13
13
  gem.homepage = "https://github.com/morissetcl/pry-byetypo"
14
14
  gem.license = "MIT"
15
- gem.required_ruby_version = ">= 2.6.0"
15
+ gem.required_ruby_version = ">= 2.7.0"
16
16
 
17
17
  gem.metadata["homepage_uri"] = gem.homepage
18
18
  gem.metadata["source_code_uri"] = "https://github.com/morissetcl/pry-byetypo"
@@ -28,7 +28,7 @@ Gem::Specification.new do |gem|
28
28
  gem.bindir = "exe"
29
29
  gem.executables = gem.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
30
  gem.require_paths = ["lib"]
31
+ gem.add_development_dependency "rails", "~> 7.0"
31
32
  gem.add_runtime_dependency "colorize", "~> 1.1.0"
32
33
  gem.add_runtime_dependency "pry", ">= 0.13", "< 0.15"
33
- gem.add_runtime_dependency "rails", "~> 7.0"
34
34
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pry-byetypo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - morissetcl
7
+ - Clément Morisset
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-29 00:00:00.000000000 Z
11
+ date: 2024-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: colorize
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,22 +58,8 @@ dependencies:
44
58
  - - "<"
45
59
  - !ruby/object:Gem::Version
46
60
  version: '0.15'
47
- - !ruby/object:Gem::Dependency
48
- name: rails
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '7.0'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '7.0'
61
- description: This small Pry plugin captures exceptions that could be due to typos
62
- and deduces the correct command based on your database information.
61
+ description: This Pry plugin captures exceptions that could be due to typos and deduces
62
+ the correct command based on your database information.
63
63
  email:
64
64
  - morissetcl87@gmail.com
65
65
  executables: []
@@ -77,16 +77,22 @@ files:
77
77
  - Rakefile
78
78
  - lib/pry-byetypo.rb
79
79
  - lib/pry-byetypo/base.rb
80
+ - lib/pry-byetypo/constants/errors.rb
80
81
  - lib/pry-byetypo/exceptions/active_record/base.rb
81
82
  - lib/pry-byetypo/exceptions/active_record/configuration_error.rb
82
83
  - lib/pry-byetypo/exceptions/active_record/statement_invalid.rb
84
+ - lib/pry-byetypo/exceptions/active_record/uninitialized_constant.rb
83
85
  - lib/pry-byetypo/exceptions/exceptions_base.rb
84
- - lib/pry-byetypo/exceptions/name_error.rb
86
+ - lib/pry-byetypo/exceptions/name_error/handler.rb
87
+ - lib/pry-byetypo/exceptions/name_error/undefined_variable.rb
85
88
  - lib/pry-byetypo/exceptions_handler.rb
89
+ - lib/pry-byetypo/session/clear_history.rb
90
+ - lib/pry-byetypo/session/populate_history.rb
86
91
  - lib/pry-byetypo/setup/application_dictionary.rb
87
92
  - lib/pry-byetypo/setup/checks/base.rb
88
93
  - lib/pry-byetypo/setup/checks/database_pool.rb
89
94
  - lib/pry-byetypo/setup/checks/database_url.rb
95
+ - lib/pry-byetypo/setup/dictionary/active_record.rb
90
96
  - lib/pry-byetypo/setup/store.rb
91
97
  - lib/pry-byetypo/version.rb
92
98
  - pry-byetypo.gemspec
@@ -106,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
106
112
  requirements:
107
113
  - - ">="
108
114
  - !ruby/object:Gem::Version
109
- version: 2.6.0
115
+ version: 2.7.0
110
116
  required_rubygems_version: !ruby/object:Gem::Requirement
111
117
  requirements:
112
118
  - - ">="
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "exceptions_base"
4
-
5
- module Exceptions
6
- class NameError < ExceptionsBase
7
- private
8
-
9
- def unknown_from_exception
10
- exception.to_s.split.last
11
- end
12
-
13
- def corrected_word
14
- @corrected_word ||= spell_checker(ar_models_dictionary).correct(unknown_from_exception).first
15
- end
16
-
17
- def corrected_cmd
18
- @corrected_cmd ||= last_cmd.gsub(unknown_from_exception, corrected_word)
19
- end
20
-
21
- def ar_models_dictionary
22
- @ar_models_dictionary ||= store.transaction { |s| s["active_record_models"] }
23
- end
24
- end
25
- end