text:
Early Childhood Recollections
Give Them Music
Arts for World Unity Statement
"Early Childhood Recollections." In Elena Fitzhum and Primavera Gruber, eds., Give Them Music: Musiktherapie im Exil Am Beispiel von Vally Weigl. Wiener Beiträge zur Musiktherapie 6. Vienna: Praesens, 2003, 185–95.
by Vally Weigl
Just about thirty years ago when, prompted by a severe accident after my husband's death, I took an M.A. in Rehabilitation and Recreation at Columbia University, I enrolled in a course in "Psychology of Family Relations" whose very fine instructor, Dr. Osborne, had us write down early childhood recollections. I dug these up in 1973, adding a few memories of later years, for a friend whom I met in the Peace movement. This friend recently convinced me that my grandchildren, and eventually my great-granddaughter, might be interested in these recollections as part of their "Roots".
I grew up in a turn-of-the-century good bourgeois intellectual family milieu in Vienna together with my by only 11 months younger sister Käthe who grew ever closer to our father whom we both adored; especially when at night time he would sit at our bedside and tell us about Homer, Schiller and Wagner and his travels to Italy, Greece and Africa as a young newspaper reporter before he passed the bar and became a well-known character among Austrian lawyers, a "Hof- und Gerichts-Advokat", as the official title was. We were fascinated when he spoke of Galileo, of Benjamin Franklin, the invention of the light'ning rod and lots of other things he got us interested in when we were only 4 and 5 years old. He was renowned for his wit, his short-sightedness and the candies he always had in his pockets, bulging with lots of fascinating things he collected and brought home from Viennese antique shops on his way home from court. He just could not pass them by without digging up now an ivory bishop's cane or a nodding Chinese Buddha, some rare French miniature or books or similar discoveries. My mother, who was 22 years younger than he, often reproached him [for] his lack of concern for outward appearance and decor which meant much more to her, a weakness which we children soon discovered and against which we rebelled more and more as time went on. I recall the Friday afternoon receptions ("jours") for respected society ladies and relatives for which Käthe and I had to appear hand in hand, identically impeccably dressed to recite some little verse one of which until today has stuck in my memory: "Mein liebes Schwesterchen und ich, wir lieben uns recht inniglich" (My darling sister and I we love each other tenderly") while, just because we had to say so, we were squeezing or punching each other's hands until with much relief we could take refuge in our joint nursery, having retrieved some delicious tid-bits or cake which had taken our mother hours to prepare. She fixed these personally, even though in those early days we still had a cook, a chamber-maid and a charwoman for the heavy house work and for cleaning our 5-room apartment and the adjac[ent] "Kanzlei", our father's 3-room office. Gradually that number of household help and the family wealth disappeared during World War I and the following depression, but for quite some time mother still clung to the "facade", the symbols of the respectable bourgeois intellectual family. When at the age of 18 I insisted on earning some money by giving music lessons, mother was still objecting to it as not in keeping with the status of a "Hof & Gerichts Advokat's" daughter and I had to fight for every step toward independence from those society rules which I rejected by then more and more. When Käthe later followed suit, her emancipation from those rules was by far easier since the parents had gotten more resigned to it and resented it much less.
In my very early years I had most of the then still rather virulent children's diseases: diphtheria, [w]hooping cough, Chicken pox etc. and at the age of 6½ after a serious case of measles retained a severe eye infection. I had to remain for 3 weeks in a darkened room and to be taken out of school. My parents engaged then a very kindly private teacher whom I can still see before me and who was to teach me reading, so I wouldn't lose a school year.
In order to make better use of his services and also because we were supposed to share in everything we did, Käthe was to take part in these lessons. I later often wished my parents had not insisted on this economy-equalization measure; for the 5½ year old Käthe, sitting opposite me at the table learned to read even upside down faster than I with my weakened eyes could manage, —a deficiency which has remained with me all my life and has caused me much grief and feelings of rejection. For papa, delighted with his younger daughter's early reading achievements—she became a real "bookworm", devouring all the interesting books in his extensive library—always teased me and made fun of my slow reading tempo, duly seconded by Käthe, probably neither of them realizing how badly and helpless I felt about it. I have been in treatment by oculists, got reading glasses which did not remedy the easy tiring of my eyes, even (50 years afterwards!) took a "speed-reading course" at Columbia University in 1952 without much avail, and I realized only then that probably a "reading block" had been added to that eye deficiency which even today prevents me from catching up with texts on TV or in cinemas before they disappear or from finishing the main sections of the Sunday New York Times before the following Saturday.
From those early reading results dated the permanent labeling we had to hear over and over again: "Vally is the prettier, Käthe the more clever one" which made either of us feel unhappy and frustrated and laid the foundation to an unfortunate, lasting siblings' rivalry, which prevented us from seeing each other as we really were and from recognizing each other's strength and good qualities. I have often regarded the mutual inability to overcome these early feelings towards each other as a failure and great loss for both of us, since I secretly gradually learned to admire her early awakened sense of social conscience which led her to the Youth and Labor Movement while mine a few years later brought me into the Anti-War camp.
At that early stage, however, I hardly was aware that Käthe, too, must have suffered from that "label"; for whenever Papa realized it, he came to her rescue and comforted her, especially since he, too, was often blamed for his lack of outward appearance, while no one even imagined that I was not happy about "just being pretty and graceful." So I rebelled against both and became the "black sheep" of the family, probably behaving at times like a brat, while Käthe, then still much more docile, was always regarded as the good child ("das brave Kind"), and later, when I had led the way and taken the brunt of the scolding, was allowed to go her own way, too.
But all through primary school and the respectable all-girl "Mädchenlyceum für Beamtentöchter", to which we were sent, we were dressed alike and had to attend the same gym-, dancing classes, "Kränzchen" (House balls) etc. in which I, having a little better body coordination, could excel, but whose superficiality I, too, felt strongly. So I abandoned all that "social glamor" at 17, as soon as I finished the Lyceum and, much to mama's disappointment was ever since only interested in the outdoors, mountain climbing, hiking, skiing, in rough, simple tourist clothing which I could afford myself from savings or early earnings in giving music lessons; all this still considered rather "revolutionary" and unconventional at that time.
Fortunately I met just then two wonderful, life-long friends, the beautiful, sensitive Fini Kohn (later Barany and a gifted photographer) with whom I started teaching in an orphanage as a volunteer; and whom (as well as later her children) I taught the piano; and Toni Bächer-Maar, the inspiring, rather masculine geologist-historian, about to become a revered high-school professor, with both of whom I took long hikes and skiing trips, enriched by long, stimulating discussions about everything one could imagine. Sometimes we spent even a whole weekend out and, to my parents' disgust were even joined by some of our (really very "platonic") male colleagues from Vienna University whose Musicological Institute and Philosophy faculty dep[artment] I entered in 1912, after my Lyceal-Matura and where I later became assistant to Prof. W. Fischer, playing in his classes and preparing students for the entrance examination to the Musicological Institute.
From 1915 on we were joined on these wonderful mountain trips by Dr. Karl Weigl, my composition teacher to whom I was recommended by the Musicological Institute in the Fall of 1914, whom I admired more and more for his music as well as his strong individuality, integrity, exceptional sense of humor and indomitable love of the mountains and to whom I got married 6 years later in 1921.
Only Hitler and the ensuing emigration in 1938 separated us from these dear friends; Fini Barany and family went to Sweden; we with our 12 year old son, Wolfgang, to America from were we vainly tried to get Toni and her husband, Dr. Oscar Maar, to come over, too; threatened with separation from her "Aryan" husband, she ended her life in the early years of the war. Unfortunately our efforts to get Käthe out (who as a noted socialist and as a hostage for her prominent social-democratic husband, Dr. Otto Leichter, had been sent to the Nazi concentration camp in Ravensbrück) were just as futile; but at least our Quaker Friends in Philadelphia made it easier for her husband and two sons to adjust to their new home country when at last after very trying escape from France on a special permit from President Roosevelt they and other Austrian political émigrés on the Nazis' "special list" were allowed to enter this country without final visas which they obtained via Canada later on. Especially one of our Quaker friends, Carolyn Norment, was their hostess in a Home near Haverford, Pa. and took a deep interest in them, esp. little Franz; also at the time when they learned of Käthe's tragic death in Ravensbrück.
But all this happened much, much later and I wanted to say more about those early childhood days. There were periods in which the rivalry between the two of us was at a minimum, especially during summer trips when Papa was eager to show my mother, Käthe, me and a motherless cousin the wonders of the world; the art treasures of Verona, Venice, Pisa, Ravenna, Munich etc. and the mountains of the Salzkammergut, the Tyrol, the Dolomites and Switzerland. Then he was really "in his element" and he did impart his "Wanderlust" to both of us; in spite of my hobbling on a cane for the last 2 years I still am longing for the "wonders of the world" and the mountains and grasping any opportunity to see them again—. Another field in which we shared love and interest without feeling threatened by competition were Papa's birthdays when the two of us "surprised him" by waking him up with his favorite selections from Haydn, Schubert or Mendelssohn on Violin and Piano or some piano duets, later also some other chamber music, some Bach or Beethoven.
Mama had been quite a good pianist herself and was both surprised and pleased when one day at the age of 5 she heard me play by ear on the piano whatever I had heard her play before. Thereupon it was decided that we both were to start piano lessons with a Leschetitzki pupil, Miss Bertha Löw. But my mother sat usually in on these lessons and involved our teacher in conversation about lots of other unconnected matters. It may have been this or the not very inspiring personality of Miss Löw, that at the age of 12 when one of my colleagues at the Lyceum was looking for some one to share in painting lessons, I signed up and wasted 2 or 3 years which I could have much better used for building a sound technical foundation on the piano. (At that time, when asked what I wanted to be later on, I used to reply "Malerin, Musikerin und Mutter" (a painter, musician and mother); but at the age of 14 or 15 I knew music was to be my destination; I took lessons with Prof. Wolfsohn, passed the Music Teachers State Exam 1913 and then started studying with Prof. Richard Robert among whose prize pupils were then also Rudi Serkin and George Szell. I later became his assistant in preparing his students for the Austrian State Teachers Exam, especially in theory and harmony which (as well as counterpoint and composition) I had started studying with Dr. Karl Weigl. At that time Papa presented me with a Bechstein baby grand piano which has been my cherished companion ever since and still takes up the main space of my bedroom in New York. — Unfortunately in our parental home in Vienna it stood in the family living room and since "Musik wird störend oft empfunden, dieweil sie mit Geräusch verbunden" (Music often disturbs by making noise), my practicing there must often have been a nuisance for the rest of the family, while Käthe's studies for her Ph.D. in National Economy were silent and did not chase anybody out of the room. Papa was extremely proud of Käthe's taking her Doctorate in Heidelberg, since no such degree existed for women in Austria then; all the greater was his disappointment when, after having been involved in anti-war activates in German munitions factories, she was not allowed to continue her studies there; she only could do so and get her degree after an amnesty at the end of the war, in 1918.
By that time Papa's work as a lawyer had shrunk considerably and he let Käthe move into one room of his office which reduced the frictions of sharing out old "children's room", on the other hand encouraged our going our own separate ways, she with her social-democratic friends, I with mine. But at least we did no longer as in earlier years have to go along as "Gardedame" or "duenna" whenever one of us wanted to go out for even ½ hour with any male friend or acquaintance, which certainly had not improved our feelings toward each other. The new set-up helped us feel relieved by a last no longer having to live under "the sister's watchful eye" and by developing our own personalities. This desire shut us off from each other and inhibited communication until it was too late to really get to know and do justice to one another.
Not long after Käthe's return from Heidelberg as Dr. Käthe Pick, I too left Vienna in 1920 in the midst of the post-war depression for taking a job as a 4 language interpreter and secretary to Edo Fimmen, Secretary General of the International Transport Workers Union in Amsterdam, Holland. (I had been hoping for work in the field of music, but since that was hard to find, I was glad to have obtained this one, especially since Fimmen was a very interesting man who at that time was trying to stop all war, by having all transport workers everywhere refuse to handle and transport any war materials,—a fascinating idea which for a brief time seemed to work at least in Europe, but failed as soon as Mussolini and Fascism broke the workers' resistance.
Other reasons for my taking that job in Holland were that during that starvation period to overcome the general sense of hopelessness and frustration of that time in Austria it enabled me to send small food parcels to Papa and Prof. Robert; also to save some money in case my absence—and this was, of course, the primary reason—might convince both, Karl Weigl and myself, that we could and would no longer live without each other. After 15 months we felt pretty sure of that and, when I returned in the fall of 1921 he waited for me at the railroad station in Rekawinkel from were we took a lovely hike and decided to get married on December 27th, taking over my father's offices and my mother's adjacent "parlor" as our apartment. Käthe married Otto Leichter at about the same time and got our grandmother's nearby apartment who moved in with our mother. My Amsterdam savings, however, provided us with not only the "emergency backlog" for our marriage but also, since it was not used up, with one 16½ years later to start life anew in the U.S. The night after the "Anschluss" I transferred it to the father of one of our students in Switzerland who later could send it to us in New York, so we had then not only the 10.oo-Mark we could take along in leaving Vienna and felt not quite as lost and insecure: also we could rely on ourselves during my husband's illness.
This tendency to save without really being interested in money or even knowing what I have, is one of the traits and habits inherited from Papa. We were only 10 and 11 years old when he handed each of us a savings book with a modest deposit ("The children must learn to handle and save money") to which wealthier relatives added more on birthdays or occasional visits. He could have been terribly disappointed had he—or anyone else—ever known that I used some of it for "emergency taxis" when a few times I woke up late for school. Since Papa was too short-sighted to see me hail the driver, perhaps that guilt feelings [sic] from those times, even today, make it harder for me to spend $2.00 for a taxi, which I sill like to avoid as a luxury item, than to spend $20.00 for the AFSC or other good humanitarian causes or $200.00 for a Weigl recording or performance. In the same way I sometimes put checks away in a hurry and to my pleasant surprise re-discover them after a month or two; or I may forget about food in the refrigerator if I do not need it at the moment and if it is not in the front row within hurried reach. Papa insisted on frugality in food and everyday living while spending money without regret on antiques, rare books and other things he cared for. Mama was more easygoing from the outset. It is only fair to say that she gained enormously in stature in her later years: She devotedly nursed her sick and almost blind husband through his last illness; she was over 60 when she studied Spanish and started earning some money by translations and she courageously stood up for Käthe under Nazi persecution as long as she could do anything for her. — — Karl Weigl used to say: "Fundamentally good people become better, worthless people worse through trials and tribulations". . . . In this respect our family record, in spite of its partly "socialite" background, stands up pretty well: Mama's formerly rather superficial younger sister Lenci, took care of her up to her tragic voluntary death in 1940 [1939] and faithfully transmitted Käthe's letters to her family. Annie Ekler, the orphaned cousin whom we took along on summer trips and whose early interests focused on Bridge and fashionable clothing, later supported her husband's medical studies here by working hard in a N.Y. factory. I had to remain Karl Weigl's always optimistic "rosy-eyed Calliope", even in his times of despondency and his long last illness, knowing the doctors' verdict, but never letting him give up hope. Also, when he entrusted his musical heritage to me he knew I would never let him down but do for it whatever I could. — Otto Leichter and Käthe's older son Henry fully rose to their responsibility of being both father and mother to their younger one, Franz, both of whom became highly respected lawyers and conscientious public citizens here. And more than anyone one else, Käthe grew to really heroic strength of character in not only enduring her own tragic fate, but also helping her "3 boys" and fellow-inmates keep up hope and courage to the very end and under much harder and trying circumstances than any of us ever had to face.
The more I learned about her later years and how she faced them, the sadder I am that we never got to know each other better before. Had we outgrown and overcome those unfortunate sibling rivalries early enough, we could have meant so much to each other!
Käthe and I also shared two important life-long interests, which I am grateful to our parents for having instilled in us at an early age: our love for nature (especially the mountains) and for music. Every summer we went traveling to the "Salzkammergut", the Tyrol or Switzerland and Papa felt exhilarated and "in his element" as soon as we started out. Mama took long early morning walks to the Selle-am-Grödener Joch when we stayed in that part of the Dolomites and we all bravely hiked across the St. Gotthard-of-Grimsel Pass in Switzerland; once even almost got lost in the fog when I tried to take some shortcuts.
As to music, I started at age 5 playing the piano and Käthe the violin. To her, too, although not a professional, playing chamber music with her Labor movement friends was an unfailing source of refreshment and joy.
Learning languages was also easy for us since we started at 3 and 4 when Mama read to us French children's stories which I still remember: so we soon spoke French almost as fluently as German. A few years later we got instruction in English and afterwards also some Italian which we could practice during summer travels to Lago di Garda, Como or Venice.
All these languages came very handy when, after World War I, during the severe scarcity and depression in Vienna, I took a job as an interpreter for a very interesting international labor leader in Holland; and of course 18 years later, when in 1938 we emigrated to the U.S.
Mama even took up Spanish and did some translation work when after age 55 she became widowed. It kept her busy when both Käthe and I had become involved in our own families and professional goals: Käthe in the labor and working women movement, and I, in Karl's creative work and some of my own, as well as in teaching. Twenty-three years later I similarly took an M.A. at Columbia U[niversity] and started professional work in music therapy (when I was about the same age as mama) when after Karl's death I had to struggle through life alone. (in 1949)
Unfortunately mama could not see her three grandchildren, Henry, Franz and Wolfi (later called Johnny but originally named Wolfgang Johannes after Mozart and Goethe) after hardships during the first years of transition to the new home country, develop into well-adjusted, responsible citizens and family heads. Mama would also have enjoyed watching her great-grandchildren grow up and follow their own goals in life.
I am sorry of course that my own grandchildren, Kit (with husband John and baby Valerie Helen), Karl and Andy live rather far away two that we can only rarely get together. But I love them dearly and on those rare occasions cherish the feeling of closeness and understanding in spite of the distance.
I hope that these brief recollections may help them look back and better understand our background and common heritage.
Affectionately
Vally Mutti
(Christmas 1981)
[Ed. note: Vally Weigl's essay has been transcribed verbatim, with no corrections of punctuation, spelling, or syntax except where indicated with editorial brackets. For background on the essay it is useful to know that Vally Weigl's sister, Käthe Leichter, was arrested in Vienna by the Gestapo on 30 May 1938 and held in various prisons in Vienna until in January 1940 being transferred to the Ravensbrück concentration camp; in March 1942 she was sent away with a transport of women and gassed in the Bernburg mental asylum. Toward the beginning of her ordeal, in the fall of 1938 while in solitary confinement, she wrote a memoir, "Lebenserinnerungen," which was published in 1973 in Herbert Steiner, ed., Käthe Leichter: Leben, Werk und Sterben einer österreichischen Sozialdemokratin (Vienna: Ibera & Molden, 1973). Some of Vally Weigl's recollections may have been prompted by the chapter "Die Schwester" (My Sister) in Käthe Leichter's memoir.]
"Give Them Music" The Crippled Child (April 1957): 13-15.
Music has at all times been one of mankind's most important means of expression and communication. In all cultures, all through recorded history, lullabies, love songs and dirges have accompanied man from cradle to grave. Long before Shakespeare, ancient legends of the Greeks, the Scandinavians, the Germans and many other nations have portrayed the "soothing of the savage breast" through music.
Music, more than any other art, can make us laugh or cry and, at least temporarily, forget worries, hardship and anxieties. It is an ideal means of establishing contact with adults and children who cannot be reached or who cannot express themselves through language. The vital and stimulating force of rhythm in live music especially attracts also lonely outsiders and induces them to join in the fun with other children, whereby they often unconsciously attempt tasks they previously may never have attempted and which they would have considered beyond their capacity. This in turn will encourage them to venture further and become part of the group.
Music has a special impact in filling the needs of those groups of handicapped, who by the very nature of their impairment, may be compelled to miss many other sensual impressions of the world around them and thereby to lead more isolated lives because of it.
For the bling, for instance, acoustic acuity and differentiation is invaluable. They need it for recognizing people's voices, danger signs of traffic or of the weather; and their ear can be sharpened to any kind of acoustic perception through systematic ear training. At the same time music for them can alse become an important substitute for those socializing and recreational activities which their sighted fellowmen take for granted, such as pictures, movies, sports, and dancing. At many good training centers for the blind, they dance together, perform on the stage in dramatic and musical plays and take part in concerts, which enriches their lives and gives them a sense of personal worth.
With the deaf and hard of hearing, too, the former widespread belief that music was inaccessible and meaningless to them is vanishing more and more. In the deafened person especially, the loss of feeling of relationship with the outside world often causes a feeling of depression, which to some degree at least can be counteracted by training in rhythm and music. They learn quickly to dance and dramatize songs remarkably well. At the "Escuela Oral para Ninos Sordas" in Mexico City, the wife of the audiologist, Dr. Berruecos, an excellent musician with long experience with the handicapped, teaches the children to recognize and translate into movement vibrations on drums and on an especially adapted grand piano with a built-in extra sounding board. Their specially constructed flooring of easily vibrating material is now being tried in a few institutions for the deaf in this country, too. Many such patients, while deaf to the spoken word, can this way perceive music, especially tones of lower frequencies. Although they usually cannot learn to reproduce tones of a certain pitch themselves, they do become aware of pitch differences in the speech of others and thereby often can be taught to produce relative differences of pitch in their own speaking voice. This gives them greater variety in inflection and helps them avoid the otherwise typical monotone speaking voice of the deaf and hard of hearing.
An expression of radiant joy and triumph came over the face of seven-year-old aphasic little Larry who was deaf so far as verbal communication was concerned, when he was permitted to climb on a table and lean over the top of the piano at San Francisco's Cerebral Palsy school. With his whole small body thereby feeling the vibrations of the low and loud chords, Larry could perceive and clap with every chord and rhythm, and stop whenever the music stopped. To make sure that he did not just watch the keyboard, he was asked to close his eyes, to avoid imitation of the other children's movements. They and the teacher went on clapping, even when the playing ceased. But Larry would invariably stop with the music and the fact that he could not be fooled made him feel the prouder of himself and of his new achievement. Each day, as soon as the teacher entered the classroom and before she could reach the piano, Larry rushed on his crutches to the little table, climbed on top of it and leaned over the piano, his eyes closed, his hands ready to clap the rhythm, his whole little being anxious to resume that wonderful new game.
For cardiac patients who today no longer are condemned to absolute bed rest and idleness, physicians now recommend limited activities of daily living and of recreation, which can be performed more easily if coordinated to music of a desirable moderate tempo and character. Thereby these activities cause less effort and less "wear and tear," while in a mild form they can provide release of stress and energy.
Also, for tuberculous and other chronically ill patients, listening to and participation in music can improve attention span, maintain interest and reduce worries and self-preoccupation. By producing a calming effect it can favorably influence their mood and thereby chances for recovery.
The effectiveness of music in working with mental patients has often been described. Not quite as much is generally known about its importance in the training of motor handicapped, especially also cerebral palsied children. Most of them respond eagerly to music and rhythm and how much they can benefit from participation in such activities. Patients who cannot run or jump or climb trees with other children and who lack the stimulus of dancing, sports and many other social pleasures, need all the more an emotional outlet in creative activities within their reach, which they can share with others, which gives them some feeling of achievement, of belonging to the group and thereby immediate satisfaction. Music can fill many of these psychological needs and at the same time help them physically, too.
For patients with severe speech impairment music can act as a means of contact with other people and a valve for pent-up tensions, as it communicates moods, attitudes and feelings. It can also aid considerably toward better speech, as though vocalizing and group singing, breathing habits, phonation and inflection can be improved. Often stutterers who cannot speak a single continuous sentence may sing whole verses of some familiar song.
To the accompaniment of rhythmic music, children will put ever so much more effort into physical exercises which can be disguised as "acting out songs," personifying ponies, bicycle riders, windmills, a postman, a policeman, and a host of other people and things. Children's imagination spurred on by stimulating rhythm often carries them over hurdles they never thought they could take. Any such experience will strengthen their self-confidence and encourage them.
For those who are not too severely involved, learning to play an instrument is a good incentive in this direction. For others, participation in a rhythm band is an excellent mans of combining efforts toward several treatment goals. Even the desire to hold and handle such instruments as cymbals, drums or triangles is strong motivation for many severely handicapped patients, and eyes light with pride in youngsters who for the first time succeed in making their paralyzed hands jingle a bell or their athetoid arms hit a drum or tambourine right in time according to the demands of the music. These requirements of the music are often more readily accepted, even by otherwise undisciplined youngsters, than verbal directions from parents or teachers. Waiting for their turn to come in, letting go here, restraining their strength there (according to loud or soft sections of the music) encourages control, coordination and lengthened attention span. Additional discipline may be provide4d by letting the children conduct the band, which gives them a feeling of power, too rarely experienced by these children, since all the others must follow their lead in tempo and dynamics.
In a very good article Earl Schenck Miers wrote: "Handicapped people are just like everyone else—only more so." All of us need self-assurance, the feeling of being wanted, needed and able to contribute and mean something to others. These are very real and often unfulfilled needs of the handicapped child, too, which can be satisfied partially by giving him adequate responsibilities and means of gaining a sense of personal worth and achievement wherever possible. Even a few bars of a "solo" on drums, triangles or xylophone make him feel needed and important, since he is the center of everyone's attention for those moments and can contribute to that enjoyable group project of which he is proud to be a part. And even though functional music can help toward their physical improvement, better use of hands and fingers, better motor control and coordination in speech and gait, its value as a socializing force, as motivation and providing emotional satisfaction and stamina are at least as important. Severely handicapped cerebral palsied children, who can hardly stand on their feet will try to dance or "practice for a parade" if a stirring dance or march is played for them; and it has been touching to see how considerately their less involved classmates have helped and supported them in such attempts which then encouraged them to practice walking more effectively, too. Also some more withdrawn patients improved in posture as well as in self-reliance and in their attitude toward the group after leading the rhythm band. After a while they could take the next big step forward in learning to accept others, too, as leaders.
Of course, the musician at the piano must be able to project a strong sense of rhythm which will pull the timid, withdrawn youngster irresistibly into its spell. He must not only be a good, well-versed musician, who watches the patients while he plays and understands their condition and needs, but also one who is imaginative in adapting his techniques to these needs.
Most important of all, he must like people, enjoy working with them, and be genuinely interested in them as human beings, not just as patients or cases. The children sense this and react accordingly. The person who, besides bringing them the enjoyment of music, makes them feel he is personally interested in them, can motivate them to greater effort.
"Arts for World Unity Statement." Typescript, 3 pages, 3 March 1982.
From my very early days as a musician and composer, I have been concerned about using the power of music for reaching people toward "that of God in every man." [phrase attributed to George Fox, founder of the Religious Society of Friends.] Yet while teaching music at Westtown School 1942–49, I realized how few Quakers as yet were aware of this power. Obviously, the centuries of condemnation of everything "worldly" or "emotional" had left their imprint and could be altered only gradually. But there were beginnings of more receptiveness toward the arts, and by today the attitude toward them has been changing.
I felt really encouraged when in 1972 Francis Hall invited me to Powell House to meet a wonderful man and graphic artist, Fritz Eichenberg, then a member of Scarsdale Meeting, who had nurtured exactly the same dreams as I did from the point of view of the visual artist. In many months of concentrated joint efforts we then set up goals and purposes for such an "Arts for World Unity Committee" and won the moral support as sponsors from prominent clergymen and Quakers (such as Howard Brinton, Clarence Picket, Philip Berrigan, Thomas Merton, etc.) and distinguished artists (Casals, Menuhin, Martha Graham, Gerhard Marcks, and many others).
In 1963 Fritz Eichenberg and I presented our concert to the New York Yearly Meeting's Peace Institute, which agreed to set up an "Arts for World Unity Committee," and for a few years also gave us some modest financial support. Fritz Eichenberg became its first Chairman, but after suffering a slight heart attack a few years later, he asked me to take over the Chairmanship. More recently Ruth Ringenbach of the Westbury Meting has been our Chairperson while I continued to be responsible for the performing arts program.
Unfortunately, the "Peace Institute" did not get the suggested financial backing from its members and could no longer support us and other planned projects. Our committee was turned over to the "Friends' Peace and Social Action Committee" and our peace-centered programs had to rely on volunteer performers. Still, we were able to arrange such intercultural, international programs in churches of 8 various denominations, libraries, and other institutions on a good artistic level and with warm response. — In fact, the Donnell Library has been so appreciative that they offered us their attractive hall and printed programs free of charge for one or two annual programs there. — I was able to provide them (via Community Trust Fund) with a small annual support which, though not enough for the participants' "fees" should at least cover their expenses. However, the Library's staff is not supposed to make arrangements with individual artists or disburse money to the performers, so they agreed that one of my assistants, Max Lifchitz, would take care of this should I be unable to continue organizing these programs myself.
With the New York Quarterly Meeting's, the Library's, and Community Trust Fund's consent, the NY Quarterly Meeting will hold the amount for Max Lifchitz so it can be disbursed for the expenses of the artists participating at the Library concerts. Clerk Paul Cormier of NY Quarterly Meeting suggested that I write this article for their three NY Meetings' Newsletters, Morningside, 15th Sheet, and Brooklyn, which all have kindly promised to cooperate.
So that interested members and attenders may know about our work and possibly, especially if they have experience in public relations, typing, fund-raising, or the like, help in our efforts. At Morningside Meeting Mary Jane Matz (Musicologist) and Kathy Wood (Flutist) and at the FOR Jane Jeats (Pianist) have already consented to join our committee, other professionals may follow.
A few years ago Friends' Journal published an article about our concern that Arts for World Unity (AWU) "should not go down the drain"; the response was really gratifying. From various parts of the country (Philadelphia, Pennsylvania; Hays, Kansas; St. Paul, Minnesota; and Cincinnati, Ohio) Friends wrote to me and started arranging small-scale AWU programs in their communities; Angela Seidel presented some programs at Pendle Hill, Friends' General Conference, and in Cincinnati.
Since the creative arts can stir people's imagination as nothing else can, it is a worthwhile goal to work for. Help us in our efforts to encourage creative people in the performing, literary, and visual arts to find new expressions for the power that can transform evil into good and violence into reverence for life.
If you are interested in participating, please contact me so we may arrange for a meeting at my home.
Ed. note: An original copy of this untitled three-page typescript, written nine months before her death, is located at the KWF; this transcription includes corrections and changes in ink made by Vally Weigl, as well as a few silent corrections of punctuation and spelling. It has not been determined for whom Vally Weigl wrote this letter or whether it was indeed duplicated and distributed.
Writings by Vally Weigl


Copper has a number of beneficial effects on the body, and can be found in mushrooms. Copper can regulate and stimulate the absorption of iron from food and properly utilize it. Mushrooms also have high levels of iron, so the both of these minerals together are great for preventing anemia.
The teff grain has a high concentrations of copper, which plays important roles throughout our body, including in energy production, nervous system function, growth and repair, enzymatic reactions and red blood cell creation. If the body doesn't have the proper copper levels, many of our body's systems will begin to fail, so consuming teff can be great for your body.


text:
#include <fstream>
#include <OpenSim/Common/Storage.h>
#include <OpenSim/Auxiliary/auxiliaryTestFunctions.h>
#include <OpenSim/Common/STOFileAdapter.h>

using namespace OpenSim;
using namespace std;

void testStorageLoadingFromFile(const std::string& fileName, const int numCols)
{
    Storage storage{ fileName};
    // remove extension
    auto ix = fileName.rfind(".");
    if (ix == std::string::npos) {
        throw Exception("File name for Storage loading '" + fileName +
                        "' must have a valid extension.");
    }
    std::string name = fileName.substr(0, ix-1);
    Array<std::string> labels = storage.getColumnLabels();

    storage.print("test_" + name + ".sto");

    ASSERT(numCols == labels.size());
}

void testStorageLegacy() {
    // Create a storage from a std file "std_storage.sto"
    //ofstream checkRunDirFile("rundir.txt");
    //checkRunDirFile << "Run from here:\n\n";
    //checkRunDirFile.close();
    string stdLabels[] = { "time", "v1", "v2" };
    std::unique_ptr<Storage> st(new Storage("test.sto"));
    // time[\t]v1[\t]v2
    // 1.[\t]   10.0[Space]20
    // 2.[\t\t] 20.0[\t]40
    ASSERT(st->getSize() == 2);
    const Array<std::string> &lbls = st->getColumnLabels();
    ASSERT(lbls.getSize() == 3);
    int i = 0;
    for (i = 0; i<lbls.getSize(); i++) {
        ASSERT(lbls[i] == stdLabels[i]);
    }

    double val;
    for (i = 0; i<st->getSize(); i++) {
        StateVector& row = (*st->getStateVector(i));
        ASSERT(row.getTime() == i + 1);
        ASSERT(row.getData()[0] == row.getTime()*10.0);
        row.getDataValue(0, val);
        ASSERT(val == row.getTime()*10.0);
        ASSERT(row.getData()[0] == row.getTime()*10.0);
        ASSERT(row.getData()[1] == row.getTime()*20.0);
    }
    int ncol = st->getSmallestNumberOfStates();
    ASSERT(ncol == 2);
    Array<double> col(SimTK::CNT<SimTK::Real>::getNaN(), 4);
    st->getDataColumn(1, col);
    ASSERT(col[0] == 20.);
    ASSERT(col[1] == 40.0);

    ASSERT(st->getStateIndex("v2") == 1);

    Storage st2("testDiff.sto");
    // Test Comparison
    double diff = st->compareColumn(st2, stdLabels[1], 0.);
    ASSERT(fabs(diff) < 1E-7);
    diff = st->compareColumn(st2, stdLabels[2], 0.);
    ASSERT(fabs(diff) < 1E-7);

    // Loading version 2 storage file with Storage class.
    auto table = st->exportToTable();
    STOFileAdapter::write(table, "testStorage_version2.sto");
    {
        // Now read using Storage() constructor.
        Storage stVersion2("testStorage_version2.sto");
        // Compare with st.
        SimTK_TEST_EQ(table.getMatrix(),
            stVersion2.exportToTable().getMatrix());
    }

    // The version 2 storage file does not require nRows and nColumns
    // metadata (Issue #2120).
    {
        table.removeTableMetaDataKey("nRows");
        table.removeTableMetaDataKey("nColumns");
        STOFileAdapter::write(table,
            "testStorage_version2_short_header.sto");
        Storage stVersion2("testStorage_version2_short_header.sto");
        SimTK_TEST_EQ(table.getMatrix(),
            stVersion2.exportToTable().getMatrix());
    }
}

void testStorageGetStateIndexBackwardsCompatibility() {
    auto convert = [](const std::vector<std::string>& vec) {
        OpenSim::Array<std::string> out({}, 0);
        for (const auto& element : vec) out.append(element);
        return out;
    };
    Storage sto;

    // States file column labels from OpenSim 3.3.
    sto.setColumnLabels(convert({
        "time",
        "hip_flexion",
        "hip_flexion_u",
        "soleus.activation",
        "soleus.fiber_length"
        }));
    SimTK_TEST(sto.getStateIndex("nonexistant") == -1);
    SimTK_TEST(sto.getStateIndex("/jointset/hip/hip_flexion/value") == 0);
    SimTK_TEST(sto.getStateIndex("/jointset/hip/hip_flexion/speed") == 1);
    SimTK_TEST(sto.getStateIndex("/forceset/soleus/activation") == 2);
    SimTK_TEST(sto.getStateIndex("/forceset/soleus/fiber_length") == 3);
    // Random checks.
    SimTK_TEST(sto.getStateIndex("value") == -1);
    SimTK_TEST(sto.getStateIndex("fiber_length") == -1);

    // States file column labels from OpenSim 4.0 development.
    sto.setColumnLabels(convert({
        "time",
        "hip/hip_flexion/value",
        "hip/hip_flexion/speed",
        "soleus/activation",
        "soleus/fiber_length"
    }));
    SimTK_TEST(sto.getStateIndex("nonexistant") == -1);
    SimTK_TEST(sto.getStateIndex("/jointset/hip/hip_flexion/value") == 0);
    SimTK_TEST(sto.getStateIndex("/jointset/hip/hip_flexion/speed") == 1);
    SimTK_TEST(sto.getStateIndex("/forceset/soleus/activation") == 2);
    SimTK_TEST(sto.getStateIndex("/forceset/soleus/fiber_length") == 3);

    // States file from OpenSim 4.0.
    sto.setColumnLabels(convert({
        "time",
        "/jointset/hip/hip_flexion/value",
        "/jointset/hip/hip_flexion/speed",
        "/forceset/soleus/activation",
        "/forceset/soleus/fiber_length"
    }));
    SimTK_TEST(sto.getStateIndex("nonexistant") == -1);
    SimTK_TEST(sto.getStateIndex("/jointset/hip/hip_flexion/value") == 0);
    SimTK_TEST(sto.getStateIndex("/jointset/hip/hip_flexion/speed") == 1);
    SimTK_TEST(sto.getStateIndex("/forceset/soleus/activation") == 2);
    SimTK_TEST(sto.getStateIndex("/forceset/soleus/fiber_length") == 3);

    // TODO: Put XML document version in Storage header.
}

int main() {
    SimTK_START_TEST("testStorage");

        // Verify loading of scalar Outputs (there are 2) from .sto into a Storage
        SimTK_SUBTEST2(testStorageLoadingFromFile, "sampleOutputs.sto", 2+1);

        // Verify loading of Vec3 Outputs (2) from .sto into a Storage
        SimTK_SUBTEST2(testStorageLoadingFromFile, "sampleOutputsVec3.sto", 2*3+1);

        // Verify loading of SpatialVec Outputs (2) from .sto into a Storage
        SimTK_SUBTEST2(testStorageLoadingFromFile, "sampleOutputsSpatialVec.sto", 2*6+1);

        // Verify the loading of marker data (14 markers) from .trc into a Storage
        SimTK_SUBTEST2(testStorageLoadingFromFile, "dataWithNaNsOfDifferentCases.trc", 43);

        #if defined (WITH_EZC3D)
            // Verify the loading of forces from .c3d into a Storage. Includes 2
            // force-plates with force, point, moment vectors (Vec3 flattened)
            SimTK_SUBTEST2(testStorageLoadingFromFile, "walking2.c3d", 3*6+1);
        #endif

        SimTK_SUBTEST(testStorageLegacy);

        SimTK_SUBTEST(testStorageGetStateIndexBackwardsCompatibility);
    SimTK_END_TEST();
}

redpajama_set_name: RedPajamaGithub


Yesterday was held at the second meeting of the working group of womANDigital and our director, María José escalona, returns to participate as spokesperson of the equality policy group of the Andalusian communities.
The event is organized by the Ministry of employment, business and commerce of the Junta de Andalucía within the framework of the program for gender equality in the ICT sector. ICT 2020.


While it's been assumed all along that it would take time for Lamar Jackson to fully develop as a quarterback, the Baltimore Ravens believe that he can be an immediate asset to their offense and intend to use both him and Joe Flacco on the field at the same time.
One of the reasons that this is possible is because of Jackson's excellent running ability, which he has already been able to feature through the Ravens' first two preseason games. Those efforts culminated in an impressive rushing touchdown on Thursday night that got his teammates and coaches excited.
"I thought he was tackled twice, and then he wasn't", Head Coach John Harbaugh recalled of the run. "You always appreciate that if he's on your team. Flacco said that "he's so good at cutting back on guys" and praised his ability to "stop on a dime". Even Jackson himself felt it was an "excellent" play.
That said, he hasn't exactly looked like somebody who is going to be under center for every snap of the season opener. He struggled as a passer in his preseason debut, and while he showed improvement in the Ravens' second game, he still only completed seven of 18 pass attempts, albeit for 119 yards.
Through two games, Jackson has completed 11 of 28 pass attempts for 152 yards. He has thrown one touchdown pass and one interception. As a runner, he has carried the ball 13 times for 46 yards, which finally culminated in his nine-yard touchdown run against the Los Angeles Rams.
While the numbers have not been there yet, the Ravens do see the poise of a more seasoned player. That includes the way that he has handled himself both on and off the field. He improved with his ability to remain in the pocket. He has also been highly critical of his performances and has focused on making the plays next time that he left on the field.
Jackson will eventually be the future, but it may not come this year. Flacco is still a reasonable commodity at the quarterback position that can win the team some games, even if both sides seem to understand that that partnership is now living on borrowed time and will inevitably end in a year or two.
At some point, it will be the 2018 first-round pick's chance to run the offense, and Baltimore hopes that they will have the time to develop him before throwing him out there. In the meantime, he has three more exhibition games to improve before the regular season gets underway.


Where Can You Buy a Great Two Way Radio?
Posted by Icom earpiece | in Uncategorized | Comments Off on Where Can You Buy a Great Two Way Radio?
Posted by Icom earpiece | in Uncategorized | Comments Off on Can Headphones Make Me Go Deaf?


text:
points logo
Metrics & Financial Reports
View printer-friendly version
Frontier Airlines Expands Partnership With Points To Enhance Travel Redemption Program
Frontier leverages Points Travel services for members to gain access to over 300,000 hotels
TORONTO, Feb. 04, 2019 (GLOBE NEWSWIRE) -- Points (TSX:PTS) (Nasdaq:PCOM), the global leader in powering loyalty commerce, today announced that it has expanded its partnership with FRONTIER Miles, the recently reinvented frequent flyer program of low-fare carrier, Frontier Airlines to leverage Points Travel services, the industry leading white label hotel platform for access to over 300,000 hotels at the click of a button.
Through a single integration with Points' Loyalty Commerce Platform, which powers Points Travel services, FRONTIER Miles members now have the ability to redeem miles when booking on over 300,000 hotels across the globe. This significant program enhancement is following a successful partnership launch this year with Points and Groupon when FRONTIER Miles leveraged Points' platform to offer Groupon customers the opportunity to earn up to 5 FRONTIER Miles per dollar spent when buying eligible Groupon deals.
"With the introduction of our recently reimagined FRONTIER Miles program, we're excited to expand our relationship with Points, growing our redemption capabilities to more than 300,000 hotels, and ensuring the redemption of miles is made even easier and more rewarding for our customers," said Tommy Langhauser, senior manager of loyalty and partnerships, Frontier Airlines. "The addition of mileage redemption for hotels through Points allows us to give our passengers added flexibility in redeeming their FRONTIER Miles for any part of their next vacation and beyond."
Frontier Airlines first partnered with Points in 2011 to offer a number of Points' Loyalty Currency Retailing services, including Buy, Gift and Share to enable its members to get more out of their participation. The introduction of Points Travel services and overall closer collaboration with Points will ensure the recently reinvented FRONTIER Miles loyalty program can continue to better engage their members, generate incremental revenue and drive new member acquisition.
"We have been proud partners of Frontier Airlines for many years now, and are thrilled to be able to further expand our relationship," said Rob MacLean, CEO of Points. "We look forward to leveraging our platform to power new features for Frontier to attract new members, and to deepen loyalty and flexible engagement opportunities with its existing members."
Points Travel can be quickly integrated into any loyalty program's web and mobile properties to create a highly-converting, travel eCommerce offering that is program-branded and leverages the capabilities of the Points Loyalty Commerce Platform. The user experience is simple and intuitive, taking members from consideration to purchase in just a few clicks, ultimately rewarding members with thousands of points or miles as well as being able to redeem points for bookings at thousands of hotels and car rental companies around the world.
For more information, visit the FRONTIER Miles implementation of the Points Travel platform at https://redeem.flyfrontier.com/.
Points, (TSX: PTS) (NASDAQ: PCOM), provides loyalty e-commerce and technology solutions to the world's top brands to power innovative services that drive increased loyalty program revenue and member engagement. Currently, the Company has a growing network of nearly 60 global loyalty programs integrated into its unique Loyalty Commerce Platform. Points offers three core private or co-branded services: its Loyalty Currency Retailing service sells loyalty points and miles directly to consumers; its Platform Partners service offers developers transactional access to dozens of loyalty programs and their hundreds of millions of members via a package of APIs; and its Points Travel service helps loyalty programs increase revenue from hotel and car rental bookings while offering members more opportunities to earn and redeem loyalty rewards more broadly. Points is headquartered in Toronto with offices in San Francisco, London and Dubai.
For more information, visit company.points.com or follow Points on Twitter (@PointsLoyalty).
About Frontier Airlines
Frontier Airlines is committed to offering 'Low Fares Done Right' to 100 destinations and growing in the United States, Canada, Dominican Republic Jamaica and Mexico on more than 350 daily flights. Headquartered in Denver, Frontier's hard-working aviation professionals pride themselves in delivering the company's signature Low Fares Done Right service to customers. Frontier Airlines is the proud recipient of the Federal Aviation Administration's 2016 Diamond Award for maintenance excellence and was recently named the industry's most fuel-efficient airline by The International Council on Clean Transportation (ICCT) as a result of superior technology and operational efficiencies.
Points Media Relations
Catherine Lowe
Catherine.lowe@points.com
Frontier Airlines Media Relations
media@flyfrontier.com
Source: Points International, Ltd.
Points Loyalty Wallet
© Points.com Inc.
We use cookies to ensure we give you the best experience on our website.
If you continue without changing your browser settings, we'll assume you are happy to receive all cookies on our website. You can change your cookie settings in your browser at any time. Find out more about how we use cookies.

redpajama_set_name: RedPajamaCommonCrawl


text:
HomeAnnouncements
Kyber Network announces new CEO
byAzzital • January 23, 2022
The transition marks Kyber Network's next high growth phase.
With many major developments over the past year as well as in the 2022 pipeline, Kyber Network plans to build even more momentum and continue to contribute transformative technologies and on-chain infrastructure to the Decentralized Finance (DeFi) space. While Loi Luu has led Kyber Network to where it is today as co-founder and CEO, successfully delivering every milestone to date; the enormous changes in the space over the past two years and Kyber's many upcoming high growth initiatives require new management and team structure in order to rise to the challenge.
On that note, Kyber Network enters its next stage of growth with a strategic shift in leadership, the appointment of Victor Tran as its CEO. Meanwhile, Loi Luu, the former CEO and co-founder of Kyber Network will remain on the leadership team in the capacity of Chairman of Kyber Group to oversee all Kyber activities.
The newly appointed CEO, Victor Tran, is the former CTO and co-founder of Kyber Network, as well as a Forbes 30 under 30 Asia alumni. Victor has a strong engineering background; having studied Computer Science at Vietnam National University, where he first met Loi Luu, with whom he would later go on to build Kyber Network. With his experience since the early days, Victor has an in-depth understanding of Kyber Network and its enormous potential. Having demonstrated both his leadership and management capabilities over the years, it is clear that Victor is well-positioned to take Kyber to new heights, especially at a time when not only DeFi, but the entire crypto space is growing and transforming at an exponential rate.
Kyber entered the DeFi space as a key contributor to cutting-edge technology and infrastructure. The space has since undergone massive changes, with crypto adoption increasing by 880% over 2021 alone. It is no longer an early, experimental industry with only a few thousand users, but now a 3 trillion USD market with tens of millions of users around the world.
Victor will be bringing his own unique brand of leadership to Kyber, helping the team and community navigate this fast-changing DeFi landscape and execute on their goals. Under his guidance, Kyber can look forward to a new era of growth and development.
Kyber Network was established with a mission to build an innovative decentralized exchange (DEX) protocol to address the core need for seamless token swaps and liquidity in the cryptocurrency ecosystem. It has made a name for itself as one of the first and biggest DeFi protocols in the world since its launch in 2017. It also contributed to the space with its liquidity infrastructure as well as its development of wrapped Bitcoin (WBTC) and DEX aggregation.
More recently, Kyber 2021 was filled with many notable milestones. It was the first to introduce capital efficiency through the launch of DeFi's first dynamic market maker protocol, KyberSwap, and its deployment to six blockchain networks. TVL reached an all-time high of over $500 million and trading volume surpassed $4.7 billion. KyberSwap also launched its DEX aggregation feature, providing the best rates on all on-chain trades via split trading and dynamic routing.
Together with their new leaders and community support, Kyber aims to hit even more milestones in 2022 and beyond.
Website | Discord | Twitter | Telegram | Forum | Blog | Reddit | Facebook | Github | KyberSwap | KyberSwap Docs
Tags: Announcements Kyber Network Network


text:
Destiny City Film Festival > Blog > Uncategorized
By Emily on October 31, 2021
Happy Halloween from DCFF! October 30, 2021 – It's Halloweekend! Around this time of year, many search every nook...
Screenwriter Spotlight October 25, 2021 – Throughout the month of October, we've interviewed previous winners of our Short Screenplay...
Screenwriter Spotlight October 15, 2021 – Throughout the month of October, we'll be interviewing previous winners of our Short...
By Emily on October 5, 2021
Screenwriter Spotlight October 5, 2021 – Throughout the month of October, we'll be interviewing previous winners of our Short...
By Emily on August 1, 2021
More than a Month: Celebrate Pride! Our mission at Destiny City Film Festival is to use the power of...
Where Are They Now? Chapter 10
By Emily on January 5, 2021
After 7 seasons, 100+ short screenplays read, and 200+ films programmed, DCFF has met an immense amount of visionary...
Where Are They Now? Chapter 9
By Emily on December 10, 2020
A little bit of demystification
By Emily on July 16, 2014
One of the most common questions a film festival programmer receives is… "how do you pick the films for...
How many people does it take to make an independent movie?
By Emily on February 11, 2014
Not as many as other films might, if Jim Jarmusch is at the helm. What other directors write the...
By Emily on November 12, 2013
As a festival programmer or director, sometimes you encounter a film that just sticks with you. And the dozens...


We were all born for a specific purpose in this life. I have finally understood what it means to be a chief. The chief has to be vigilant, to share his or her knowledge while they are here in their temporary time. One must learn not only be a good follower, but also a good leader. It is not that some of us are meant to be Chiefs and other's Indians. We simply must be more conscious of when to know the time to lead and when to put down ones own personal pride. It is an active choice of also having courage to take up the torch in this life for others when no one else will. We take on many different roles, but the one with the greatest amount of honor as well as burden and pressure, is the role of the chief. One must be fierce, constant, regal and concerned for all in the tribe.


text:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>The Basics of Creating Rails Plugins</title>

	<link href="stylesheets/base.css" media="screen" rel="Stylesheet" type="text/css" />
	<link href="stylesheets/forms.css" media="screen" rel="Stylesheet" type="text/css" />
	<link href="stylesheets/more.css" media="screen" rel="Stylesheet" type="text/css" />

Ruby on Rails

Sustainable productivity for web-application development

Ruby on Rails

Guides Index

Chapters

					Preparation

Create the basic app

Generate the plugin skeleton

Setup the plugin for testing

Run the plugin tests

					Extending core classes

Creating the test

Organize your files

Working with init.rb

					Add an <tt>acts_as_yaffle</tt> method to Active Record

Add a class method

Add an instance method

					Create a generator

Testing generators

Adding to the manifest

Manually test the generator

The USAGE file

					Add a custom generator command

					Add a model

					Add a controller

					Add a helper

					Add a Custom Route

					Odds and ends

Generate RDoc Documentation

Write custom Rake tasks in your plugin

Store plugins in alternate locations

Create your own Plugin Loaders and Plugin Locators

Use Custom Plugin Generators

					Appendix

References

Final plugin directory structure

The Basics of Creating Rails Plugins

A Rails plugin is either an extension or a modification of the core framework. Plugins provide:

a way for developers to share bleeding-edge ideas without hurting the stable code base

a segmented architecture so that units of code can be fixed or updated on their own release schedule

an outlet for the core developers so that they don't have to include every cool new feature under the sun

After reading this guide you should be familiar with:

Creating a plugin from scratch

Writing and running tests for the plugin

Storing models, views, controllers, helpers and even other plugins in your plugins

Writing generators

Writing custom Rake tasks in your plugin

Generating RDoc documentation for your plugin

Avoiding common pitfalls with init.rb

This guide describes how to build a test-driven plugin that will:

Extend core ruby classes like Hash and String

Add methods to ActiveRecord::Base in the tradition of the acts_as plugins

Add a view helper that can be used in erb templates

Add a new generator that will generate a migration

Add a custom generator command

A custom route method that can be used in routes.rb

For the purpose of this guide pretend for a moment that you are an avid bird watcher.  Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle goodness.  First, you need to get setup for development.

1. Preparation

1.1. Create the basic app

The examples in this guide require that you have a working rails application.  To create a simple rails app execute:

<tt>gem install rails
rails yaffle_guide
cd yaffle_guide
script/generate scaffold bird name:string
rake db:migrate
script/server</tt>

Then navigate to http://localhost:3000/birds.  Make sure you have a functioning rails app before continuing.

Editor's note:
The aforementioned instructions will work for sqlite3.  For more detailed instructions on how to create a rails app for other databases see the API docs.

1.2. Generate the plugin skeleton

Rails ships with a plugin generator which creates a basic plugin skeleton. Pass the plugin name, either CamelCased or under_scored, as an argument. Pass <tt>--with-generator</tt> to add an example generator also.

This creates a plugin in vendor/plugins including an init.rb and README as well as standard lib, task, and test directories.

Examples:

<tt>./script/generate plugin yaffle
./script/generate plugin yaffle --with-generator</tt>

To get more detailed help on the plugin generator, type <tt>./script/generate plugin</tt>.

Later on this guide will describe how to work with generators, so go ahead and generate your plugin with the <tt>--with-generator</tt> option now:

<tt>./script/generate plugin yaffle --with-generator</tt>

You should see the following output:

<tt>create  vendor/plugins/yaffle/lib
create  vendor/plugins/yaffle/tasks
create  vendor/plugins/yaffle/test
create  vendor/plugins/yaffle/README
create  vendor/plugins/yaffle/MIT-LICENSE
create  vendor/plugins/yaffle/Rakefile
create  vendor/plugins/yaffle/init.rb
create  vendor/plugins/yaffle/install.rb
create  vendor/plugins/yaffle/uninstall.rb
create  vendor/plugins/yaffle/lib/yaffle.rb
create  vendor/plugins/yaffle/tasks/yaffle_tasks.rake
create  vendor/plugins/yaffle/test/core_ext_test.rb
create  vendor/plugins/yaffle/generators
create  vendor/plugins/yaffle/generators/yaffle
create  vendor/plugins/yaffle/generators/yaffle/templates
create  vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb
create  vendor/plugins/yaffle/generators/yaffle/USAGE</tt>

To begin just change one thing - move init.rb to rails/init.rb.

1.3. Setup the plugin for testing

If your plugin interacts with a database, you'll need to setup a database connection.  In this guide you will learn how to test your plugin against multiple different database adapters using Active Record.  This guide will not cover how to use fixtures in plugin tests.

To setup your plugin to allow for easy testing you'll need to add 3 files:

A database.yml file with all of your connection strings

A schema.rb file with your table definitions

A test helper method that sets up the database

vendor/plugins/yaffle/test/database.yml:

<tt>sqlite:
  :adapter: sqlite
  :dbfile: vendor/plugins/yaffle/test/yaffle_plugin.sqlite.db

sqlite3:
  :adapter: sqlite3
  :dbfile: vendor/plugins/yaffle/test/yaffle_plugin.sqlite3.db

postgresql:
  :adapter: postgresql
  :username: postgres
  :password: postgres
  :database: yaffle_plugin_test
  :min_messages: ERROR

mysql:
  :adapter: mysql
  :host: localhost
  :username: root
  :password: password
  :database: yaffle_plugin_test</tt>

For this guide you'll need 2 tables/models, Hickwalls and Wickwalls, so add the following:

vendor/plugins/yaffle/test/schema.rb:

<tt>ActiveRecord::Schema.define(:version => 0) do
  create_table :hickwalls, :force => true do |t|
    t.string :name
    t.string :last_squawk
    t.datetime :last_squawked_at
  end
  create_table :wickwalls, :force => true do |t|
    t.string :name
    t.string :last_tweet
    t.datetime :last_tweeted_at
  end
  create_table :woodpeckers, :force => true do |t|
    t.string :name
  end
end
</tt>

vendor/plugins/yaffle/test/test_helper.rb:

<tt>ENV['RAILS_ENV'] = 'test'
ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'

require 'test/unit'
require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))

