brick 1.0.190 → 1.0.192

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.
@@ -15,4 +15,10 @@ module ::Brick::Rails
15
15
 
16
16
  AA_PNG = "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAgCAYAAABNXxW6AAAMPmlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnluSkEBooUsJvQkiNYCUEFoA6V1UQhIglBgDQcVeFhVcu1jAhq6KKFhpFhRRLCyKvS8WVJR1sWBX3qSArvvK9873zb3//efMf86cO7cMAGonOCJRHqoOQL6wUBwbEkBPTkmlk54CBOgCGsAAgcMtEDGjoyMAtKHz3+3ddegN7YqDVOuf/f/VNHj8Ai4ASDTEGbwCbj7EhwDAK7kicSEARClvPqVQJMWwAS0xTBDiRVKcJceVUpwhx/tkPvGxLIjbAFBS4XDEWQCoXoI8vYibBTVU+yF2EvIEQgDU6BD75udP4kGcDrEN9BFBLNVnZPygk/U3zYxhTQ4naxjL5yIzpUBBgSiPM+3/LMf/tvw8yVAMK9hUssWhsdI5w7rdzJ0ULsUqEPcJMyKjINaE+IOAJ/OHGKVkS0IT5P6oIbeABWsGdCB24nECwyE2hDhYmBcZoeAzMgXBbIjhCkGnCgrZ8RDrQbyIXxAUp/DZIp4Uq4iF1meKWUwFf5YjlsWVxrovyU1gKvRfZ/PZCn1MtTg7PgliCsQWRYLESIhVIXYsyI0LV/iMKc5mRQ75iCWx0vwtII7lC0MC5PpYUaY4OFbhX5pfMDRfbEu2gB2pwAcKs+ND5fXB2rgcWf5wLtglvpCZMKTDL0iOGJoLjx8YJJ879owvTIhT6HwQFQbEysfiFFFetMIfN+PnhUh5M4hdC4riFGPxxEK4IOX6eKaoMDpenidenMMJi5bngy8HEYAFAgEdSGDLAJNADhB09jX0wSt5TzDgADHIAnzgoGCGRiTJeoTwGAeKwZ8Q8UHB8LgAWS8fFEH+6zArPzqATFlvkWxELngCcT4IB3nwWiIbJRyOlggeQ0bwj+gc2Lgw3zzYpP3/nh9ivzNMyEQoGMlQRLrakCcxiBhIDCUGE21xA9wX98Yj4NEfNmecgXsOzeO7P+EJoYvwkHCN0E24NVEwT/xTlmNBN9QPVtQi48da4FZQ0w0PwH2gOlTGdXAD4IC7wjhM3A9GdoMsS5G3tCr0n7T/NoMf7obCj+xERsm6ZH+yzc8jVe1U3YZVpLX+sT7yXDOG680a7vk5PuuH6vPgOfxnT2wRdhBrx05i57CjWAOgYy1YI9aBHZPi4dX1WLa6hqLFyvLJhTqCf8QburPSShY41Tj1On2R9xXyp0rf0YA1STRNLMjKLqQz4ReBT2cLuY4j6c5Ozi4ASL8v8tfXmxjZdwPR6fjOzf8DAJ+WwcHBI9+5sBYA9nvAx7/pO2fDgJ8OZQDONnEl4iI5h0sPBPiWUINPmj4wBubABs7HGbgDb+APgkAYiALxIAVMgNlnw3UuBlPADDAXlIAysBysARvAZrAN7AJ7wQHQAI6Ck+AMuAAugWvgDlw9PeAF6AfvwGcEQUgIFaEh+ogJYonYI84IA/FFgpAIJBZJQdKRLESISJAZyHykDFmJbEC2ItXIfqQJOYmcQ7qQW8gDpBd5jXxCMVQF1UKNUCt0FMpAmWg4Go+OR7PQyWgxugBdiq5Dq9A9aD16Er2AXkO70RfoAAYwZUwHM8UcMAbGwqKwVCwTE2OzsFKsHKvCarFmeJ+vYN1YH/YRJ+I0nI47wBUciifgXHwyPgtfgm/Ad+H1eBt+BX+A9+PfCFSCIcGe4EVgE5IJWYQphBJCOWEH4TDhNHyWegjviESiDtGa6AGfxRRiDnE6cQlxI7GOeILYRXxEHCCRSPoke5IPKYrEIRWSSkjrSXtILaTLpB7SByVlJRMlZ6VgpVQlodI8pXKl3UrHlS4rPVX6TFYnW5K9yFFkHnkaeRl5O7mZfJHcQ/5M0aBYU3wo8ZQcylzKOkot5TTlLuWNsrKymbKncoyyQHmO8jrlfcpnlR8of1TRVLFTYamkqUhUlqrsVDmhckvlDZVKtaL6U1OphdSl1GrqKep96gdVmqqjKluVpzpbtUK1XvWy6ks1spqlGlNtglqxWrnaQbWLan3qZHUrdZY6R32WeoV6k/oN9QENmsZojSiNfI0lGrs1zmk80yRpWmkGafI0F2hu0zyl+YiG0cxpLBqXNp+2nXaa1qNF1LLWYmvlaJVp7dXq1OrX1tR21U7UnqpdoX1Mu1sH07HSYevk6SzTOaBzXeeTrpEuU5evu1i3Vvey7nu9EXr+eny9Ur06vWt6n/Tp+kH6ufor9Bv07xngBnYGMQZTDDYZnDboG6E1wnsEd0TpiAMjbhuihnaGsYbTDbcZdhgOGBkbhRiJjNYbnTLqM9Yx9jfOMV5tfNy414Rm4msiMFlt0mLynK5NZ9Lz6OvobfR+U0PTUFOJ6VbTTtPPZtZmCWbzzOrM7plTzBnmmearzVvN+y1MLMZazLCosbhtSbZkWGZbrrVst3xvZW2VZLXQqsHqmbWeNdu62LrG+q4N1cbPZrJNlc1VW6ItwzbXdqPtJTvUzs0u267C7qI9au9uL7DfaN81kjDSc6RwZNXIGw4qDkyHIocahweOOo4RjvMcGxxfjrIYlTpqxaj2Ud+c3JzynLY73RmtOTps9LzRzaNfO9s5c50rnK+6UF2CXWa7NLq8crV35btucr3pRnMb67bQrdXtq7uHu9i91r3Xw8Ij3aPS4wZDixHNWMI460nwDPCc7XnU86OXu1eh1wGvv7wdvHO9d3s/G2M9hj9m+5hHPmY+HJ+tPt2+dN903y2+3X6mfhy/Kr+H/ub+PP8d/k+Ztswc5h7mywCnAHHA4YD3LC/WTNaJQCwwJLA0sDNIMyghaEPQ/WCz4KzgmuD+ELeQ6SEnQgmh4aErQm+wjdhcdjW7P8wjbGZYW7hKeFz4hvCHEXYR4ojmsejYsLGrxt6NtIwURjZEgSh21Kqoe9HW0ZOjj8QQY6JjKmKexI6OnRHbHkeLmxi3O+5dfED8svg7CTYJkoTWRLXEtMTqxPdJgUkrk7qTRyXPTL6QYpAiSGlMJaUmpu5IHRgXNG7NuJ40t7SStOvjrcdPHX9ugsGEvAnHJqpN5Ew8mE5IT0rfnf6FE8Wp4gxksDMqM/q5LO5a7gueP281r5fvw1/Jf5rpk7ky81mWT9aqrN5sv+zy7D4BS7BB8ConNGdzzvvcqNyduYN5SXl1+Ur56flNQk1hrrBtkvGkqZO6RPaiElH3ZK/Jayb3i8PFOwqQgvEFjYVa8Ee+Q2Ij+UXyoMi3qKLow5TEKQenakwVTu2YZjdt8bSnxcHFv03Hp3Ont84wnTF3xoOZzJlbZyGzMma1zjafvWB2z5yQObvmUubmzv19ntO8lfPezk+a37zAaMGcBY9+CfmlpkS1RFxyY6H3ws2L8EWCRZ2LXRavX/ytlFd6vsyprLzsyxLukvO/jv513a+DSzOXdi5zX7ZpOXG5cPn1FX4rdq3UWFm88tGqsavqV9NXl65+u2bimnPlruWb11LWStZ2r4tY17jeYv3y9V82ZG+4VhFQUVdpWLm48v1G3sbLm/w31W422ly2+dMWwZabW0O21ldZVZVvI24r2vZke+L29t8Yv1XvMNhRtuPrTuHO7l2xu9qqPaqrdxvuXlaD1khqevek7bm0N3BvY61D7dY6nbqyfWCfZN/z/en7rx8IP9B6kHGw9pDlocrDtMOl9Uj9tPr+huyG7saUxq6msKbWZu/mw0ccj+w8anq04pj2sWXHKccXHB9sKW4ZOCE60Xcy6+Sj1omtd04ln7raFtPWeTr89NkzwWdOtTPbW876nD16zutc03nG+YYL7hfqO9w6Dv/u9vvhTvfO+oseFxsveV5q7hrTdfyy3+WTVwKvnLnKvnrhWuS1rusJ12/eSLvRfZN389mtvFuvbhfd/nxnzl3C3dJ76vfK7xver/rD9o+6bvfuYw8CH3Q8jHt45xH30YvHBY+/9Cx4Qn1S/tTkafUz52dHe4N7Lz0f97znhejF576SPzX+rHxp8/LQX/5/dfQn9/e8Er8afL3kjf6bnW9d37YORA/cf5f/7vP70g/6H3Z9ZHxs/5T06ennKV9IX9Z9tf3a/C38293B/MFBEUfMkf0KYLChmZkAvN4JADUFABrcn1HGyfd/MkPke1YZAv8Jy/eIMnMHoBb+v8f0wb+bGwDs2w63X1BfLQ2AaCoA8Z4AdXEZbkN7Ndm+UmpEuA/Ywv6akZ8B/o3J95w/5P3zGUhVXcHP538Bjs98Nq8UJCYAAACEZVhJZk1NACoAAAAIAAYBBgADAAAAAQACAAABEgADAAAAAQABAAABGgAFAAAAAQAAAFYBGwAFAAAAAQAAAF4BKAADAAAAAQACAACHaQAEAAAAAQAAAGYAAAAAAAAASAAAAAEAAABIAAAAAQACoAIABAAAAAEAAABBoAMABAAAAAEAAAAgAAAAAMvlv6wAAAAJcEhZcwAACxMAAAsTAQCanBgAAAMXaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjE8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6UGhvdG9tZXRyaWNJbnRlcnByZXRhdGlvbj4yPC90aWZmOlBob3RvbWV0cmljSW50ZXJwcmV0YXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xMzM8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NjU8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KwTPR3wAAEI5JREFUaN7NWWlUVFe2hqgdTWumto1J1jJJx+6YzmiMGhChmGeReYZClBkEgaKKYrjUPFGMBRQgKCpPcYyJOEQkJpqoMSZpxbRPly5bgyadxGeMZjDxvG9f6tIlDzXa/ePVWleqzrDPPt/+zrf3uTo43MUnMjJyjP1vjuPuc/j3P44jfvM2CwoKgmNjY10c/j99hA2npaU9lJiYKI6JiZlJv0Ui0dh7tQlQfwd74wQbwl+0TUtKTLwSHx/P4uLi4v+DgN/7R2BAbm7u/dh8d3JyMkOUPk1KSppG7f7+/vffq017kAkQ+g7bk6Ojo/+enZ1N6xjsQHAchTn2jHK8C8bdG13hUHKyWMywebZo0SIGQBS2CI4fbSKBg409EBwczD9CpK1W6zjbvOmItgqAzBI2SkDTdzDgVTypWPMxGzvG2eyNE4vF4wVbBCb9pr/UR+yy9536aB6145kwEvy7ipjZbJ4QGBh4QKlUsJUrOn9GO0XpCByfKFDbLqpjRzhjH/EH6C9jbPKyZQX74TgDEK2380FgyJ3YNLLvVvNu1X5HFuAIBCIyTKNWfdK3e1dbRno6i4iIuL5kyZJQ26ITRmoENvcgaP0antl4XrQ/13K5rG7p0qUEAEtMTDgHVjWAXXUajboR4FrwuwnrrcUTQ+OhQ8/jew768vGEL1iwgGdISUnJQ1FRUbGwb0K/Go/XCNCnwrfEgICApWli8Uv2enRXYmijZ49YnMIARtK+ffsmweGvExISGOjWIgAmGIayT4CjxdjIWQKOHtosbQ7dfBQWLlx4ICYmlvqu0vFKSUlhmMfa21uZTXNYVlYW/W2j8dhkLo0jW7RueHh4ni2qLgCI0QNfGAB5R/A5PDwqGcfwAo4YKyosZGFhYSwgIEgLFt53N4y4z8aCZ2GcFRcVsY8/PvAytRkN+n1EZWz0aEZGxtP29ITDNdSHeQNwGl/TCsQ2LcH3uUPHy1SQl5f7K7UBjAOYG5eenp5SUSEPVSqrOtLT025QH0Aw2YD9E4C/Rn4QWE1NjTJboB7Oy8vbTxpFQIBd6QJo1FaQn/+NVFoc3dzcHFVdbTqZB0BCQkKMv+VI3cQCGC9DpmIVFeVf79q1fevO7dss7a3WM+QQRUaclJQljEWUXgEwLB7RKisre05oh9K/BrCWoO0Z+r1v37vucnnp5RQ4CodX2q+NNWILC5fxEReyA30KCwvVxKjFixczpVKZLbTn5GRZqA0Af0C/KWuFhoZeAchsy+ZNO69cuTLl8OHDkz/+6KBKq9UwVze3qwAugMaScN4RBKD+KEA4RSAQZcvK5KyysoIRonCepy4c29nT0zNmiILh2eQQx1UcP3/+/B+E1CrY7e3t5b9vf/ttX5m05AoxhI4atfX39/N6snFjT9oyRJsiC0CHo6ZWqx/HBi9Te3h4ZBW1SSSSJwDWRWJNfn5+ok2Lkul3fHzcD/nQnaV5eYw0jICltEv+Yd2+254BnJlhQczNyhWnpqYSlb+Pi0t4393ds9/b27d/QUjIHrSdpk1g0e9h2JvGh4VFVCLiTKNS/u3w4fcep7aOjo4/Go3G3+PrGKRHPjvs7O31kEiKL5FDgcHB3dR2oLf3Qfq7fv3adAEE4TgIegOKa8kfAPe+rX0W0R5AfCL4DL8W01z49TPsn4WNoxg/AJ8/WrgwtA9jj2Avq9H32B1ZQFHLyEg/aqvcSqjNYuEmIup8JkhPT3WNior+ChmCYeOdNsGLJWDg6IW2toZnRtru7OwcPxT1HdMRpaPkLFi1xX7MhnXrMgQQsMlq+7OLdabAn6t05IqLi5/F2noCAWvGCPPx252YgPnX62tq4m3N91OapxRNNcttawZ7FnR1dUZSVMGtH2tqapxGG4/N7kpMTGI5OdlfI2uQBozFMflnGoBZsiS1p76+3u2DvXud339/zysjBXdxauqHiAyr4ioGT588kf3FF1/wpfjOndtjUEPwGQLnvNw2Z6zgNAJSSkDn5mbvwRH9Cdr0iX16pg1i7n4KDvzrt1gss++qkhRAwCJTodYXKRqEdGZmxptC+hPEBHSKJp2geoGyAYD4lM4/QAghUSPaZmdnsTJ5KZPJpKQp72RmZj5il3bjaaNoY0ajgZXKpD/XVJv2FhTk/13IJtCc72Bn+oj7yzjM+4xSny0FzxmpPVRXYMwA9sDrAeydiIqO/pDSdkxMnPFOl0BHW4k8A4gO0iLkKCavs2OJoy0N0SYu0xgCKyoq5rCQLiMSxKKY+IQVsfHxxyKjo48hWidBY7kgoEJUk5Li/dG3XuThcQi5/NvuNatp8zfCwyOuwe4ZrLsdY4Us4yg4jXVfRt8H0QkJOSOKNPsyfzKeLLJBqRz2TiA4p4lJv+km7A9U48Tpf45JSJhPN0ZUaJNGXlTIABycBsMijHGmRR3sjpPdZ5J9lG5Bw/v7tm17CqnsJUqzVGnC5tO3o7B9dep/s/3/c5mi+w2xEMfzt132Zt1lbT3yE89xD7pIlSIXuTaSW73xjd9Si9zrpS6zaflzQIDfmAh3Fvsx93RZsokCv4C3RP+Ep0zjJFB3NIM2Joyxj4SoVP3ifKnq+BsSBfNT1bHS9lX//dmpU1P4KtGWVUZuyM4+gXIf2RUeW7QdRwMhQmGY71uuu+wmU20nf0cBwkGwIaxB3+8EPm/ctVz3kqdc86W7TP2TSKqWU9sLHHfbC8fw4iRa6poW7zId81I3MHnHmoETg4OTqatnYOB3Dv+ZD++nu0zj5qdvZgGmVuYh0yzhWWy7pf7b7w3cS9UbnCpNzK1MzzxK1Vf8OO3Q3eAOQMyT6HndWNbY+heRTP05OVje0X1AAIFDofS0mBs/0g4dP75thJ4MMa1nzK0iR+xzk6qLEawmH844ZYi1PWMiIb5OBeYJIwLnSIGKHBJmx9uCML9U/do8qYrJV65lkrZVbPoyjoUpq/lcTYZHOxL+ubzYDItRAYoSZ4niWFB1GyvrWHNIAEG4vfHOwhl/Eil7/SHKwnEO5fP0EUI6K8067k75feRR4IlptY7j/bPr48GI7BlzSxZ4yrUWJ87M2rZu72/c8ObZByQaFlxpOCikR/vJIw2Ja5qfjqiq9oxW18jBhPO+OgsrW77myGenLvJRKmrumuIl14W4SrUzb3oBwtVNC8ARHC0ywZz1gVttPNlQ/yw0KEYkUyWLpNrpw6nRZJrsW6ENdy5Sv3jTOmbzo4FS7SO3Ao03HqrR/MFVproUrm1gndv3hGm71y8OMrYwUO5aCGf0smeDIIjEBK8yXaabTH3w1WXcxefyK5iLXMfmSZTXfJS1JIzHz1y69DCNdSlR9c5Dnwf0Iq22rXBRdcsrs4u4TWDNtziCl1yl6v259e1zBgYGJjpJlIo5RVUnoEkn0XfKq1wXZb+uk0SV/1JhFXOXa5mPup652TRBxJkmzypS9LkgkNClS0vqWpcmGZtenlPI7XEqVpxzk6ouQEtWCEd3llVgmO3M+ZTpUz1U9Szd3PwPvv2x0ClJZitzKtUyvwoD//KEaCogmAs6Q51XemKz3gozy7Z0sJL21SzBaGHIDj8QCPL21ccOnTkzlcbnW9pVUdp65l5pZHH6hp8COROLMjaxKIOF+VRVszlyPUutbv6f1Oqmb4O1jSwBa3uh3Z3DA5B8ivhLGP/JMlheLWho/8azwsg8KgzMq1S9yHZuxi2tazUn1LSxeSVKlmSysEDOyML0FhZnauL9JH89S9WWUUXBS679cG65kVm3bBs88+Vg+nufHVVXdnazJ6ELQRWG07FA2Uaj8UOgaYLcgXiAopoVNHUo3xkYmDb43XeT69ZvCvEv057z1FpYRUf3R33Hjz9F4/ccOzYvr7H9spNMw3wBRIqpSVnS1TOtp//QVLHRstUTDHGBGC/kjN+Y124K6ezvf7hqRbcsGPbd0IeUHWTjMR+Elbv6GiKMzcwZm/WR65OFfWw/csSpsLWLPZNXxhYoTL8gKJUizjJxza7+V5Ur1m4OMlkZ2Hc2lKv+y00FS4SyRuQNx4DeT7GaWlZgXclyENmACv115xIV8yrXM79yg9j+LLnK1I1+RitbbLK8A834lxJncVPnFisGeGFcvuagAMLmDz8S5Ta0f+WO1Olbpu23D0Cs2hzlWqK6EQgdkbR1NQvtFy9enIKj+E9P1Bw4/5X2War+zbdros2tzAn+eZdrxcKc/+rb61xg7WLPF6tYEGfccdMtdkdfYnbrGjYjv/waBH/BTcVRmKpmraeiBrTSELLXXyus+uX1oqrrEEo6CkxUboDjuuH3dwv0+knQgQ/8kaNjVNV1Q3dYCX/OlG1dz/vKtSd9DC1IkWsOCSBsPHDAPaeh7UtvpM55JYqt9kcR60S/Uay8EYVN1W9+u0lYB3eNid5yzaA7fEs2NHbaJI7XhcYt2+qiq608CH52IKze8968ZQDhrxIV8y/XbRpqfYEHrumtHSnZLSvZC8VKsFsvG0ZH1tk9IwAbdUFqTDVa0iX65ZNm5HNPO8RlPtL+1ltzMmtbzr9eZiBWfJdssPgMRa72MQKBmJBW3bTBHu11u/cWJuob2Ey5ASmy+9O+I0Mg9Ow/6JFd3/aVt64JIKh6+ZxtCwKEL84JIERiU+YNbw6/gicd8CzVXJwLXSq2rjw/yNhwtqjd+FYDgUBMdS/VLhqO9u535xMTXgAIPuW6rcJVfIg9vUsyLZ3sZamGANIPO13U0tnurqxjIVWmk6NpxWJzS4gXxIlEKKOuZZPQjgi1e0CZQxXVVxBx/jZ34NixcOWK7m+n55f/6oE5ylXrPjl37esnqW/v0c+dCyzLL3jpWxgyxSZ7JgRz+oUQ01+jIWit23bV3qxVmvMzS9SsauXaSzh2w+LYuXO3gcY7QROiVDWxQvuev30+q7htNfszjkMoZ1wvXNDon1W7++NzwIS/lmjQZ+Jf0TkUNLfPidfV/zK7qIol6Ou/l7d0vc771tMzfMZ9yrWLvKAXLjgmMZraG9LWrgxqD0HtjnrgR09FLUOWuJpstHwRpaljsXAsQmX+1UWmZhk1zZcNa9fz7ClrX7U0QVf3oxspeqnm8zCV6SlhjaBKnXI+NMEf2WCpZTlSZT3/qi1MYZqNEv6H14sUN3Ial7PWzVv5VGns6vp9YVPHfj9kGGgTjqTZOvx/Gu1dEnGNlSFl34hSVp+WWzqchXclBZaO2lhkozlSNQnwlh7aJxY2ekCovGHMvaqGzZMqNTfVAxAhbPRdXyi9J4Cg+wCiuEtY0EOuCkX/PzxRZhObZhdyZ1SrezqRBc7OhdJThvAo03K2y9UOT8wnRvlA6NxkykyhGkQtcs4Px8QT7HGF/kTpan157VGYrF7QAz5zYI2ZhVwFtcdoG0RUjxDbfGELl7bByRLJJGx07ALOeNgFe/HBeFovuNK4l+YEamv+5CHXXBChjy53qImuQoxdHfxlmj+KZJpkOCHzkOuC/XENtinmcHXmzemfgMMpbqVqBdJUapDc/KR9xYhKbAKKlZTgKmM6GPQotT2Xwz2BQiYL+TuJijA+qqXqxz1KtWIUQJWw5S2kWh7MElUwtYvkGqUPp3MerlB1uodEUqUfHE72kWnm5treB/Blt1z/Bl3wRFJVjn3FGKkyP4kLVQpA56iiJP36VwWqmeEuVRegYJJ4lfOVq+P/Am9657pjUG9AAAAAAElFTkSuQmCC\">
