rails-tips

Railsのローカル変数、インスタンス変数について

pepe87
記事内に商品プロモーションを含む場合があります

Railsには変数の種類が複数あり、混乱してしまうことがあると思います。

そこで、今回は具体的なコードと表示される画面を含め、違いを確認し、つまりどういうことかの解説まで行います。

ローカル変数、インスタンス変数について、整理していきましょう。

ローカル変数とインスタンス変数の違いについて

結論からお伝えしてしまうと、ローカル変数とインスタンス変数では変数を使える範囲、スコープが違います。

posts_controller.rbのnewにて、新規投稿をしようとした例で紹介します。

インスタンス変数の例

コントローラーのコードは以下のようになっているとします。

posts_controller.rb
1class PostsController < ApplicationController 
2  def new
3    @post = Post.new
4    title = "ローカル変数とインスタンス変数の使い方"
5  end
6end

ローカル変数は@がついていないtitle、インスタンス変数は@がついている@postですね。

そして、ビューファイルのnew.html.erbが以下のようになっているとします。

new.html.erb
1<%= @post %>

すると、以下のように表示されます。

ぱっと見だと中身がよくわかりませんね。

ただ、何かが表示されている、エラーの赤い画面が表示されていないという事実から変数が使えていることはわかります。

ローカル変数の例

先ほどとコントローラーのコードは変えずに、ローカル変数であるtitleを表示するため、ビューファイルのコードを以下にしてみます。

new.html.erb
1<%= title %>

すると、以下のようにエラー画面が表示されてしまいます。

今度はぱっと見うまくいっていないことがわかりますね。

なぜ使えないか、までは現時点ではわかりませんが、@をつけたインスタンス変数でないと、ビューファイルに表示ができないことがわかりました。

一応エラーの内容を読んでみると、undefind local variable ~ titleと書いてありますので、「titleというローカル変数は定義されていないよ」と言われています。

つまり、new.html.erbに定義されているtitleという変数を探しにいっているわけですね。

補足編:ローカル変数とインスタンス変数について

ここからはまだ気になることがあって、先に進めないという方に向けて、もう少し具体例を示して、解説していきます。

上記で腹落ちしたかたは作成中のアプリの作業の再開なりに戻っていただければと思います。

補足1: title変数に@をつけたら表示されるのか

結論、表示されます。

コントローラーとビューをそれぞれ変えた実行結果までお見せします。

posts_controller.rb
1class PostsController < ApplicationController 
2  def new
3    @post = Post.new
4    @title = "ローカル変数とインスタンス変数の使い方"
5  end
6end
new.html.erb
1<%= @title %>

この例で、Postモデルなどテーブル(DB)に紐づいた中身のインスタンス変数でないと表示されないのではないか?という疑問に対して、「インスタンス変数の中身は関係ない」という事が証明されましたね。

補足②:@postの中身が本当にビューに渡せているのか

結論、渡せています。

しかし、先ほどの例だと、@postが本当に渡せているのか?と気になる方もいらっしゃると思うので、紹介していきます。

早速コードを変えていきたいところですが、@postはPostクラスのインスタンス変数ですので、Postモデル(テーブル)のカラムが関係します。

そのため、Postテーブルのカラムがどうなっているかを先に紹介します。

1create_table "posts", force: :cascade do |t|
2    t.string "title"
3    t.text "content"
4    t.datetime "created_at", null: false
5    t.datetime "updated_at", null: false
6  end

上記のような場合で、中身が渡せているかを確認したいので、コードを以下のように変えていきます。

posts_controller.rb
1class PostsController < ApplicationController 
2  def new
3    @post = Post.new
4    @post.title = "タイトル"
5    title = "ローカル変数とインスタンス変数の使い方"
6  end
7end
new.html.erb
1<%= @post.title %>

補足③:@をつけないローカル変数はどこで使えるのか

結論、def ~ endの中までなら使えます。

使える範囲を小さい単位順でまとめると以下になります。

  • def ~ endの中
  • class ~ endの中
  • 他のクラスから

rubyではブロックという単位、継承が使えますので、厳密にはもう少し細分化できますが、より混乱しますので、上記のような分け方とします。

では、実際にローカル変数はdef ~ endの中でしか使えないことを確認します。

ローカル変数がdef ~ endの中で使えるかの確認

