การเพิ่มไดเร็กทอรีไปที่ $ LOAD_PATH (Ruby)


96

ฉันได้เห็นสองเทคนิคที่ใช้กันทั่วไปในการเพิ่มไดเร็กทอรีของไฟล์ที่กำลังดำเนินการอยู่ใน $ LOAD_PATH (หรือ $ :) ฉันเห็นข้อดีของการทำเช่นนี้ในกรณีที่คุณไม่ได้ทำงานกับอัญมณี เห็นได้ชัดว่าคนหนึ่งดูละเอียดกว่าอีกคนหนึ่ง แต่มีเหตุผลที่จะไปกับอีกคนหนึ่งหรือไม่?

วิธีแรก verbose (อาจ overkill):

$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))

และตรงไปตรงมารวดเร็วและสกปรกมากขึ้น:

$:.unshift File.dirname(__FILE__)

มีเหตุผลอะไรที่จะไปกับคนอื่น?


2
เล็กน้อยรุ่น verbose น้อยของ verbose หนึ่งคือ:File.expand_path(File.dirname(__FILE__)).tap {|pwd| $LOAD_PATH.unshift(pwd) unless $LOAD_PATH.include?(pwd)}
นาธานยาว

แล้วประโยค "เว้นแต่" ล่ะ? สองข้อข้างต้นเทียบเท่ากันได้อย่างไร?
inger

ในฐานะคนที่มาที่นี่เพื่อพยายามทำความเข้าใจวิธีใช้สิ่งนี้มันเป็นเรื่องที่ลึกลับมาก ฉันไม่เห็นว่าชื่อไดเร็กทอรีมาจากไหนในตัวอย่าง ฉันจะขอบคุณถ้ามีใครบอกเรื่องนี้ได้ชัดเจน
SlySherZ

1
การใช้__dir__(ณ Ruby 2.0) สามารถทำให้สิ่งเหล่านี้รัดกุมมากขึ้น
Nathan Long

คำตอบ:


52

ฉันจะบอกว่าไปกับ$:.unshift File.dirname(__FILE__)อีกอันหนึ่งเพียงเพราะฉันเห็นการใช้งานในโค้ดมากกว่าโค้ด$LOAD_PATHและมันสั้นกว่าด้วย!


เมื่อฉันเริ่มต้นกับ Ruby ครั้งแรกเห็นได้ชัดว่า $ LOAD_PATH ดีกว่า แต่เมื่อคุณจบการศึกษาจากสถานะเริ่มต้นแล้วฉันจะใช้ $ LOAD_PATH ก็ต่อเมื่อฉันพยายามทำให้โค้ดของฉันอ่านง่ายขึ้นสำหรับผู้เริ่มต้น Meh เป็นการแลกเปลี่ยน ขึ้นอยู่กับว่าโค้ด "สาธารณะ" เป็นอย่างไรตราบใดที่การใช้หน่วยความจำเหมือนกันสำหรับแต่ละรหัสซึ่งฉันคิดว่ามันเป็นหลัก
boulder_ruby

9
ขึ้นอยู่กับคำแนะนำสไตล์ที่คุณติดตามสำหรับโครงการของคุณ Ruby Style Guide ที่ได้รับความนิยมกล่าวว่า "หลีกเลี่ยงการใช้ตัวแปรพิเศษสไตล์ Perl (เช่น $ :, $; ฯลฯ ) มันค่อนข้างคลุมเครือและไม่แนะนำให้ใช้สคริปต์ซับเดียว"
bobmagoo

153

เส้นทางการโหลด Ruby มักถูกเขียนว่า $: แต่เพียงเพราะมันสั้นไม่ได้ทำให้ดีขึ้น ถ้าคุณชอบความชัดเจนกับความฉลาดหรือถ้าความสั้นเพราะเห็นแก่ตัวเองทำให้คุณคันคุณไม่จำเป็นต้องทำเพียงเพราะคนอื่นเป็น ทักมาที่ ...

$LOAD_PATH

... และบอกลา ...

# I don't quite understand what this is doing...
$:

29
นอกจากนี้ Google ยากกว่ามากสำหรับสตริงเช่น "$:" ที่มีเฉพาะสัญลักษณ์
DSimon

24

ฉันไม่ค่อยชอบวิธีที่ 'รวดเร็วและสกปรก' ทุกคนใหม่เพื่อทับทิมจะขบคิดสิ่งที่$:.เป็น

ฉันพบว่าสิ่งนี้ชัดเจนมากขึ้น

libdir = File.dirname(__FILE__)
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)

หรือถ้าฉันสนใจที่จะมีเส้นทางที่สมบูรณ์ ...

libdir = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)

อัพเดท 2009/09/10

เมื่อตอนสายฉันได้ทำสิ่งต่อไปนี้:

$:.unshift(File.expand_path(File.dirname(__FILE__))) unless
    $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

ฉันเคยเห็นมันในโครงการทับทิมที่แตกต่างกันมากมายในขณะที่เรียกดู GitHub

น่าจะเป็นตามอนุสัญญา?


@LukeAntins นี่ยอดเยี่ยมมาก แต่ฉันจะ "bootstrap" load_path ในแอปพลิเคชันได้ที่ไหน
gaussblurinc

@gaussblurinc ที่ไหนสักแห่ง 'ใกล้ด้านบน' ของ lib / แอปพลิเคชันของคุณ แต่มันขึ้นอยู่กับจริงๆ หากคุณมีbinไฟล์ที่สัมพันธ์กับของคุณเสมอcodeและเคยเรียกใช้โดยbinไฟล์ ... bootstrap ใน bin ถ้าคุณมีห้องสมุดแล้วบูตที่ด้านบนของคุณรหัสห้องสมุดต้องการในการได้รับการเข้าถึงทุกอย่างภายใต้lib/code.rb lib/code/หวังว่าการเดินเตร่นี้จะช่วยได้!
Luke Antins

1
RuboCop แจ้งให้ฉันทราบว่า__dir__สามารถใช้เพื่อรับเส้นทางไปยังไดเร็กทอรีของไฟล์ปัจจุบันได้
Raphael

8

หากคุณพิมพ์script/consoleโปรเจ็กต์ Rails ของคุณและป้อน$:คุณจะได้รับอาร์เรย์ที่มีไดเร็กทอรีทั้งหมดที่จำเป็นในการโหลด Ruby สิ่งที่จะได้รับจากการออกกำลังกายเล็กน้อยนี้$:คืออาร์เรย์ ด้วยเหตุนี้คุณจึงสามารถใช้งานฟังก์ชั่นดังกล่าวได้เช่นการเติมไดเรกทอรีอื่น ๆ ด้วยunshiftวิธีการหรือตัว<<ดำเนินการ ตามที่คุณบอกโดยนัยในคำชี้แจงของคุณ$:และ$LOAD_PATHก็เหมือนกัน

ข้อเสียของการทำวิธีที่รวดเร็วและสกปรกตามที่คุณกล่าวไว้คือ: หากคุณมีไดเร็กทอรีในเส้นทางการบูตของคุณอยู่แล้วมันจะทำซ้ำ

ตัวอย่าง:

ฉันมีปลั๊กอินที่ฉันสร้างขึ้นชื่อสิ่งที่ต้องทำ ไดเร็กทอรีของฉันมีโครงสร้างดังนี้:

/ --- ผู้ขาย
  |
  | --- / ปลั๊กอิน
        |
        | --- / สิ่งที่ต้องทำ
              |
              | --- / lib
                    |
                    | --- / แอพ
                          |
                          | --- / รุ่น
                          | --- / ตัวควบคุม
              |
              | --- / ราง
                    |
                    | --- init.rb

ในไฟล์ init.rb ฉันป้อนรหัสต่อไปนี้:

## In vendor/plugins/todo/rails/init.rb
    %w{ models controllers models }.each do |dir|
      path = File.expand_path(File.join(File.dirname(__FILE__), '../lib', 'app', dir))
      $LOAD_PATH << path
      ActiveSupport::Dependencies.load_paths << path
      ActiveSupport::Dependencies.load_once_paths.delete(path)
    end 

สังเกตว่าฉันบอกให้บล็อกโค้ดดำเนินการภายในบล็อกกับสตริง 'model', 'controllers' และ 'models' ได้อย่างไรโดยที่ฉันทำซ้ำ 'models' (FYI %w{ ... }เป็นอีกวิธีหนึ่งในการบอกให้ Ruby ถืออาร์เรย์ของสตริง) เมื่อฉันเรียกใช้script/consoleฉันพิมพ์สิ่งต่อไปนี้:

>> puts $:

และฉันพิมพ์สิ่งนี้เพื่อให้ง่ายต่อการอ่านเนื้อหาในสตริง ผลลัพธ์ที่ฉันได้รับคือ:

...
...
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/models
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/controllers
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/models

อย่างที่คุณเห็นแม้ว่านี่จะเป็นตัวอย่างง่ายๆที่ฉันสามารถสร้างได้ในขณะที่ใช้โปรเจ็กต์ที่ฉันกำลังดำเนินการอยู่หากคุณไม่ระวังวิธีที่รวดเร็วและสกปรกจะนำไปสู่เส้นทางซ้ำ ๆ วิธีที่ยาวกว่าจะตรวจสอบเส้นทางที่ซ้ำและตรวจสอบให้แน่ใจว่าไม่ได้เกิดขึ้น

หากคุณเป็นโปรแกรมเมอร์ Rails ที่มีประสบการณ์คุณอาจมีความคิดที่ดีมากว่าคุณกำลังทำอะไรอยู่และมีแนวโน้มที่จะไม่ทำผิดพลาดในการทำเส้นทางซ้ำ หากคุณเป็นมือใหม่ฉันจะใช้เวลานานกว่านี้จนกว่าคุณจะเข้าใจจริงๆว่าคุณกำลังทำอะไรอยู่


คำตอบของคุณมีประโยชน์มากและอธิบายได้ดี การแก้ไขที่แนะนำ: วิธีการload_pathsและload_once_paths.deleteเลิกใช้งานแล้ว จะได้รับความช่วยเหลือในการอัปเดตบรรทัดที่อ้างถึง: ActiveSupport::Dependencies.autoload_paths << path ActiveSupport::Dependencies.autoload_once_paths.delete(path)
Uzzar

8

ดีที่สุดที่ฉันเจอในการเพิ่ม dir ผ่านทางญาติเมื่อใช้ Rspec ฉันคิดว่ามันละเอียดเพียงพอ แต่ก็ยังเป็นซับที่ดี

$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))

1

มีอัญมณีที่จะช่วยให้คุณตั้งค่าเส้นทางการโหลดของคุณด้วยรหัสที่ดีกว่าและสะอาดกว่า ตรวจสอบนี้: https://github.com/nayyara-samuel/load-path

นอกจากนี้ยังมีเอกสารที่ดี


-2

ฉันรู้ว่ามันเป็นเวลานานแล้วที่คำถามนี้ถูกถามครั้งแรก แต่ฉันมีคำตอบเพิ่มเติมที่ฉันต้องการจะแบ่งปัน

ฉันมีแอปพลิเคชัน Ruby หลายตัวที่พัฒนาโดยโปรแกรมเมอร์คนอื่นในช่วงหลายปีที่ผ่านมาและพวกเขาใช้คลาสเดียวกันซ้ำในแอปพลิเคชั่นอื่นแม้ว่าพวกเขาอาจเข้าถึงฐานข้อมูลเดียวกัน เนื่องจากสิ่งนี้ละเมิดกฎ DRY ฉันจึงตัดสินใจสร้างไลบรารีชั้นเรียนเพื่อแชร์โดยแอปพลิเคชัน Ruby ทั้งหมด ฉันสามารถวางไว้ในไลบรารี Ruby หลักได้ แต่จะซ่อนโค้ดที่กำหนดเองใน codebase ทั่วไปที่ฉันไม่ต้องการทำ