def load_schema
  config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
  ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")

  db_adapter = ENV['DB']

  # no db passed, try one of these fine config-free DBs before bombing.
  db_adapter ||=
    begin
      require 'rubygems'
      require 'sqlite'
      'sqlite'
    rescue MissingSourceFile
      begin
        require 'sqlite3'
        'sqlite3'
      rescue MissingSourceFile
      end
    end

  if db_adapter.nil?
    raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
  end

  ActiveRecord::Base.establish_connection(config[db_adapter])
  load(File.dirname(__FILE__) + "/schema.rb")
  require File.dirname(__FILE__) + '/../rails/init.rb'
end
</tt>

Now whenever you write a test that requires the database, you can call load_schema.

1.4. Run the plugin tests

Once you have these files in place, you can write your first test to ensure that your plugin-testing setup is correct.  By default rails generates a file in vendor/plugins/yaffle/test/yaffle_test.rb with a sample test.  Replace the contents of that file with:

vendor/plugins/yaffle/test/yaffle_test.rb:

<tt>require File.dirname(__FILE__) + '/test_helper.rb'

class YaffleTest < Test::Unit::TestCase
  load_schema

  class Hickwall < ActiveRecord::Base
  end

  class Wickwall < ActiveRecord::Base
  end

  def test_schema_has_loaded_correctly
    assert_equal [], Hickwall.all
    assert_equal [], Wickwall.all
  end