コントローラーのコードを以下のように変更します。

1class PostsController < ApplicationController
2  def new
3    @post = Post.new
4    @post.title = "タイトル"
5    title = "ローカル変数とインスタンス変数の使い方"
6    puts title
7  end

そして、Webブラウザで、「~ posts/new」にアクセスするとターミナルにtitle変数の中身、「ローカル変数とインスタンス変数の使い方」が表示されていると思います。

Started GET "/posts/new" for ::1 at 2024-03-14 09:55:24 +0900
Processing by PostsController#new as HTML
ローカル変数とインスタンス変数の使い方
  Rendering layout layouts/application.html.erb
  Rendering posts/new.html.erb within layouts/application
  Rendered posts/new.html.erb within layouts/application (Duration: 0.1ms | Allocations: 14)
  Rendered layout layouts/application.html.erb (Duration: 8.8ms | Allocations: 15323)
Completed 200 OK in 14ms (Views: 9.4ms | ActiveRecord: 0.3ms | Allocations: 20307)

これで、def ~ endの中からは使えている事がわかりました。

次に、class ~ endの中では使えるのか、を試していきます。

ローカル変数がclass ~ endの中で使えるかの確認

コントローラーを以下のように変更します。

1class PostsController < ApplicationController
2  def new
3    @post = Post.new
4    @post.title = "タイトル"
5    title = "ローカル変数とインスタンス変数の使い方"
6    output
7  end
8
9  def output
10    puts title
11  end
12end

すると、以下のようにエラーになってしまいます。

エラー文を読むと、「titleというローカル変数が定義されていません」となっていますので、def~endの外、class~endの中でも使えない事がわかりました。

そのため、当然classの外側、つまりビューファイルからも変数は使えない事がわかります。

さらに深い補足について

ここまで読んでもよくわからない事があるという人がいると思います。

controllerからviewにはなぜ@をつけたインスタンス変数だけ渡せるのか、ということです。

回答を記述する前にRails(フレームワーク)を使う上で、初学者の方に向けて大事な心得の話をしたいと思います。

ここから先は初学者の方がどこまで何を理解するべきなのか、ということを書いています。

「今何を出来るようになりたいのか?」と問いを立てる

補足の部分まで分かったことをと分かっていないことを整理すると以下になると思います。

ここまでで分かっていること
  • コントローラーからビューに変数を渡すには@をつける必要がある
  • @の変数の中身はなんでもビューに渡せる
ここまでで分かっていないこと

コントローラーからビューに変数を渡すにはなぜ@が必要なのか?

整理してみたら次は、ここまでで出来るようになったことを整理したいと思います。

ここまでで出来るようになったこと
  • コントローラーからビューに変数を渡せる
  • ローカル変数を適切に使える

整理してみるといかがですか?検索した目的は達成していませんか?

検索している間に、何を理解したかったのか、の目的がわからなくなってしまうことはよくあることだと思うので、初学者の方は特に以下を行動してもらえると良いと思います。

検索するときの心得
  • 検索前に何を困っているか、理解したいかをメモする
  • 検索した記事を読んだ後はメモを見返し、今何が出来るようになったか?を自分に問う

学校の勉強とプログラミングの学習は違う点がたくさんありますので、最初はうまく行かなて当然だと僕は思っています。

プログラミング学習について困ったら以下から相談もしてみてください。

それでも知りたい方へ

転職されることをゴールに学習されている方は優先順位、時間の使い方を整理していただいた上で、調べる余裕があればヒントをお出ししますので、さらに自分で調べてみてください。

ここから先は記事を読んだから理解できるモノではないと思っていますので、ヒントという形にしています。

コントローラーとビューをそれぞれを深ぼる

インスタンス変数を渡す側のコントローラー、受け取る側のビューをそれぞれ深ぼる必要があります。

コントローラーはActionControllerを継承しているので、Githubから検索すると以下のコードに辿り着けます。