17
17
  "
18
+
19
+ IN_APP = "<svg height=\"36px\" viewBox=\"0 0 214 274\" xmlns=\"http://www.w3.org/2000/svg\">
20
+ <g transform=\"matrix(4.16667,0,0,4.16667,-1049.47,-789.371)\">
21
+ <path d=\"M281.386,193.134L287.086,193.134C287.433,193.134 287.716,193.417 287.716,193.764C287.716,194.11 287.433,194.394 287.086,194.394L281.386,194.394C281.04,194.394 280.756,194.11 280.756,193.764C280.756,193.417 281.04,193.134 281.386,193.134ZM284.252,245.638C282.456,245.638 281.008,247.086 281.008,248.851C281.008,250.645 282.456,252.094 284.252,252.094C286.047,252.094 287.496,250.645 287.496,248.851C287.496,247.086 286.047,245.638 284.252,245.638ZM284.252,246.331C282.835,246.331 281.701,247.465 281.701,248.851C281.701,250.268 282.835,251.401 284.252,251.401C285.638,251.401 286.803,250.268 286.803,248.851C286.803,247.465 285.638,246.331 284.252,246.331ZM275.843,208.63L287.559,218.11L275.843,227.622L275.843,221.858L251.874,221.858L251.874,214.394L275.843,214.394L275.843,208.63ZM278.866,239.906L278.866,233.102C278.866,232.851 278.677,232.693 278.456,232.693L271.622,232.693C271.401,232.693 271.212,232.851 271.212,233.102L271.212,239.906C271.212,240.157 271.401,240.314 271.622,240.314L278.456,240.314C278.677,240.314 278.866,240.157 278.866,239.906ZM280.41,233.102L280.41,239.906C280.41,240.157 280.599,240.314 280.819,240.314L287.653,240.314C287.874,240.314 288.063,240.157 288.063,239.906L288.063,233.102C288.063,232.851 287.874,232.693 287.653,232.693L280.819,232.693C280.599,232.693 280.41,232.851 280.41,233.102ZM289.606,233.102L289.606,239.906C289.606,240.157 289.795,240.314 290.047,240.314L296.851,240.314C297.071,240.314 297.26,240.157 297.26,239.906L297.26,233.102C297.26,232.851 297.071,232.693 296.851,232.693L290.047,232.693C289.795,232.693 289.606,232.851 289.606,233.102ZM269.197,189.449L299.276,189.449C301.354,189.449 303.055,191.149 303.055,193.197L303.055,251.275C303.055,253.355 301.354,255.055 299.276,255.055L269.197,255.055C267.118,255.055 265.449,253.355 265.449,251.275L265.449,223.496L267.842,223.496L267.842,242.488L300.63,242.488L300.63,198.394L267.842,198.394L267.842,212.725L265.449,212.725L265.449,193.197C265.449,191.149 267.118,189.449 269.197,189.449Z\" style=\"fill:rgb(31,147,209);\"/>
22
+ </g>
23
+ </svg>"
18
24
  end
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brick
4
+ class << self
5
+ attr_accessor :routes_done
6
+ end
7
+
8
+ module RouteMapper
9
+ def add_brick_routes
10
+ routeset_to_use = ::Rails.application.routes
11
+ path_prefix = ::Brick.config.path_prefix
12
+ existing_controllers = routeset_to_use.routes.each_with_object({}) do |r, s|
13
+ if (r.verb == 'GET' || (r.verb.is_a?(Regexp) && r.verb.source == '^GET$')) &&
14
+ (controller_name = r.defaults[:controller])
15
+ path = r.path.ast.to_s
16
+ path = path[0..((path.index('(') || 0) - 1)]
17
+ # Skip adding this if it's the default_route_fallback set from the initializers/brick.rb file
18
+ next if "#{path}##{r.defaults[:action]}" == ::Brick.established_drf ||
19
+ # or not a GET request
20
+ [:index, :show, :new, :edit].exclude?(action = r.defaults[:action].to_sym)
21
+
22
+ # Attempt to backtrack to original
23
+ c_parts = controller_name.split('/')
24
+ while c_parts.length > 0
25
+ c_dotted = c_parts.join('.')
26
+ if (relation = ::Brick.relations.fetch(c_dotted, nil)) # Does it match up with an existing Brick table / resource name?
27
+ # puts path
28
+ # puts " #{c_dotted}##{r.defaults[:action]}"
29
+ if (route_name = r.name&.to_sym) != :root
30
+ relation[:existing][action] = route_name
31
+ else
32
+ relation[:existing][action] ||= path
33
+ end
34
+ s[c_dotted.tr('.', '/')] = nil
35
+ break
36
+ end
37
+ c_parts.shift
38
+ end
39
+ s[controller_name] = nil if c_parts.length.zero?
40
+ end
41
+ end
42
+
43
+ tables = []
44
+ views = []
45
+ table_class_length = 38 # Length of "Classes that can be built from tables:"
46
+ view_class_length = 37 # Length of "Classes that can be built from views:"
47
+
48
+ brick_namespace_create = lambda do |path_names, res_name, options|
49
+ if path_names&.present?
50
+ if (path_name = path_names.pop).is_a?(Array)
51
+ module_name = path_name[1]
52
+ path_name = path_name.first
53
+ end
54
+ send(:scope, { module: module_name || path_name, path: path_name, as: path_name }) do
55
+ brick_namespace_create.call(path_names, res_name, options)
56
+ end
57
+ else
58
+ send(:resources, res_name.to_sym, **options)
59
+ end
60
+ end
61
+
62
+ # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
63
+ # If auto-controllers and auto-models are both enabled then this makes sense:
64
+ controller_prefix = (path_prefix ? "#{path_prefix}/" : '')
65
+ sti_subclasses = ::Brick.config.sti_namespace_prefixes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
66
+ # Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
67
+ s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
68
+ end
69
+ versioned_views = {} # Track which views have already been done for each api_root
70
+ ::Brick.relations.each do |k, v|
71
+ next if k.is_a?(Symbol)
72
+
73
+ if (schema_name = v.fetch(:schema, nil))
74
+ schema_prefix = "#{schema_name}."
75
+ end
76
+
77
+ resource_name = v.fetch(:resource, nil)
78
+ next if !resource_name ||
79
+ existing_controllers.key?(
80
+ controller_prefix + (resource_name = "#{schema_prefix&.tr('.', '/')}#{resource_name}".pluralize)
81
+ )
82
+
83
+ object_name = k.split('.').last # Take off any first schema part
84
+
85
+ full_schema_prefix = if (full_aps = aps = v.fetch(:auto_prefixed_schema, nil))
86
+ aps = aps[0..-2] if aps[-1] == '_'
87
+ (schema_prefix&.dup || +'') << "#{aps}."
88
+ else
89
+ schema_prefix
90
+ end
91
+
92
+ # Track routes being built
93
+ resource_name = v.fetch(:resource, nil) || k
94
+ if (class_name = v.fetch(:class_name, nil))
95
+ if v.key?(:isView)
96
+ view_class_length = class_name.length if class_name.length > view_class_length
97
+ views
98
+ else
99
+ table_class_length = class_name.length if class_name.length > table_class_length
100
+ tables
101
+ end << [class_name, aps, "#{"#{schema_name}/" if schema_name}#{resource_name[full_aps&.length || 0 .. -1]}"]
102
+ end
103
+
104
+ options = {}
105
+ options[:only] = [:index, :show] if v.key?(:isView)
106
+
107
+ # First do the normal routes
108
+ prefixes = []
109
+ prefixes << [aps, v[:class_name]&.split('::')[-2]&.underscore] if aps
110
+ prefixes << schema_name if schema_name
111
+ prefixes << path_prefix if path_prefix
112
+ brick_namespace_create.call(prefixes, resource_name, options)
113
+ sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
114
+ brick_namespace_create.call(prefixes, sc.underscore.tr('/', '_').pluralize, options)
115
+ end
116
+
117
+ # Now the API routes if necessary
118
+ full_resource = nil
119
+ ::Brick.api_roots&.each do |api_root|
120
+ api_done_views = (versioned_views[api_root] ||= {})
121
+ found = nil
122
+ test_ver_num = nil
123
+ view_relation = nil
124
+ # If it's a view then see if there's a versioned one available by searching for resource names
125
+ # versioned with the closest number (equal to or less than) compared with our API version number.
126
+ if v.key?(:isView)
127
+ if (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
128
+ core_object_name = object_name[ver.length + 1..-1]
129
+ next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
130
+
131
+ # Expect that the last item in the path generally holds versioning information
132
+ api_ver = api_root.split('/')[-1]&.gsub('_', '.')
133
+ vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
134
+ # Was: .to_d
135
+ test_ver_num = api_ver_num = api_ver[vn_idx + 1..-1].gsub('_', '.').to_i # Attempt to turn something like "v3" into the decimal value 3
136
+ # puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
137
+
138
+ next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
139
+
140
+ test_ver_num -= 1 until test_ver_num.zero? ||
141
+ (view_relation = ::Brick.relations.fetch(
142
+ found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
143
+ ))
144
+ api_done_views[unversioned] = nil # Mark that for this API version this view is done
145
+
146
+ # puts "Found #{found}" if view_relation
147
+ # If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
148
+ # fall back to simply looking for "v_view_name", and then finally "view_name".
149
+ no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
150
+ standard_prefix = 'v_'
151
+ else
152
+ core_object_name = object_name
153
+ end
154
+ if (rvp = ::Brick.config.api_remove_view_prefix) && core_object_name.start_with?(rvp)
155
+ core_object_name.slice!(0, rvp.length)
156
+ end
157
+ no_prefix_name = "#{schema_prefix}#{core_object_name}"
158
+ unversioned = "#{schema_prefix}#{standard_prefix}#{::Brick.config.api_add_view_prefix}#{core_object_name}"
159
+ else
160
+ unversioned = k
161
+ end
162
+
163
+ view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
164
+ (no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil)) ||
165
+ (no_prefix_name && ::Brick.relations.fetch(found = no_prefix_name, nil))
166
+ if view_relation
167
+ actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
168
+ # Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
169
+ # Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
170
+ # these 3 things controls and changes the nature of the endpoint that gets built:
171
+ # (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
172
+ proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
173
+ begin
174
+ num_args = filter.arity.negative? ? 6 : filter.arity
175
+ filter.call(*[unversioned, k, view_relation, actions, api_ver_num, found, test_ver_num][0...num_args])
176
+ rescue StandardError => e
177
+ puts "::Brick.api_filter Proc error: #{e.message}"
178
+ end
179
+ end
180
+ # proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
181
+
182
+ case proc_result
183
+ when NilClass
184
+ # Do nothing differently than what normal behaviour would be
185
+ when FalseClass # Skip implementing this endpoint
186
+ view_relation[:api][api_ver_num] = nil
187
+ next
188
+ when Array # Did they give back an array of actions?
189
+ unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
190
+ proc_result = [unversioned, to_relation, proc_result]
191
+ end
192
+ # Otherwise don't change this array because it's probably legit
193
+ when String
194
+ proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
195
+ else
196
+ puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n #{proc_result.inspect}"
197
+ proc_result = nil # Couldn't understand what in the world was returned
198
+ end
199
+
200
+ if proc_result&.present?
201
+ if proc_result[1] # to_other_relation
202
+ if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
203
+ k = proc_result[1] # Route this call over to this different relation
204
+ view_relation = new_view_relation
205
+ else
206
+ puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
207
+ end
208
+ end
209
+ if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
210
+ found = proc_result.first
211
+ end
212
+ actions &= proc_result[2] if proc_result[2] # allowed_actions
213
+ end
214
+ (view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
215
+
216
+ # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
217
+ # first_part[1..-1].gsub('_', '.').to_i
218
+ # end
219
+ controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
220
+ "#{full_schema_prefix}#{last}"
221
+ else
222
+ found
223
+ end.tr('.', '/')
224
+
225
+ { :index => 'get', :create => 'post' }.each do |action, method|
226
+ if actions.include?(action)
227
+ # Normally goes to something like: /api/v1/employees
228
+ send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
229
+ end
230
+ end
231
+ # %%% We do not yet surface the #show action
232
+ if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
233
+ { :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
234
+ if actions.include?(action)
235
+ methods.each do |method|
236
+ send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ # Trestle compatibility
245
+ if Object.const_defined?('Trestle') && ::Trestle.config.options&.key?(:site_title) &&
246
+ !Object.const_defined?("#{(res_name = resource_name.tr('/', '_')).camelize}Admin")
247
+ begin
248
+ ::Trestle.resource(res_sym = res_name.to_sym, model: class_name&.constantize) do
249
+ menu { item res_sym, icon: "fa fa-star" }
250
+ end
251
+ rescue
252
+ end
253
+ end
254
+ end
255
+
256
+ if (named_routes = instance_variable_get(:@set).named_routes).respond_to?(:find)
257
+ if ::Brick.config.add_status && (status_as = "#{controller_prefix.tr('/', '_')}brick_status".to_sym)
258
+ (
259
+ !(status_route = instance_variable_get(:@set).named_routes.find { |route| route.first == status_as }&.last) ||
260
+ !status_route.ast.to_s.include?("/#{controller_prefix}brick_status/")
261
+ )
262
+ get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: status_as.to_s)
263
+ end
264
+
265
+ # ::Brick.config.add_schema &&
266
+ if (schema_as = "#{controller_prefix.tr('/', '_')}brick_schema".to_sym)
267
+ (
268
+ !(schema_route = instance_variable_get(:@set).named_routes.find { |route| route.first == schema_as }&.last) ||
269
+ !schema_route.ast.to_s.include?("/#{controller_prefix}brick_schema/")
270
+ )
271
+ post("/#{controller_prefix}brick_schema", to: 'brick_gem#schema_create', as: schema_as.to_s)
272
+ end
273
+
274
+ if ::Brick.config.add_orphans && (orphans_as = "#{controller_prefix.tr('/', '_')}brick_orphans".to_sym)
275
+ (
276
+ !(orphans_route = instance_variable_get(:@set).named_routes.find { |route| route.first == orphans_as }&.last) ||
277
+ !orphans_route.ast.to_s.include?("/#{controller_prefix}brick_orphans/")
278
+ )
279
+ get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
280
+ end
281
+ end
282
+
283
+ if instance_variable_get(:@set).named_routes.names.exclude?(:brick_crosstab)
284
+ get("/#{controller_prefix}brick_crosstab", to: 'brick_gem#crosstab', as: 'brick_crosstab')
285
+ get("/#{controller_prefix}brick_crosstab/data", to: 'brick_gem#crosstab_data')
286
+ end
287
+
288
+ if ((rswag_ui_present = Object.const_defined?('Rswag::Ui')) &&
289
+ (rswag_path = routeset_to_use.routes.find { |r| r.app.app == ::Rswag::Ui::Engine }
290
+ &.instance_variable_get(:@path_formatter)
291
+ &.instance_variable_get(:@parts)&.join) &&
292
+ (doc_endpoints = ::Rswag::Ui.config.config_object[:urls])) ||
293
+ (doc_endpoints = ::Brick.instance_variable_get(:@swagger_endpoints))
294
+ last_endpoint_parts = nil
295
+ doc_endpoints.each do |doc_endpoint|
296
+ puts "Mounting OpenApi 3.0 documentation endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}" unless ::Brick.routes_done
297
+ send(:get, doc_endpoint[:url], { to: 'brick_openapi#index' })
298
+ endpoint_parts = doc_endpoint[:url]&.split('/')
299
+ last_endpoint_parts = endpoint_parts
300
+ end
301
+ end
302
+ return if ::Brick.routes_done
303
+
304
+ if doc_endpoints.present?
305
+ if rswag_ui_present
306
+ if rswag_path
307
+ puts "API documentation now available when navigating to: /#{last_endpoint_parts&.find(&:present?)}/index.html"
308
+ else
309
+ puts "In order to make documentation available you can put this into your routes.rb:"
310
+ puts " mount Rswag::Ui::Engine => '/#{last_endpoint_parts&.find(&:present?) || 'api-docs'}'"
311
+ end
312
+ else
313
+ puts "Having this exposed, one easy way to leverage this to create HTML-based API documentation is to use Scalar.
314
+ It will jump to life when you put these two lines into a view template or other HTML resource:
315
+ <script id=\"api-reference\" data-url=\"#{last_endpoint_parts.join('/')}\"></script>
316
+ <script src=\"https://cdn.jsdelivr.net/@scalar/api-reference\"></script>
317
+ Alternatively you can add the rswag-ui gem."
318
+ end
319
+ elsif rswag_ui_present
320
+ sample_path = rswag_path || '/api-docs'
321
+ puts
322
+ puts "Brick: rswag-ui gem detected -- to make OpenAPI 3.0 documentation available from a path such as '#{sample_path}/v1/swagger.json',"
323
+ puts ' put code such as this in an initializer:'
324
+ puts ' Rswag::Ui.configure do |config|'
325
+ puts " config.swagger_endpoint '#{sample_path}/v1/swagger.json', 'API V1 Docs'"
326
+ puts ' end'
327
+ unless rswag_path
328
+ puts
329
+ puts ' and put this into your routes.rb:'
330
+ puts " mount Rswag::Ui::Engine => '/api-docs'"
331
+ end
332
+ end
333
+
334
+ puts "\n" if tables.present? || views.present?
335
+ if tables.present?
336
+ puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
337
+ puts "======================================#{' ' * (table_class_length - 38)} ====="
338
+ ::Brick.display_classes(controller_prefix, tables, table_class_length)
339
+ end
340
+ if views.present?
341
+ puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
342
+ puts "=====================================#{' ' * (view_class_length - 37)} ====="
343
+ ::Brick.display_classes(controller_prefix, views, view_class_length)
344
+ end
345
+ ::Brick.routes_done = true
346
+ end
347
+ end
348
+ end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 190
8
+ TINY = 192
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil