📜 ⬆️ ⬇️

Apply Inheritance to Mason Templates

Mason is a fairly well-known and widely used framework for developing Perl applications. Despite the simple syntax, it has quite wide capabilities and high performance; It has built-in tools for integration with mod_perl and memcached. Mastering the work with mason is very simple - the system is well documented. At the same time, there are a number of interesting subtleties, to which not everyone reads the documentation. Perhaps that is why the code that I have ever seen was painfully reminiscent of an unsuccessful hack in PHP, in which the developer did not use anything more difficult to divide into easier people to simplify life. Is it possible to write better on mason? Yes, I think so.

Disclaimer

In this article, I do not plan to discover America and describe something that greatly goes beyond the known documentation . Rather, it will be a story about simple but effective practical solutions that successfully work on several projects. I also will not write an introductory “mason for dummies” - those who have already seen Masonic components, it is unlikely to be needed, and the rest can either figure it out on the spot, like real cool peppers, or spend a quarter of an hour smoking a manual, if nothing else does not help :) In terms of terminology, I will also try not to deviate from the original documentation. For example, I will call mason templates as components.

Spherical knee creativity in a vacuum

Suppose we have a set of simple pages with content. When accessing the page, it is checked whether the current user has access to it; if not, send it to the identification. Naturally, the content is inscribed in the standard design of the site, and the author has taken care to bring the common parts to the viewers, and the code is in the function. So, an example of the company.html file:
 <% INIT>
 my $ user = GetUserFromCookies ();
 $ m-> redirect ('/ login.html') unless $ user && $ user-> haveAccess ($ r-> uri);
 </% INIT>

 <& /Header.inc, title => "About site" &>
 <h1> About Site </ h1>
 <p> Our site is the most rulezny in RuNet! </ p>
 <& /Footer.inc &>

Familiar approach? In principle, there is nothing bad in this example. Is that annoying to drag in almost all components of almost identical inclusions to display the design, and verification of authorization requires that it copy-paste from page to page. What can be done? Just remove too much :)
')

Theoretical foundations of inheritance

Feature # 1: parent components

Each component mason can be inherited from any other component. Such a component can be set (or reset) forcibly using the <%FLAGS> section and a flag named inherit , or it can be calculated automatically. In the second case, in the folder where the component is located, a file with the name autohandler , and if it is not there, the search continues with the folder above, and so on - until the folder that mason considers to be the root. If an ancestor is found, the mason tries to find a parent for it.

Once the inheritance chain is built, the execution is transferred to the “most parent” component, which can perform some actions and transfer control to its descendant — using the magic call $m->call_next (the magic is exposed in the documentation). After the child has completed the execution of the parent component continues.

The above procedure for building the chain of inheritance works only when the page is accessed; When other components are included, the mechanism does not turn on.

Feature # 2: Attributes and Methods

With attributes, everything is simple: in the <%ATTR> section of the component, you can <%ATTR> an arbitrary set of named parameters. If the parent components have attributes that are not directly declared in the current component, they are inherited.

With methods, the situation is almost as simple: each method is wrapped in a <%METHOD> section and is a “component in a component” that can be called in much the same way as connectors. Like conventional components, methods can be used as a template (for data output), and as a function (for obtaining the result of calculations).

Practical Basis of Inheritance

So, we will try to refine pages with the help of these two features. It is logical to drag off the cap and tail in /autohandler . The only "but" - in the header displays the name of the page (in the title tag). It can be safely put in the attributes of the page, if we can get to them from the autohandler (and looking ahead, I will say yes, we can). Now we only have the duplicated <%INIT> section with the authorization check. What if authorization is not needed on all pages? An excellent and completely secure way to solve this problem is to entrust the page itself to control the verification. Let's get the authorize attribute, let the authorization be checked on all pages where it is set to the true value (for definiteness, we assume that we have to check all pages except /profile/login.html ). The results of our thinking:

/ autohandler:
 <% INIT>
 my $ component_requested = $ m-> request_comp;  # company.html component
 if ($ component_requested-> attr ('authorize'))
 {
     my $ user = GetUserFromCookies ();
     $ m-> redirect ('/ login.html') unless $ user && $ user-> haveAccess ($ r-> uri);
 }
 </% INIT>

 <% ATTR>
 authorize => 1
 </% ATTR>

 <& /Header.inc, title => $ component_requested-> attr ('title') &>
 <h1> <% $ component_requested-> attr ('title')%> </ h1>
 % $ m-> call_next;
 <& /Footer.inc &>

/company.html:
 <% ATTR>
 title => "About site"
 </% ATTR>

 <p> Our site is the most rulezny in RuNet! </ p>

/profile/login.html:
 <% ATTR>
 title => "Login to site"
 authorize => 0
 </% ATTR>

 <p> There could be a login form </ p>

Some simple conclusions, not obvious for a simple example:
  1. Attributes it is convenient to store information about which menu item is current and highlight it.
  2. In the above code, Header.inc accepts the title of the page as an argument, but in principle no one bothers to get to it from the header itself using exactly the same code.
  3. If you correctly decompose the pages into folders, you can create an additional level of autohandler in them. This will allow more flexible management of attributes (for example, you can open access to the entire contents of the /free/ folder by moving the authorize attribute to the /free/autohandler ). You can also modify the content of the page (for example, add a top-level heading or a block with an EULA under the main text of the page).

Complicate the task

Suppose we have a certain block that can change from page to page. For example, on the whole site in the right column shows the news section, but on the pages of the user profile shows the obligation to keep private data in secret. Obviously, inheritance will also help here, but this time it is much more convenient to solve the problem on methods. In /autohandler write the default method to display the news (think up the implementation yourself), and in /profile/autohandler , the text that overrides it:
 <% METHOD rightmenu>
 <p> We will not give the data to anyone! </ p>
 </% METHOD>

Now suppose that on some (not all) pages we have a submenu block that is dynamically assembled, and in different sections of the site the submenu is formed differently. For example, on pages with content it can be taken from the CMS database, and in the section “my profile” a static, hand-shaped menu. Naturally, we don’t want to mix menu data with its presentation, so the SubMenu.inc component will deal with the SubMenu.inc , and the structure that describes the menu itself will be passed to it as a parameter. Question: where to put the structure to use the inheritance feature? Answer: in the component method. The following are fragments that are proposed to be added to the appropriate files.
/ autohandler:
 % my $ component_requested = $ m-> request_comp;
 % if ($ component_requested-> method_exists ('submenu')) {
     <& /SubMenu.inc, MenuItems => $ component_requested-> call_method ('submenu') &>
 %}

/ profile / autohandler:
 <% METHOD submenu>
     <% INIT>
     return [
         '/profile/password.html' => 'Change Password',
         '/profile/update.html' => 'Refresh personal data',
         '/profile/logout.html' => 'Logout'
     ];
     </% INIT>
 </% METHOD>

Instead of epilogue

The above techniques solve several simple, but very useful tasks. The removal of common code fragments in autohandler 's significantly reduces the amount of non-informative code in the templates. Attributes allow you to move from writing code to its configuration. Methods are ideal for describing visual blocks on a page, and allow flexible linking of code with components.

The article shows only ideas, and they can be easily developed. For example, how would you solve the problem if you needed to make a component that can render a CSV file instead of showing the page? Great, and if the page, depending on the wishes of the user, gives out either CSV or HTML in the standard site design? And weakly make an application with skins and the ability to connect different skins in different pages? By the way, not as simple as it seems at first glance. Nevertheless, try :)

Source: https://habr.com/ru/post/54290/


All Articles