Q
コントローラーのGithub探索
1# frozen_string_literal: true
2
3# :markup: markdown
4
5require "action_view"
6require "action_controller/log_subscriber"
7require "action_controller/metal/params_wrapper"
8
9module ActionController
10  # # Action Controller Base
11  #
12  # Action Controllers are the core of a web request in Rails. They are made up of
13  # one or more actions that are executed on request and then either it renders a
14  # template or redirects to another action. An action is defined as a public
15  # method on the controller, which will automatically be made accessible to the
16  # web-server through Rails Routes.
17  #
18  # By default, only the ApplicationController in a Rails application inherits
19  # from `ActionController::Base`. All other controllers inherit from
20  # ApplicationController. This gives you one class to configure things such as
21  # request forgery protection and filtering of sensitive request parameters.
22  #
23  # A sample controller could look like this:
24  #
25  #     class PostsController < ApplicationController
26  #       def index
27  #         @posts = Post.all
28  #       end
29  #
30  #       def create
31  #         @post = Post.create params[:post]
32  #         redirect_to posts_path
33  #       end
34  #     end
35  #
36  # Actions, by default, render a template in the `app/views` directory
37  # corresponding to the name of the controller and action after executing code in
38  # the action. For example, the `index` action of the PostsController would
39  # render the template `app/views/posts/index.html.erb` by default after
40  # populating the `@posts` instance variable.
41  #
42  # Unlike index, the create action will not render a template. After performing
43  # its main purpose (creating a new post), it initiates a redirect instead. This
44  # redirect works by returning an external `302 Moved` HTTP response that takes
45  # the user to the index action.
46  #
47  # These two methods represent the two basic action archetypes used in Action
48  # Controllers: Get-and-show and do-and-redirect. Most actions are variations on
49  # these themes.
50  #
51  # ## Requests
52  #
53  # For every request, the router determines the value of the `controller` and
54  # `action` keys. These determine which controller and action are called. The
55  # remaining request parameters, the session (if one is available), and the full
56  # request with all the HTTP headers are made available to the action through
57  # accessor methods. Then the action is performed.
58  #
59  # The full request object is available via the request accessor and is primarily
60  # used to query for HTTP headers:
61  #
62  #     def server_ip
63  #       location = request.env["REMOTE_ADDR"]
64  #       render plain: "This server hosted at #{location}"
65  #     end
66  #
67  # ## Parameters
68  #
69  # All request parameters, whether they come from a query string in the URL or
70  # form data submitted through a POST request are available through the `params`
71  # method which returns a hash. For example, an action that was performed through
72  # `/posts?category=All&limit=5` will include `{ "category" => "All", "limit" =>
73  # "5" }` in `params`.
74  #
75  # It's also possible to construct multi-dimensional parameter hashes by
76  # specifying keys using brackets, such as:
77  #
78  #     <input type="text" name="post[name]" value="david">
79  #     <input type="text" name="post[address]" value="hyacintvej">
80  #
81  # A request coming from a form holding these inputs will include `{ "post" => {
82  # "name" => "david", "address" => "hyacintvej" } }`. If the address input had
83  # been named `post[address][street]`, the `params` would have included `{ "post"
84  # => { "address" => { "street" => "hyacintvej" } } }`. There's no limit to the
85  # depth of the nesting.
86  #
87  # ## Sessions
88  #
89  # Sessions allow you to store objects in between requests. This is useful for
90  # objects that are not yet ready to be persisted, such as a Signup object
91  # constructed in a multi-paged process, or objects that don't change much and
92  # are needed all the time, such as a User object for a system that requires
93  # login. The session should not be used, however, as a cache for objects where
94  # it's likely they could be changed unknowingly. It's usually too much work to
95  # keep it all synchronized -- something databases already excel at.
96  #
97  # You can place objects in the session by using the `session` method, which
98  # accesses a hash:
99  #
100  #     session[:person] = Person.authenticate(user_name, password)
101  #
102  # You can retrieve it again through the same hash:
103  #
104  #     "Hello #{session[:person]}"
105  #
106  # For removing objects from the session, you can either assign a single key to
107  # `nil`:
108  #
109  #     # removes :person from session
110  #     session[:person] = nil
111  #
112  # or you can remove the entire session with `reset_session`.
113  #
114  # By default, sessions are stored in an encrypted browser cookie (see
115  # ActionDispatch::Session::CookieStore). Thus the user will not be able to read
116  # or edit the session data. However, the user can keep a copy of the cookie even
117  # after it has expired, so you should avoid storing sensitive information in
118  # cookie-based sessions.
119  #
120  # ## Responses
121  #
122  # Each action results in a response, which holds the headers and document to be
123  # sent to the user's browser. The actual response object is generated
124  # automatically through the use of renders and redirects and requires no user
125  # intervention.
126  #
127  # ## Renders
128  #
129  # Action Controller sends content to the user by using one of five rendering
130  # methods. The most versatile and common is the rendering of a template.
131  # Included in the Action Pack is the Action View, which enables rendering of ERB
132  # templates. It's automatically configured. The controller passes objects to the
133  # view by assigning instance variables:
134  #
135  #     def show
136  #       @post = Post.find(params[:id])
137  #     end
138  #
139  # Which are then automatically available to the view:
140  #
141  #     Title: <%= @post.title %>
142  #
143  # You don't have to rely on the automated rendering. For example, actions that
144  # could result in the rendering of different templates will use the manual
145  # rendering methods:
146  #
147  #     def search
148  #       @results = Search.find(params[:query])
149  #       case @results.count
150  #         when 0 then render action: "no_results"
151  #         when 1 then render action: "show"
152  #         when 2..10 then render action: "show_many"
153  #       end
154  #     end
155  #
156  # Read more about writing ERB and Builder templates in ActionView::Base.
157  #
158  # ## Redirects
159  #
160  # Redirects are used to move from one action to another. For example, after a
161  # `create` action, which stores a blog entry to the database, we might like to
162  # show the user the new entry. Because we're following good DRY principles
163  # (Don't Repeat Yourself), we're going to reuse (and redirect to) a `show`
164  # action that we'll assume has already been created. The code might look like
165  # this:
166  #
167  #     def create
168  #       @entry = Entry.new(params[:entry])
169  #       if @entry.save
170  #         # The entry was saved correctly, redirect to show
171  #         redirect_to action: 'show', id: @entry.id
172  #       else
173  #         # things didn't go so well, do something else
174  #       end
175  #     end
176  #
177  # In this case, after saving our new entry to the database, the user is
178  # redirected to the `show` method, which is then executed. Note that this is an
179  # external HTTP-level redirection which will cause the browser to make a second
180  # request (a GET to the show action), and not some internal re-routing which
181  # calls both "create" and then "show" within one request.
182  #
183  # Learn more about `redirect_to` and what options you have in
184  # ActionController::Redirecting.
185  #
186  # ## Calling multiple redirects or renders
187  #
188  # An action may contain only a single render or a single redirect. Attempting to
189  # try to do either again will result in a DoubleRenderError:
190  #
191  #     def do_something
192  #       redirect_to action: "elsewhere"
193  #       render action: "overthere" # raises DoubleRenderError
194  #     end
195  #
196  # If you need to redirect on the condition of something, then be sure to add
197  # "and return" to halt execution.
198  #
199  #     def do_something
200  #       redirect_to(action: "elsewhere") and return if monkeys.nil?
201  #       render action: "overthere" # won't be called if monkeys is nil
202  #     end
203  #
204  class Base < Metal
205    abstract!
206
207    # Shortcut helper that returns all the modules included in
208    # ActionController::Base except the ones passed as arguments:
209    #
210    #     class MyBaseController < ActionController::Metal
211    #       ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
212    #         include left
213    #       end
214    #     end
215    #
216    # This gives better control over what you want to exclude and makes it easier to
217    # create a bare controller class, instead of listing the modules required
218    # manually.
219    def self.without_modules(*modules)
220      modules = modules.map do |m|
221        m.is_a?(Symbol) ? ActionController.const_get(m) : m
222      end
223
224      MODULES - modules
225    end
226
227    MODULES = [
228      AbstractController::Rendering,
229      AbstractController::Translation,
230      AbstractController::AssetPaths,
231
232      Helpers,
233      UrlFor,
234      Redirecting,
235      ActionView::Layouts,
236      Rendering,
237      Renderers::All,
238      ConditionalGet,
239      EtagWithTemplateDigest,
240      EtagWithFlash,
241      Caching,
242      MimeResponds,
243      ImplicitRender,
244      StrongParameters,
245      ParameterEncoding,
246      Cookies,
247      Flash,
248      FormBuilder,
249      RequestForgeryProtection,
250      ContentSecurityPolicy,
251      PermissionsPolicy,
252      RateLimiting,
253      AllowBrowser,
254      Streaming,
255      DataStreaming,
256      HttpAuthentication::Basic::ControllerMethods,
257      HttpAuthentication::Digest::ControllerMethods,
258      HttpAuthentication::Token::ControllerMethods,
259      DefaultHeaders,
260      Logging,
261
262      # Before callbacks should also be executed as early as possible, so also include
263      # them at the bottom.
264      AbstractController::Callbacks,
265
266      # Append rescue at the bottom to wrap as much as possible.
267      Rescue,
268
269      # Add instrumentations hooks at the bottom, to ensure they instrument all the
270      # methods properly.
271      Instrumentation,
272
273      # Params wrapper should come before instrumentation so they are properly showed
274      # in logs
275      ParamsWrapper
276    ]
277
278    MODULES.each do |mod|
279      include mod
280    end
281    setup_renderer!
282
283    # Define some internal variables that should not be propagated to the view.
284    PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + %i(
285      @_params @_response @_request @_config @_url_options @_action_has_layout @_view_context_class
286      @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy
287      @_marked_for_same_origin_verification @_rendered_format
288    )
289
290    def _protected_ivars
291      PROTECTED_IVARS
292    end
293    private :_protected_ivars
294
295    ActiveSupport.run_load_hooks(:action_controller_base, self)
296    ActiveSupport.run_load_hooks(:action_controller, self)
297  end
298end