ฉันมีปัญหาที่ฉันมีความขัดแย้งของชื่อระหว่างชื่อที่กำหนดไว้แล้ว "profile.rb" กับคลาสที่ฉันใช้อยู่ ความขัดแย้งนี้ไม่ใช่ปัญหาจนกว่าฉันจะพยายามสร้างไลบรารีรหัสทั่วไป โดยปกติ Ruby จะค้นหาตำแหน่งของแอปพลิเคชันก่อนจากนั้นไปที่ตำแหน่ง $ LOAD_PATH

application_controller.rb ไม่พบคลาสที่ฉันสร้างและทำให้เกิดข้อผิดพลาดในนิยามดั้งเดิมเนื่องจากไม่ใช่คลาส เนื่องจากฉันลบคำจำกัดความของคลาสออกจากส่วนแอพ / รุ่นของแอปพลิเคชัน Ruby ไม่พบที่นั่นและไปหามันในพา ธ Ruby

ดังนั้นฉันจึงแก้ไขตัวแปร $ LOAD_PATH เพื่อรวมพา ธ ไปยังไดเร็กทอรีไลบรารีที่ฉันใช้ ซึ่งสามารถทำได้ในไฟล์ environment.rb ในเวลาเริ่มต้น

แม้ว่าจะมีการเพิ่มไดเร็กทอรีใหม่ลงในเส้นทางการค้นหา แต่ Ruby ก็ยังแสดงข้อผิดพลาดเนื่องจากถูกนำไฟล์ที่ระบบกำหนดไว้ก่อน เส้นทางการค้นหาในตัวแปร $ LOAD_PATH จะค้นหาเส้นทาง Ruby ก่อน

ดังนั้นฉันต้องเปลี่ยนลำดับการค้นหาเพื่อให้ Ruby พบคลาสในไลบรารีทั่วไปของฉันก่อนที่จะค้นหาไลบรารีในตัว

รหัสนี้ทำในไฟล์ environment.rb:

Rails::Initializer.run do |config|

* * * * *

path = []
path.concat($LOAD_PATH)
$LOAD_PATH.clear
$LOAD_PATH << 'C:\web\common\lib'
$LOAD_PATH << 'C:\web\common'
$LOAD_PATH.concat(path)

* * * * *

end

ฉันไม่คิดว่าคุณจะสามารถใช้โครงสร้างการเข้ารหัสขั้นสูงใด ๆ ที่ให้มาก่อนในระดับนี้ได้ แต่จะใช้ได้ดีหากคุณต้องการตั้งค่าบางอย่างในเวลาเริ่มต้นในแอปของคุณ คุณต้องรักษาลำดับเดิมของตัวแปร $ LOAD_PATH เดิมเมื่อเพิ่มกลับไปยังตัวแปรใหม่มิฉะนั้นคลาส Ruby หลักบางส่วนจะสูญหายไป

ในไฟล์ application_controller.rb ฉันใช้ไฟล์

require 'profile'
require 'etc' #etc

และสิ่งนี้จะโหลดไฟล์ไลบรารีที่กำหนดเองสำหรับแอปพลิเคชันทั้งหมดกล่าวคือฉันไม่ต้องใช้คำสั่งที่ต้องการในทุกคอนโทรลเลอร์

สำหรับฉันนี่คือวิธีแก้ปัญหาที่ฉันกำลังมองหาและฉันคิดว่าจะเพิ่มเข้าไปในคำตอบนี้เพื่อส่งต่อข้อมูลไป

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.