end
</tt>

To run this, go to the plugin directory and run <tt>rake</tt>:

<tt>cd vendor/plugins/yaffle
rake</tt>

You should see output like:

<tt>/opt/local/bin/ruby -Ilib:lib "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/yaffle_test.rb"
-- create_table(:hickwalls, {:force=>true})
   -> 0.0220s
-- create_table(:wickwalls, {:force=>true})
   -> 0.0077s
-- initialize_schema_migrations_table()
   -> 0.0007s
-- assume_migrated_upto_version(0)
   -> 0.0007s
Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
Started
.
Finished in 0.002236 seconds.

1 test, 1 assertion, 0 failures, 0 errors</tt>

By default the setup above runs your tests with sqlite or sqlite3.  To run tests with one of the other connection strings specified in database.yml, pass the DB environment variable to rake:

<tt>rake DB=sqlite
rake DB=sqlite3
rake DB=mysql
rake DB=postgresql</tt>

Now you are ready to test-drive your plugin!

2. Extending core classes

This section will explain how to add a method to String that will be available anywhere in your rails app by:

Writing tests for the desired behavior

Creating and requiring the correct files

2.1. Creating the test

In this example you will add a method to String named <tt>to_squawk</tt>.  To begin, create a new test file with a few assertions:

vendor/plugins/yaffle/test/core_ext_test.rb