また、ビュー側も同様にGithubで検索していくと、以下になります。

Q
ビューファイルのGithub探索
1# frozen_string_literal: true
2
3require "active_support/core_ext/module/attr_internal"
4require "active_support/core_ext/module/attribute_accessors"
5require "active_support/ordered_options"
6require "action_view/log_subscriber"
7require "action_view/helpers"
8require "action_view/context"
9require "action_view/template"
10require "action_view/lookup_context"
11
12module ActionView # :nodoc:
13  # = Action View \Base
14  #
15  # Action View templates can be written in several ways.
16  # If the template file has a <tt>.erb</tt> extension, then it uses the erubi[https://rubygems.org/gems/erubi]
17  # template system which can embed Ruby into an HTML document.
18  # If the template file has a <tt>.builder</tt> extension, then Jim Weirich's Builder::XmlMarkup library is used.
19  #
20  # == ERB
21  #
22  # You trigger ERB by using embeddings such as <tt><% %></tt>, <tt><% -%></tt>, and <tt><%= %></tt>. The <tt><%= %></tt> tag set is used when you want output. Consider the
23  # following loop for names:
24  #
25  #   <b>Names of all the people</b>
26  #   <% @people.each do |person| %>
27  #     Name: <%= person.name %><br/>
28  #   <% end %>
29  #
30  # The loop is set up in regular embedding tags <tt><% %></tt>, and the name is written using the output embedding tag <tt><%= %></tt>. Note that this
31  # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
32  #
33  #   <%# WRONG %>
34  #   Hi, Mr. <% puts "Frodo" %>
35  #
36  # If you absolutely must write from within a function use +concat+.
37  #
38  # When on a line that only contains whitespaces except for the tag, <tt><% %></tt> suppresses leading and trailing whitespace,
39  # including the trailing newline. <tt><% %></tt> and <tt><%- -%></tt> are the same.
40  # Note however that <tt><%= %></tt> and <tt><%= -%></tt> are different: only the latter removes trailing whitespaces.
41  #
42  # === Using sub templates
43  #
44  # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
45  # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
46  #
47  #   <%= render "application/header" %>
48  #   Something really specific and terrific
49  #   <%= render "application/footer" %>
50  #
51  # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
52  # result of the rendering. The output embedding writes it to the current template.
53  #
54  # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
55  # variables defined using the regular embedding tags. Like this:
56  #
57  #   <% @page_title = "A Wonderful Hello" %>
58  #   <%= render "application/header" %>
59  #
60  # Now the header can pick up on the <tt>@page_title</tt> variable and use it for outputting a title tag:
61  #
62  #   <title><%= @page_title %></title>
63  #
64  # === Passing local variables to sub templates
65  #
66  # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
67  #
68  #   <%= render "application/header", { headline: "Welcome", person: person } %>
69  #
70  # These can now be accessed in <tt>application/header</tt> with:
71  #
72  #   Headline: <%= headline %>
73  #   First name: <%= person.first_name %>
74  #
75  # The local variables passed to sub templates can be accessed as a hash using the <tt>local_assigns</tt> hash. This lets you access the
76  # variables as:
77  #
78  #   Headline: <%= local_assigns[:headline] %>
79  #
80  # This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use
81  # <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
82  #
83  # By default, templates will accept any <tt>locals</tt> as keyword arguments. To restrict what <tt>locals</tt> a template accepts, add a <tt>locals:</tt> magic comment:
84  #
85  #   <%# locals: (headline:) %>
86  #
87  #   Headline: <%= headline %>
88  #
89  # In cases where the local variables are optional, declare the keyword argument with a default value:
90  #
91  #   <%# locals: (headline: nil) %>
92  #
93  #   <% unless headline.nil? %>
94  #   Headline: <%= headline %>
95  #   <% end %>
96  #
97  # Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
98  # in the guides.
99  #
100  # === Template caching
101  #
102  # By default, \Rails will compile each template to a method in order to render it. When you alter a template,
103  # \Rails will check the file's modification time and recompile it in development mode.
104  #
105  # == Builder
106  #
107  # Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object
108  # named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
109  #
110  # Here are some basic examples:
111  #
112  #   xml.em("emphasized")                                 # => <em>emphasized</em>
113  #   xml.em { xml.b("emph & bold") }                      # => <em><b>emph & bold</b></em>
114  #   xml.a("A Link", "href" => "http://onestepback.org")  # => <a href="http://onestepback.org">A Link</a>
115  #   xml.target("name" => "compile", "option" => "fast")  # => <target option="fast" name="compile"\>
116  #                                                        # NOTE: order of attributes is not specified.
117  #
118  # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
119  #
120  #   xml.div do
121  #     xml.h1(@person.name)
122  #     xml.p(@person.bio)
123  #   end
124  #
125  # would produce something like:
126  #
127  #   <div>
128  #     <h1>David Heinemeier Hansson</h1>
129  #     <p>A product of Danish Design during the Winter of '79...</p>
130  #   </div>
131  #
132  # Here is a full-length RSS example actually used on Basecamp:
133  #
134  #   xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
135  #     xml.channel do
136  #       xml.title(@feed_title)
137  #       xml.link(@url)
138  #       xml.description "Basecamp: Recent items"
139  #       xml.language "en-us"
140  #       xml.ttl "40"
141  #
142  #       @recent_items.each do |item|
143  #         xml.item do
144  #           xml.title(item_title(item))
145  #           xml.description(item_description(item)) if item_description(item)
146  #           xml.pubDate(item_pubDate(item))
147  #           xml.guid(@person.firm.account.url + @recent_items.url(item))
148  #           xml.link(@person.firm.account.url + @recent_items.url(item))
149  #
150  #           xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
151  #         end
152  #       end
153  #     end
154  #   end
155  #
156  # For more information on Builder please consult the {source
157  # code}[https://github.com/jimweirich/builder].
158  class Base
159    include Helpers, ::ERB::Util, Context
160
161    # Specify the proc used to decorate input tags that refer to attributes with errors.
162    cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| content_tag :div, html_tag, class: "field_with_errors" }
163
164    # How to complete the streaming when an exception occurs.
165    # This is our best guess: first try to close the attribute, then the tag.
166    cattr_accessor :streaming_completion_on_exception, default: %("><script>window.location = "/500.html"</script></html>)
167
168    # Specify whether rendering within namespaced controllers should prefix
169    # the partial paths for ActiveModel objects with the namespace.
170    # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb)
171    class_attribute :prefix_partial_path_with_controller_namespace, default: true
172
173    # Specify default_formats that can be rendered.
174    cattr_accessor :default_formats
175
176    # Specify whether submit_tag should automatically disable on click
177    cattr_accessor :automatically_disable_submit_tag, default: true
178
179    # Annotate rendered view with file names
180    cattr_accessor :annotate_rendered_view_with_filenames, default: false
181
182    class_attribute :_routes
183    class_attribute :logger
184
185    class << self
186      delegate :erb_trim_mode=, to: "ActionView::Template::Handlers::ERB"
187
188      def cache_template_loading
189        ActionView::Resolver.caching?
190      end
191
192      def cache_template_loading=(value)
193        ActionView::Resolver.caching = value
194      end
195
196      def xss_safe? # :nodoc:
197        true
198      end
199
200      def with_empty_template_cache # :nodoc:
201        subclass = Class.new(self) {
202          # We can't implement these as self.class because subclasses will
203          # share the same template cache as superclasses, so "changed?" won't work
204          # correctly.
205          define_method(:compiled_method_container)           { subclass }
206          define_singleton_method(:compiled_method_container) { subclass }
207
208          def inspect
209            "#<ActionView::Base:#{'%#016x' % (object_id << 1)}>"
210          end
211        }
212      end
213
214      def changed?(other) # :nodoc:
215        compiled_method_container != other.compiled_method_container
216      end
217    end
218
219    attr_reader :view_renderer, :lookup_context
220    attr_internal :config, :assigns
221
222    delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context
223
224    def assign(new_assigns) # :nodoc:
225      @_assigns = new_assigns
226      new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
227    end
228
229    # :stopdoc:
230
231    def self.empty
232      with_view_paths([])
233    end
234
235    def self.with_view_paths(view_paths, assigns = {}, controller = nil)
236      with_context ActionView::LookupContext.new(view_paths), assigns, controller
237    end
238
239    def self.with_context(context, assigns = {}, controller = nil)
240      new context, assigns, controller
241    end
242
243    # :startdoc:
244
245    def initialize(lookup_context, assigns, controller) # :nodoc:
246      @_config = ActiveSupport::InheritableOptions.new
247
248      @lookup_context = lookup_context
249
250      @view_renderer = ActionView::Renderer.new @lookup_context
251      @current_template = nil
252
253      assign_controller(controller)
254      _prepare_context
255
256      super()
257
258      # Assigns must be called last to minimize the number of shapes
259      assign(assigns)
260    end
261
262    def _run(method, template, locals, buffer, add_to_stack: true, has_strict_locals: false, &block)
263      _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template
264      @current_template = template if add_to_stack
265      @output_buffer = buffer
266
267      if has_strict_locals
268        begin
269          public_send(method, buffer, **locals, &block)
270        rescue ArgumentError => argument_error
271          raise(
272            ArgumentError,
273            argument_error.
274              message.
275                gsub("unknown keyword:", "unknown local:").
276                gsub("missing keyword:", "missing local:").
277                gsub("no keywords accepted", "no locals accepted").
278                concat(" for #{@current_template.short_identifier}")
279          )
280        end
281      else
282        public_send(method, locals, buffer, &block)
283      end
284    ensure
285      @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
286    end
287
288    def compiled_method_container
289      raise NotImplementedError, <<~msg.squish
290        Subclasses of ActionView::Base must implement `compiled_method_container`
291        or use the class method `with_empty_template_cache` for constructing
292        an ActionView::Base subclass that has an empty cache.
293      msg
294    end
295
296    def in_rendering_context(options)
297      old_view_renderer  = @view_renderer
298      old_lookup_context = @lookup_context
299
300      if !lookup_context.html_fallback_for_js && options[:formats]
301        formats = Array(options[:formats])
302        if formats == [:js]
303          formats << :html
304        end
305        @lookup_context = lookup_context.with_prepended_formats(formats)
306        @view_renderer = ActionView::Renderer.new @lookup_context
307      end
308
309      yield @view_renderer
310    ensure
311      @view_renderer = old_view_renderer
312      @lookup_context = old_lookup_context
313    end
314
315    ActiveSupport.run_load_hooks(:action_view, self)
316  end
317end

ここからさらに探索的に別のファイル、コードを読む必要がありますので、気になった方はぜひこれらのヒントをもとにコードを深ぼってください。

まとめ

コントローラーとビューを具体例にローカル変数とインスタンス変数の違いを確認していきました。

  • ローカル変数はdef ~ endの中でしか使えない
  • インスタンス変数はコントローラー→ビューなど他のクラスでも使える

プログラミングについて聞いてみたい事がある方は以下から、質問をしてみてください。

ABOUT ME
いず
いず
1995年生まれ・愛知県出身・都心で二人暮らし。IT系PDM・プロコーチ・プログラミング学習サービス代表。趣味:ミニマリスト・コーヒー・デザイン
記事URLをコピーしました