{"id":370,"date":"2013-03-01T11:09:04","date_gmt":"2013-03-01T11:09:04","guid":{"rendered":"http:\/\/nicholshayes.co.uk\/blog\/?p=370"},"modified":"2013-10-31T15:56:11","modified_gmt":"2013-10-31T15:56:11","slug":"using-__line__-in-dynamic-methods","status":"publish","type":"post","link":"http:\/\/nicholshayes.co.uk\/blog\/?p=370","title":{"rendered":"Using __LINE__ in dynamic methods"},"content":{"rendered":"<p>I&#8217;ve been digging into active_support to see how <a href=\"https:\/\/github.com\/rails\/rails\/blob\/d677097eb6d49f75ef41dae2ee832d5e0a1d177d\/activesupport\/lib\/active_support\/configurable.rb#L54\"><em>config_accessor<\/em><\/a> was working. This is the code I found:<\/p>\n<div class=\"codecolorer-container ruby default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"ruby codecolorer\">&nbsp; &nbsp; &nbsp; <span class=\"kw1\">def<\/span> config_accessor<span class=\"br0\">&#40;<\/span><span class=\"sy0\">*<\/span>names<span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; options = names.<span class=\"me1\">extract_options<\/span>!<br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; names.<span class=\"me1\">each<\/span> <span class=\"kw1\">do<\/span> <span class=\"sy0\">|<\/span>name<span class=\"sy0\">|<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reader, line = <span class=\"st0\">&quot;def #{name}; config.#{name}; end&quot;<\/span>, <span class=\"kw2\">__LINE__<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; writer, line = <span class=\"st0\">&quot;def #{name}=(value); config.#{name} = value; end&quot;<\/span>, <span class=\"kw2\">__LINE__<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; singleton_class.<span class=\"me1\">class_eval<\/span> reader, <span class=\"kw2\">__FILE__<\/span>, line<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; singleton_class.<span class=\"me1\">class_eval<\/span> writer, <span class=\"kw2\">__FILE__<\/span>, line<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class_eval reader, <span class=\"kw2\">__FILE__<\/span>, line <span class=\"kw1\">unless<\/span> options<span class=\"br0\">&#91;<\/span><span class=\"re3\">:instance_reader<\/span><span class=\"br0\">&#93;<\/span> == <span class=\"kw2\">false<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class_eval writer, <span class=\"kw2\">__FILE__<\/span>, line <span class=\"kw1\">unless<\/span> options<span class=\"br0\">&#91;<\/span><span class=\"re3\">:instance_writer<\/span><span class=\"br0\">&#93;<\/span> == <span class=\"kw2\">false<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">end<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw1\">end<\/span><\/div><\/div>\n<p>What it is doing is fairly straight forward. It is creating setters and getters for attributes of a <em>config<\/em> object. The setter definition being held in the variable <em>writer<\/em> and the getter in <em>reader<\/em>.<\/p>\n<p>The thing that really piqued my interest, was the construction of <em>reader<\/em>, <em>writer<\/em> and <em>line<\/em>. In particular, what <em>line<\/em> was doing.<\/p>\n<p>After a little reading around I learnt that <em>line<\/em> is used to ensure the exception backtrace points to the place where the method is being defined. So for <em>reader<\/em>, the back trace will point to the place where the method was defined, and not to where <em>class_eval<\/em> was called.<\/p>\n<p>To help me get my head around this, I wrote this:<\/p>\n<div class=\"codecolorer-container ruby default\" style=\"overflow:auto;white-space:nowrap;height:300px;\"><div class=\"ruby codecolorer\"><span class=\"kw1\">module<\/span> LinePlay<br \/>\n<br \/>\n&nbsp; <span class=\"kw1\">def<\/span> <span class=\"kw2\">self<\/span>.<span class=\"me1\">raise_error_here<\/span><br \/>\n&nbsp; &nbsp; method, line = <span class=\"st0\">&quot;def self.raise_here; raise; end&quot;<\/span>, <span class=\"kw2\">__LINE__<\/span><br \/>\n&nbsp; &nbsp; make_into_method<span class=\"br0\">&#40;<\/span>method, line<span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; <span class=\"kw1\">end<\/span><br \/>\n<br \/>\n&nbsp; <span class=\"kw1\">def<\/span> <span class=\"kw2\">self<\/span>.<span class=\"me1\">raise_error_there<\/span><br \/>\n&nbsp; &nbsp; method, line = <span class=\"st0\">&quot;def self.raise_there; raise; end&quot;<\/span>, <span class=\"kw2\">__LINE__<\/span><br \/>\n&nbsp; &nbsp; make_into_method<span class=\"br0\">&#40;<\/span>method<span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; <span class=\"kw1\">end<\/span><br \/>\n<br \/>\n&nbsp; <span class=\"kw1\">def<\/span> <span class=\"kw2\">self<\/span>.<span class=\"me1\">make_into_method<\/span><span class=\"br0\">&#40;<\/span>method, line = <span class=\"kw2\">nil<\/span><span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">if<\/span> line<br \/>\n&nbsp; &nbsp; &nbsp; module_eval method, <span class=\"kw2\">__FILE__<\/span>, line<br \/>\n&nbsp; &nbsp; <span class=\"kw1\">else<\/span><br \/>\n&nbsp; &nbsp; &nbsp; module_eval method<br \/>\n&nbsp; &nbsp; <span class=\"kw1\">end<\/span><br \/>\n&nbsp; <span class=\"kw1\">end<\/span><br \/>\n<br \/>\n&nbsp; <span class=\"kw1\">def<\/span> <span class=\"kw2\">self<\/span>.<span class=\"me1\">rescue_error<\/span><span class=\"br0\">&#40;<\/span>method<span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">begin<\/span><br \/>\n&nbsp; &nbsp; &nbsp; send<span class=\"br0\">&#40;<\/span>method<span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">rescue<\/span> <span class=\"sy0\">=&gt;<\/span> e<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw3\">puts<\/span> <span class=\"st0\">&quot;<span class=\"es0\">\\n<\/span>Output for #{method}&quot;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw3\">puts<\/span> e.<span class=\"me1\">backtrace<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">end<\/span><br \/>\n&nbsp; <span class=\"kw1\">end<\/span><br \/>\n<br \/>\n&nbsp; raise_error_here<br \/>\n&nbsp; raise_error_there<br \/>\n<br \/>\n<span class=\"kw1\">end<\/span><br \/>\n<br \/>\nLinePlay.<span class=\"me1\">rescue_error<\/span><span class=\"br0\">&#40;<\/span><span class=\"re3\">:raise_here<\/span><span class=\"br0\">&#41;<\/span><br \/>\nLinePlay.<span class=\"me1\">rescue_error<\/span><span class=\"br0\">&#40;<\/span><span class=\"re3\">:raise_there<\/span><span class=\"br0\">&#41;<\/span><\/div><\/div>\n<p>This dynamically creates two methods via <em>module_eval<\/em>: <em>LinePlay.raise_here<\/em>, and <em>LinePlay.raise_there<\/em>. I called <em>module_eval<\/em> within <em>make_into_method<\/em>, to separate the definition method from the eval method. It then calls the two dynamically defined methods, rescues the exceptions and outputs the backtraces.<\/p>\n<p>This is the output:<\/p>\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"text codecolorer\">Output for raise_here<br \/>\nline_play.rb:4:in `raise_here'<br \/>\nline_play.rb:23:in `rescue_error'<br \/>\nline_play.rb:35:in `&lt;main&gt;'<br \/>\n<br \/>\nOutput for raise_there<br \/>\n(eval):1:in `raise_there'<br \/>\nline_play.rb:23:in `rescue_error'<br \/>\nline_play.rb:36:in `&lt;main&gt;'<\/div><\/div>\n<p>For <em>raise_here<\/em>, the line number where that method is defined, is passed to <em>module_eval<\/em>. If you look at the resulting backtrace, this shows line 4 as the source of the error. That is, the line where the method causing the error, was defined.<\/p>\n<p>For <em>raise_there<\/em>, __FILE__ and line number are not passed to <em>module_eval<\/em>. In this case the backtrace points to where class_eval was called and not where it was defined. <\/p>\n<p>So using this technique generates much more useful error reporting. A neat trick I think.<\/p>\n<p>However, have you spotted the error in the original code? The variable <em>line<\/em> for <em>reader<\/em> is overwritten on the next line when <em>writer<\/em> is assigned. That means backtraces for both <em>reader<\/em> and <em>writer<\/em> will point at the <em>writer<\/em> definition. This error has since been corrected. See this <a href=\"https:\/\/github.com\/rails\/rails\/commit\/008023c29a1dcc1b9f210dc117f8932bd708fb90#activesupport\/lib\/active_support\/configurable.rb\">commit<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve been digging into active_support to see how config_accessor was working. This is the code I found: &nbsp; &nbsp; &nbsp; def config_accessor&#40;*names&#41; &nbsp; &nbsp; &nbsp; &nbsp; options = names.extract_options! &nbsp; &nbsp; &nbsp; &nbsp; names.each do |name| &nbsp; &nbsp; &nbsp; &nbsp; &hellip; <a href=\"http:\/\/nicholshayes.co.uk\/blog\/?p=370\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[3],"tags":[],"_links":{"self":[{"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/370"}],"collection":[{"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=370"}],"version-history":[{"count":16,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/370\/revisions"}],"predecessor-version":[{"id":457,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/370\/revisions\/457"}],"wp:attachment":[{"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=370"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}