<tt>require File.dirname(__FILE__) + '/test_helper.rb'

class CoreExtTest < Test::Unit::TestCase
  def test_to_squawk_prepends_the_word_squawk
    assert_equal "squawk! Hello World", "Hello World".to_squawk
  end
end
</tt>

Navigate to your plugin directory and run <tt>rake test</tt>:

<tt>cd vendor/plugins/yaffle
rake test</tt>

The test above should fail with the message:

<tt> 1) Error:
test_to_squawk_prepends_the_word_squawk(CoreExtTest):
NoMethodError: undefined method `to_squawk' for "Hello World":String
    ./test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk'</tt>

Great - now you are ready to start development.

2.2. Organize your files

A common pattern in rails plugins is to set up the file structure like this:

<tt>|-- lib
|   |-- yaffle
|   |   `-- core_ext.rb
|   `-- yaffle.rb</tt>

The first thing we need to to is to require our lib/yaffle.rb file from rails/init.rb:

vendor/plugins/yaffle/rails/init.rb

<tt>require 'yaffle'
</tt>

Then in lib/yaffle.rb require lib/core_ext.rb:

vendor/plugins/yaffle/lib/yaffle.rb

<tt>require "yaffle/core_ext"
</tt>

Finally, create the core_ext.rb file and add the to_squawk method:

vendor/plugins/yaffle/lib/yaffle/core_ext.rb

<tt>String.class_eval do
  def to_squawk
    "squawk! #{self}".strip
  end
end
</tt>

To test that your method does what it says it does, run the unit tests with <tt>rake</tt> from your plugin directory.  To see this in action, fire up a console and start squawking:

<tt>$ ./script/console
>> "Hello World".to_squawk
=> "squawk! Hello World"</tt>

2.3. Working with init.rb

When rails loads plugins it looks for the file named init.rb.  However, when the plugin is initialized, init.rb is invoked via <tt>eval</tt> (not <tt>require</tt>) so it has slightly different behavior.

Under certain circumstances if you reopen classes or modules in init.rb you may inadvertently create a new class, rather than reopening an existing class.  A better alternative is to reopen the class in a different file, and require that file from <tt>init.rb</tt>, as shown above.

If you must reopen a class in <tt>init.rb</tt> you can use <tt>module_eval</tt> or <tt>class_eval</tt> to avoid any issues:

vendor/plugins/yaffle/init.rb

<tt>Hash.class_eval do
  def is_a_special_hash?
    true
  end
end
</tt>

Another way is to explicitly define the top-level module space for all modules and classes, like <tt>::Hash</tt>:

vendor/plugins/yaffle/init.rb

<tt>class ::Hash
  def is_a_special_hash?
    true
  end
end
</tt>

3. Add an <tt>acts_as_yaffle</tt> method to Active Record

A common pattern in plugins is to add a method called acts_as_something to models.  In this case, you want to write a method called acts_as_yaffle that adds a squawk method to your models.

To begin, set up your files so that you have:

vendor/plugins/yaffle/test/acts_as_yaffle_test.rb

<tt>require File.dirname(__FILE__) + '/test_helper.rb'

class ActsAsYaffleTest < Test::Unit::TestCase
end
</tt>

vendor/plugins/yaffle/lib/yaffle.rb

<tt>require 'yaffle/acts_as_yaffle'
</tt>

vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb

<tt>module Yaffle
  # your code will go here
end
</tt>

Note that after requiring acts_as_yaffle you also have to include it into ActiveRecord::Base so that your plugin methods will be available to the rails models.

One of the most common plugin patterns for acts_as_yaffle plugins is to structure your file like so:

vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb

<tt>module Yaffle
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    # any method placed here will apply to classes, like Hickwall
    def acts_as_something
      send :include, InstanceMethods
    end
  end

  module InstanceMethods
    # any method placed here will apply to instaces, like @hickwall
  end
end
</tt>

With structure you can easily separate the methods that will be used for the class (like <tt>Hickwall.some_method</tt>) and the instance (like <tt>@hickwell.some_method</tt>).

3.1. Add a class method

This plugin will expect that you've added a method to your model named last_squawk.  However, the plugin users might have already defined a method on their model named last_squawk that they use for something else.  This plugin will allow the name to be changed by adding a class method called yaffle_text_field.

To start out, write a failing test that shows the behavior you'd like:

vendor/plugins/yaffle/test/acts_as_yaffle_test.rb

<tt>require File.dirname(__FILE__) + '/test_helper.rb'

class Hickwall < ActiveRecord::Base
  acts_as_yaffle
end

class Wickwall < ActiveRecord::Base
  acts_as_yaffle :yaffle_text_field => :last_tweet
end

class ActsAsYaffleTest < Test::Unit::TestCase
  load_schema

  def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
    assert_equal "last_squawk", Hickwall.yaffle_text_field
  end

  def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
    assert_equal "last_tweet", Wickwall.yaffle_text_field
  end
end
</tt>

To make these tests pass, you could modify your <tt>acts_as_yaffle</tt> file like so:

vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb

<tt>module Yaffle
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    def acts_as_yaffle(options = {})
      cattr_accessor :yaffle_text_field
      self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
    end
  end
end

ActiveRecord::Base.send :include, Yaffle
</tt>

3.2. Add an instance method

This plugin will add a method named squawk to any Active Record objects that call acts_as_yaffle.  The squawk method will simply set the value of one of the fields in the database.

To start out, write a failing test that shows the behavior you'd like:

vendor/plugins/yaffle/test/acts_as_yaffle_test.rb

<tt>require File.dirname(__FILE__) + '/test_helper.rb'

class Hickwall < ActiveRecord::Base
  acts_as_yaffle
end

class Wickwall < ActiveRecord::Base
  acts_as_yaffle :yaffle_text_field => :last_tweet
end

class ActsAsYaffleTest < Test::Unit::TestCase
  load_schema

  def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
    assert_equal "last_squawk", Hickwall.yaffle_text_field
  end

  def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
    assert_equal "last_tweet", Wickwall.yaffle_text_field
  end

  def test_hickwalls_squawk_should_populate_last_squawk
    hickwall = Hickwall.new
    hickwall.squawk("Hello World")
    assert_equal "squawk! Hello World", hickwall.last_squawk
  end

  def test_wickwalls_squawk_should_populate_last_tweeted_at
    wickwall = Wickwall.new
    wickwall.squawk("Hello World")
    assert_equal "squawk! Hello World", wickwall.last_tweet
  end
end
</tt>

Run this test to make sure the last two tests fail, then update acts_as_yaffle.rb to look like this:

vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb

<tt>module Yaffle
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    def acts_as_yaffle(options = {})
      cattr_accessor :yaffle_text_field
      self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
      send :include, InstanceMethods
    end
  end

  module InstanceMethods
    def squawk(string)
      write_attribute(self.class.yaffle_text_field, string.to_squawk)
    end
  end
end

ActiveRecord::Base.send :include, Yaffle
</tt>

Editor's note:
The use of <tt>write_attribute</tt> to write to the field in model is just one example of how a plugin can interact with the model, and will not always be the right method to use.  For example, you could also use <tt>send("#{self.class.yaffle_text_field}=", string.to_squawk)</tt>.

4. Create a generator

Many plugins ship with generators.  When you created the plugin above, you specified the —with-generator option, so you already have the generator stubs in vendor/plugins/yaffle/generators/yaffle.

Building generators is a complex topic unto itself and this section will cover one small aspect of generators:  creating a generator that adds a time-stamped migration.

To create a generator you must:

Add your instructions to the manifest method of the generator

Add any necessary template files to the templates directory

Test the generator manually by running various combinations of <tt>script/generate</tt> and <tt>script/destroy</tt>

Update the USAGE file to add helpful documentation for your generator

4.1. Testing generators

Many rails plugin authors do not test their generators, however testing generators is quite simple.  A typical generator test does the following:

Creates a new fake rails root directory that will serve as destination

Runs the generator forward and backward, making whatever assertions are necessary

Removes the fake rails root

For the generator in this section, the test could look something like this:

vendor/plugins/yaffle/test/yaffle_generator_test.rb

<tt>require File.dirname(__FILE__) + '/test_helper.rb'
require 'rails_generator'
require 'rails_generator/scripts/generate'
require 'rails_generator/scripts/destroy'

class GeneratorTest < Test::Unit::TestCase

  def fake_rails_root
    File.join(File.dirname(__FILE__), 'rails_root')
  end

  def file_list
    Dir.glob(File.join(fake_rails_root, "db", "migrate", "*"))
  end

  def setup
    FileUtils.mkdir_p(fake_rails_root)
    @original_files = file_list
  end

  def teardown
    FileUtils.rm_r(fake_rails_root)
  end

  def test_generates_correct_file_name
    Rails::Generator::Scripts::Generate.new.run(["yaffle", "bird"], :destination => fake_rails_root)
    new_file = (file_list - @original_files).first
    assert_match /add_yaffle_fields_to_bird/, new_file
  end

end
</tt>

You can run rake from the plugin directory to see this fail.  Unless you are doing more advanced generator commands it typically suffices to just test the Generate script, and trust that rails will handle the Destroy and Update commands for you.

4.2. Adding to the manifest

This example will demonstrate how to use one of the built-in generator methods named migration_template to create a migration file.  To start, update your generator file to look like this:

vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb

<tt>class YaffleGenerator < Rails::Generator::NamedBase
  def manifest
    record do |m|
      m.migration_template 'migration:migration.rb', "db/migrate", {:assigns => yaffle_local_assigns,
        :migration_file_name => "add_yaffle_fields_to_#{custom_file_name}"
      }
    end
  end

  private
    def custom_file_name
      custom_name = class_name.underscore.downcase
      custom_name = custom_name.pluralize if ActiveRecord::Base.pluralize_table_names
    end

    def yaffle_local_assigns
      returning(assigns = {}) do
        assigns[:migration_action] = "add"
        assigns[:class_name] = "add_yaffle_fields_to_#{custom_file_name}"
        assigns[:table_name] = custom_file_name
        assigns[:attributes] = [Rails::Generator::GeneratedAttribute.new("last_squawk", "string")]
      end
    end
end
</tt>

The generator creates a new file in db/migrate with a timestamp and an add_column statement.  It reuses the built in rails <tt>migration_template</tt> method, and reuses the built-in rails migration template.

It's courteous to check to see if table names are being pluralized whenever you create a generator that needs to be aware of table names.  This way people using your generator won't have to manually change the generated files if they've turned pluralization off.

4.3. Manually test the generator

To run the generator, type the following at the command line:

<tt>./script/generate yaffle bird</tt>

and you will see a new file:

db/migrate/20080529225649_add_yaffle_fields_to_birds.rb

<tt>class AddYaffleFieldsToBirds < ActiveRecord::Migration
  def self.up
    add_column :birds, :last_squawk, :string
  end

  def self.down
    remove_column :birds, :last_squawk
  end
end
</tt>

4.4. The USAGE file

Rails ships with several built-in generators.  You can see all of the generators available to you by typing the following at the command line:

<tt>script/generate</tt>

You should see something like this:

<tt>Installed Generators
  Plugins (vendor/plugins): yaffle
  Builtin: controller, integration_test, mailer, migration, model, observer, plugin, resource, scaffold, session_migration</tt>

When you run <tt>script/generate yaffle</tt> you should see the contents of your vendor/plugins/yaffle/generators/yaffle/USAGE file.

For this plugin, update the USAGE file looks like this:

<tt>Description:
    Creates a migration that adds yaffle squawk fields to the given model

Example:
    ./script/generate yaffle hickwall

    This will create:
        db/migrate/TIMESTAMP_add_yaffle_fields_to_hickwall</tt>

5. Add a custom generator command

You may have noticed above that you can used one of the built-in rails migration commands <tt>migration_template</tt>.  If your plugin needs to add and remove lines of text from existing files you will need to write your own generator methods.

This section describes how you you can create your own commands to add and remove a line of text from routes.rb.  This example creates a very simple method that adds or removes a text file.

To start, add the following test method:

vendor/plugins/yaffle/test/generator_test.rb

<tt>def test_generates_definition
  Rails::Generator::Scripts::Generate.new.run(["yaffle", "bird"], :destination => fake_rails_root)
  definition = File.read(File.join(fake_rails_root, "definition.txt"))
  assert_match /Yaffle\:/, definition
end
</tt>

Run <tt>rake</tt> to watch the test fail, then make the test pass add the following:

vendor/plugins/yaffle/generators/yaffle/templates/definition.txt

<tt>Yaffle: A bird</tt>

vendor/plugins/yaffle/lib/yaffle.rb

<tt>require "yaffle/commands"
</tt>

vendor/plugins/yaffle/lib/commands.rb

<tt>require 'rails_generator'
require 'rails_generator/commands'

module Yaffle #:nodoc:
  module Generator #:nodoc:
    module Commands #:nodoc:
      module Create
        def yaffle_definition
          file("definition.txt", "definition.txt")
        end
      end

      module Destroy
        def yaffle_definition
          file("definition.txt", "definition.txt")
        end
      end

      module List
        def yaffle_definition
          file("definition.txt", "definition.txt")
        end
      end

      module Update
        def yaffle_definition
          file("definition.txt", "definition.txt")
        end
      end
    end
  end
end

Rails::Generator::Commands::Create.send   :include,  Yaffle::Generator::Commands::Create
Rails::Generator::Commands::Destroy.send  :include,  Yaffle::Generator::Commands::Destroy
Rails::Generator::Commands::List.send     :include,  Yaffle::Generator::Commands::List
Rails::Generator::Commands::Update.send   :include,  Yaffle::Generator::Commands::Update
</tt>

Finally, call your new method in the manifest:

vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb

<tt>class YaffleGenerator < Rails::Generator::NamedBase
  def manifest
    m.yaffle_definition
  end
end
</tt>

6. Add a model

This section describes how to add a model named Woodpecker to your plugin that will behave the same as a model in your main app.  When storing models, controllers, views and helpers in your plugin, it's customary to keep them in directories that match the rails directories.  For this example, create a file structure like this:

<tt>vendor/plugins/yaffle/
|-- lib
|   |-- app
|   |   |-- controllers
|   |   |-- helpers
|   |   |-- models
|   |   |   `-- woodpecker.rb
|   |   `-- views
|   |-- yaffle
|   |   |-- acts_as_yaffle.rb
|   |   |-- commands.rb
|   |   `-- core_ext.rb
|   `-- yaffle.rb</tt>

As always, start with a test:

vendor/plugins/yaffle/yaffle/woodpecker_test.rb:

<tt>require File.dirname(__FILE__) + '/test_helper.rb'

class WoodpeckerTest < Test::Unit::TestCase
  load_schema

  def test_woodpecker
    assert_kind_of Woodpecker, Woodpecker.new
  end
end
</tt>

This is just a simple test to make sure the class is being loaded correctly.  After watching it fail with <tt>rake</tt>, you can make it pass like so:

vendor/plugins/yaffle/lib/yaffle.rb:

<tt>%w{ models }.each do |dir|
  path = File.join(File.dirname(__FILE__), 'app', dir)
  $LOAD_PATH << path
  ActiveSupport::Dependencies.load_paths << path
  ActiveSupport::Dependencies.load_once_paths.delete(path)
end
</tt>

Adding directories to the load path makes them appear just like files in the the main app directory - except that they are only loaded once, so you have to restart the web server to see the changes in the browser.  Removing directories from the load_once_paths allow those changes to picked up as soon as you save the file - without having to restart the web server.  This is particularly useful as you develop the plugin.

vendor/plugins/yaffle/lib/app/models/woodpecker.rb:

<tt>class Woodpecker < ActiveRecord::Base
end
</tt>

Finally, add the following to your plugin's schema.rb:

vendor/plugins/yaffle/test/schema.rb:

<tt>ActiveRecord::Schema.define(:version => 0) do
  create_table :woodpeckers, :force => true do |t|
    t.string :name
  end
end
</tt>

Now your test should be passing, and you should be able to use the Woodpecker model from within your rails app, and any changes made to it are reflected immediately when running in development mode.

7. Add a controller

This section describes how to add a controller named woodpeckers to your plugin that will behave the same as a controller in your main app.  This is very similar to adding a model.

You can test your plugin's controller as you would test any other controller:

vendor/plugins/yaffle/yaffle/woodpeckers_controller_test.rb:

<tt>require File.dirname(__FILE__) + '/test_helper.rb'
require 'woodpeckers_controller'
require 'action_controller/test_process'

class WoodpeckersController; def rescue_action(e) raise e end; end

class WoodpeckersControllerTest < Test::Unit::TestCase
  def setup
    @controller = WoodpeckersController.new
    @request = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new
  end

  def test_index
    get :index
    assert_response :success
  end
end
</tt>

This is just a simple test to make sure the controller is being loaded correctly.  After watching it fail with <tt>rake</tt>, you can make it pass like so:

vendor/plugins/yaffle/lib/yaffle.rb:

<tt>%w{ models controllers }.each do |dir|
  path = File.join(File.dirname(__FILE__), 'app', dir)
  $LOAD_PATH << path
  ActiveSupport::Dependencies.load_paths << path
  ActiveSupport::Dependencies.load_once_paths.delete(path)
end
</tt>

vendor/plugins/yaffle/lib/app/controllers/woodpeckers_controller.rb:

<tt>class WoodpeckersController < ActionController::Base

  def index
    render :text => "Squawk!"
  end

end
</tt>

Now your test should be passing, and you should be able to use the Woodpeckers controller in your app.  If you add a route for the woodpeckers controller you can start up your server and go to http://localhost:3000/woodpeckers to see your controller in action.

8. Add a helper

This section describes how to add a helper named WoodpeckersHelper to your plugin that will behave the same as a helper in your main app.  This is very similar to adding a model and a controller.

You can test your plugin's helper as you would test any other helper:

vendor/plugins/yaffle/test/woodpeckers_helper_test.rb

<tt>require File.dirname(__FILE__) + '/test_helper.rb'
include WoodpeckersHelper

class WoodpeckersHelperTest < Test::Unit::TestCase
  def test_tweet
    assert_equal "Tweet! Hello", tweet("Hello")
  end
end
</tt>

This is just a simple test to make sure the helper is being loaded correctly.  After watching it fail with <tt>rake</tt>, you can make it pass like so:

vendor/plugins/yaffle/lib/yaffle.rb:

<tt>%w{ models controllers helpers }.each do |dir|
  path = File.join(File.dirname(__FILE__), 'app', dir)
  $LOAD_PATH << path
  ActiveSupport::Dependencies.load_paths << path
  ActiveSupport::Dependencies.load_once_paths.delete(path)
end

ActionView::Base.send :include, WoodpeckersHelper
</tt>

vendor/plugins/yaffle/lib/app/helpers/woodpeckers_helper.rb:

<tt>module WoodpeckersHelper

  def tweet(text)
    "Tweet! #{text}"
  end

end
</tt>

Now your test should be passing, and you should be able to use the Woodpeckers helper in your app.

9. Add a Custom Route

Testing routes in plugins can be complex, especially if the controllers are also in the plugin itself.  Jamis Buck showed a great example of this in http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2.

vendor/plugins/yaffle/test/routing_test.rb

<tt>require "#{File.dirname(__FILE__)}/test_helper"

class RoutingTest < Test::Unit::TestCase

  def setup
    ActionController::Routing::Routes.draw do |map|
      map.yaffles
    end
  end

  def test_yaffles_route
    assert_recognition :get, "/yaffles", :controller => "yaffles_controller", :action => "index"
  end

  private

    # yes, I know about assert_recognizes, but it has proven problematic to
    # use in these tests, since it uses RouteSet#recognize (which actually
    # tries to instantiate the controller) and because it uses an awkward
    # parameter order.
    def assert_recognition(method, path, options)
      result = ActionController::Routing::Routes.recognize_path(path, :method => method)
      assert_equal options, result
    end
end
</tt>

vendor/plugins/yaffle/init.rb

<tt>require "routing"
ActionController::Routing::RouteSet::Mapper.send :include, Yaffle::Routing::MapperExtensions
</tt>

vendor/plugins/yaffle/lib/routing.rb

<tt>module Yaffle #:nodoc:
  module Routing #:nodoc:
    module MapperExtensions
      def yaffles
        @set.add_route("/yaffles", {:controller => "yaffles_controller", :action => "index"})
      end
    end
  end
end
</tt>

config/routes.rb

<tt>ActionController::Routing::Routes.draw do |map|
  ...
  map.yaffles
end
</tt>

You can also see if your routes work by running <tt>rake routes</tt> from your app directory.

10. Odds and ends

10.1. Generate RDoc Documentation

Once your plugin is stable, the tests pass on all database and you are ready to deploy do everyone else a favor and document it!  Luckily, writing documentation for your plugin is easy.

The first step is to update the README file with detailed information about how to use your plugin.  A few key things to include are:

Your name.

How to install.

How to add the functionality to the app (several examples of common use cases).

Warning, gotchas or tips that might help save users time.

Once your README is solid, go through and add rdoc comments to all of the methods that developers will use.

Before you generate your documentation, be sure to go through and add nodoc comments to those modules and methods that are not important to your users.

Once your comments are good to go, navigate to your plugin directory and run:

<tt>rake rdoc</tt>

10.2. Write custom Rake tasks in your plugin

When you created the plugin with the built-in rails generator, it generated a rake file for you in vendor/plugins/yaffle/tasks/yaffle.rake.  Any rake task you add here will be available to the app.

Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so:

vendor/plugins/yaffle/tasks/yaffle.rake

<tt>namespace :yaffle do
  desc "Prints out the word 'Yaffle'"
  task :squawk => :environment do
    puts "squawk!"
  end
end
</tt>

When you run <tt>rake -T</tt> from your plugin you will see:

<tt>yaffle:squawk             # Prints out the word 'Yaffle'</tt>

You can add as many files as you want in the tasks directory, and if they end in .rake Rails will pick them up.

10.3. Store plugins in alternate locations

You can store plugins wherever you want - you just have to add those plugins to the plugins path in environment.rb.

Since the plugin is only loaded after the plugin paths are defined, you can't redefine this in your plugins - but it may be good to now.

You can even store plugins inside of other plugins for complete plugin madness!

<tt>config.plugin_paths << File.join(RAILS_ROOT,"vendor","plugins","yaffle","lib","plugins")
</tt>

10.4. Create your own Plugin Loaders and Plugin Locators

If the built-in plugin behavior is inadequate, you can change almost every aspect of the location and loading process.  You can write your own plugin locators and plugin loaders, but that's beyond the scope of this tutorial.

10.5. Use Custom Plugin Generators

If you are an RSpec fan, you can install the <tt>rspec_plugin_generator</tt> gem, which will generate the spec folder and database for you. See http://github.com/pat-maddox/rspec-plugin-generator/tree/master.

11. Appendix

11.1. References

http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-i

http://nubyonrails.com/articles/2006/05/09/the-complete-guide-to-rails-plugins-part-ii

http://github.com/technoweenie/attachment_fu/tree/master

http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html

11.2. Final plugin directory structure

The final plugin should have a directory structure that looks something like this:

<tt>  |-- MIT-LICENSE
  |-- README
  |-- Rakefile
  |-- generators
  |   `-- yaffle
  |       |-- USAGE
  |       |-- templates
  |       |   `-- definition.txt
  |       `-- yaffle_generator.rb
  |-- init.rb
  |-- install.rb
  |-- lib
  |   |-- acts_as_yaffle.rb
  |   |-- commands.rb
  |   |-- core_ext.rb
  |   |-- routing.rb
  |   `-- view_helpers.rb
  |-- tasks
  |   `-- yaffle_tasks.rake
  |-- test
  |   |-- acts_as_yaffle_test.rb
  |   |-- core_ext_test.rb
  |   |-- database.yml
  |   |-- debug.log
  |   |-- routing_test.rb
  |   |-- schema.rb
  |   |-- test_helper.rb
  |   `-- view_helpers_test.rb
  |-- uninstall.rb
  `-- yaffle_plugin.sqlite3.db</tt>


text:
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Kudu.Services.FetchHelpers
{
    /// <summary>
    /// Allow limit amount of action during the given internal
    /// </summary>
    public sealed class RateLimiter : IDisposable
    {
        private readonly int _limit;
        private readonly TimeSpan _interval;
        private readonly SemaphoreSlim _semaphore;
        private int _current = 0;
        private DateTime _last = DateTime.UtcNow;

        public RateLimiter(int limit, TimeSpan interval)
        {
            _limit = limit;
            _interval = interval;
            _semaphore = new SemaphoreSlim(1, 1);
        }

        public async Task ThrottleAsync()
        {
            await _semaphore.WaitAsync();
            try
            {
                await ThrottleCore();
            }
            finally
            {
                _semaphore.Release();
            }
        }

        private async Task ThrottleCore()
        {
            _current++;
            if (_current > _limit)
            {
                DateTime now = DateTime.UtcNow;
                TimeSpan ts = now - _last;
                TimeSpan waitDuration = _interval - ts;

                if (waitDuration > TimeSpan.Zero)
                {
                    await Task.Delay(waitDuration);
                    _last = DateTime.UtcNow;
                }
                else
                {
                    _last = now;
                }

                _current = 0;
            }
        }

        public void Dispose()
        {
            _semaphore.Dispose();
        }
    }
}