text:
Swan's Fine Books
Questions? Call (925) 935-1190 or Email:
Animals and Animal Stories (13)
Architecture and Engineering (7)
Arion Press (21)
Artist Books (70)
Atlases and Maps (11)
Auction Catalogues (6)
Beer Wine and Spirits (14)
Biography including Letters Memoirs and Diaries (76)
Books in Languages other than English (66)
California Private Presses (2)
Californiana and San Francisco (59)
Cartoons and Graphic Novels (2)
Children and Young Adult (86)
Collectibles and Handcrafts (9)
Education and Yearbooks (8)
Eric Gill (21)
Finance and Economics (4)
Fine Press (201)
Fine and Designer Bindings (11)
Geology including Mining and Minerals (1)
Health and Medicine (16)
Humor and Satire (10)
Hunting and Fishing (1)
John Henry Nash (15)
Modern Literature (91)
Mountaineering and Climbing (4)
Natural History and Wildlife (10)
Original Artwork (1)
Politics and Military (23)
Publishers Decorative Trade Bindings (21)
Religion Philosophy Metaphysical (45)
SF Bay Area Local History (3)
Vellum Bindings or Printed on Vellum (6)
WPA (1)
Biography including Letters Memoirs and Diaries
Adele Rosenwald Levy: July 19, 1892 - March 12, 1960
San Francisco: [Privately printed by the Grabhorn Press for Lessing J. Rosenwald in April, 1961], 1961. Limited Edition. Stiff boards. One of 500 copies, octavo size, 47 pp. This publication from the inimitable Grabhorn Press prints the eulogies read at the funeral service for Adele Rosenwald Levy, a philanthropist who..... More
Memorial Tributes to Oscar Sutro
San Francisco: [Privately printed by the Grabhorn Press for the firm of Pillsbury, Madison, and Sutro], 1935. Limited Edition. Hardcover. One of 250 copies, octavo size, 76 pp. Oscar Sutro (1874-1935) was a prominent San Franciscan and member of the San Francisco Roxburghe Club from 1933-1935 and the California Historical..... More
The New Universal Biographical Dictionary and American Remembrancer of Departed Merit; Containing Complete and Impartial Accounts of the Lives and Writings....
New York: Paul R. Johnson, 1808. Full leather. Volume four only [of a four-volume set], octavo size, 498 pp. What we would today likely have called a "biographical encyclopedia", this set presented short biographies of the "most eminent persons in every nation, but more particularly of Those who have signalized..... More
Anderson, David; Adams, Ansel (Photographer)
The Enchanted Galleon
[San Francisco]: Privately Printed by Cecil and James Johnson, The Windsor Press, 1930. Adams, Ansel. Limited Edition. Leather. No. 57 of 60, quarto size, 27 pp., frontispiece signed by Ansel Adams. Robert Louis Stevenson (1850-1894) is best known for his works of fiction such as "Treasure Island", "Strange Case of..... More
Angell, Roger
Late Innings, A Baseball Companion
New York: Simon and Schuster, (1982). First Edition. Hardcover. First printing, octavo size, 429 pp., signed by Roger Angell. An engaging account of then-recent baseball seasons, "Late Innings" provides a celebration as well as information about a beloved American sport, written by Roger Angell (b. 1920), a prolific and acclaimed..... More
Annie Nelles (Dumond)
The Life of a Book Agent
St. Louis: Published by the Author, 1892. 5th Edition. Hardcover. Duodecimo size, 458 pp. "The Life of a Book Agent" is the autobiography of the bookseller Annie Nelles, telling of her escapades and travels as she tries to make a living peddling books; although there is some dispute as to..... More
Atkins, Mary; Reinhardt, Aurelia Henry (Introduction); Uhl, Kathryn (Illustrator)
The Diary of Mary Atkins; A Sabbatical in the Eighteen Sixties
[Oakland, California]: The Eucalyptus Press, Mills College, 1937. Uhl, Kathryn. Limited Edition. Hardcover. One of 500 copies, quarto size, 54 pp. This publication by the Eucalyptus Press, based in Mills College, is a series of letters written by Mary Atkins, founder of the Young Ladies Seminary in 1852, which would..... More
Bamford, Georgia Loring
The Mystery of Jack London
Oakland, California: Georgia Loring Bamford, 1931. Hardcover. Octavo size, 255 pp. The life and work of Jack London (1876-1916) have been the subjects of many critical writings, and here Georgia Loring Bamford (1873-1966), a personal friend of London's, reminisces about London's life, his prominence in the literary circle in San..... More
Beasley, Gertrude; McMurtry, Larry (Afterword); Van Vliet, Claire (Wodcut Illustrations)
My First Thirty Years
[Austin]: The Book Club of Texas [printed at the press of W. Thomas Taylor], 1989. Van Vliet, Claire. First and Limited Edition. Wraps. First edition with woodcut illustrations by Claire Van Vliet, one of 500 copies, unnumbered, octavo size, 244 pp. Edna Gertrude Beasley (1892-1955) was born into the most..... More
Bender, Albert M.
George Sterling, The Man; A Tribute
San Francisco: The Windsor Press, 1929. Limited Edition. Hardcover. No. 10 of 60 copies, sixteenmo size, [11] pp. Poet and playwright George Sterling (1869-1926) was much acclaimed on the Pacific coast; a prolific poet, Sterling was part of the group of writers and artists in the San Francisco Bay Area..... More
Blumenthal, Joseph; Dreyfus, John (Foreword)
Bruce Rogers, A Life in Letters, 1870-1957
Austin: W. Thomas Taylor, 1989. Limited Edition. Hardcover. One of 125 copies in a special binding (total edition 2125), quarto size, 238 pp., signed by author. Bruce Rogers (1870-1957), American typographer and inventer of the "Centaur" typeface (based on the Renaissance-period printing of Nicolas Jenson), is considered by many to..... More
Boswell, James; Napier, Alexander (Notes and Appendices); Napier, Robina (Newly Collected and Edited By)
The Life of Samuel Johnson LL.D. [together with] Johnsoniana; Together with the Journal of a Tour to the Hebrides; Anecdotes of the Late Samuel Johnson LL.D. by Mrs. Piozzi, Richard Cumberland, Bishop Percy and Others Together with the Diary of Dr. Campbell and Extracts from that of Madame D'Arblay
London: George Bell and Sons, 1884. Limited Edition. Hardcover. Number 46 of 104 large paper sets, 5 vols., quarto size, approx. 2,612 pp., signed by printer Charles Whittingham & Co. The quintessential writings, and authoritative texts on Samuel Johnson, LL.D. (1709-1784), whom the OED describes as "arguably the most distinguished..... More
Breen, Patrick; Stewart, George R. (Introduction & Notes); Dean, Mallette (Illustrator)
The Diary of Patrick Breen; Recounting the Ordeal of the Donner Party Snowbound In the Sierra 1846-47
San Francisco: The Book Club of California [printed at the Allen Press], 1946. Limited Edition. Hardcover. One of 300 copies, octavo size, [68] pp. "The publication of the most famous of all documents relating to the Donner Party," published at the centennial of the author's original accounting of the tragic..... More
The Works of Robert Burns; With An Account of His Life and A Criticism on His Writings, Volumes I - IV
Edinburgh: T. Cadell and W. Davies, 1813. Hardcover. 4 volumes (complete), octavo size, 1775 total pp. A cultural icon in Scotland and among the Scottish diaspora around the world, Robert Burns (1759-1796) is widely regarded as a pioneer of the Romantic movement; his poetry is known for using the Scots..... More
Byrne, Muriel St. Clare
The Lisle Letters
[n. p.]: The University of Chicago Press, 1981. Hardcover. Six volumes (complete), octavo size. The correspondence between Arthur Plantagent, 1st Viscount Lisle (ca. 1470-1542), his wife, Honor Plantagenet, and numerous servants, courtiers, and friends, are gathered in these six volumes; the letters provide an intimate look at life during the..... More
Casanova, Giacomo; Machen, Arthur (Translator); Ben Sussan, Rene (Illustrator); Childs, J. Rives (Annotations and Selections)
The Memoirs of Jacques Casanova de Seingalt
[New York], Printed in Haarlem, Holland: The Limited Editions Club, Printed by Joh. Enschede en Zonen, 1972. ben Sussan, Rene. Limited Edition. Hardcover. No. 351 of 1500, quarto size, 521 pp., signed by Rene ben Sussan, with LEC Monthly Letter and announcement. Casanova was recognized by his contemporaries as a...... More
Chevalier, Maurice; Pagnol, Marcel (Preface); Higginson, Cornelia (Translation)
I Remember It Well (INSCRIBED TO MERV GRIFFIN)
(New York): The Macmillan Company (1970), 1970. First Edition. Hardcover. First U.S. edition, octavo size, 221 pp., inscribed by Maurice Chevalier. Perhaps one of the best-known French actors of the first half of the twentieth century, Maurice Chevalier (1888-1972) was nominated for the Academy Award in 1930 for Best Actor..... More
Cushwa, Frank W.
M. Fred Loewenstein, 1910-1933 [A Eulogy]
[San Francisco]: [Privately printed by Edwin & Robert Grabhorn], [1934]. Limited Edition. Hardcover. One of 50 copies privately printed, octavo size, 10 pp. This volume is a eulogy, given in Brookline, Massachusetts by Frank W. Cushwa of the Phillips Exeter Academy in 1933; it was privately printed by the Grabhorn..... More
Dawson, Coningsby; Dawson, W.J. (Introduction and Notes)
Carry On, Letters in Wartime
New York: John Lane Company, 1918. Hardcover. Duodecimo size, 133 pp. Although a reprint, we offer this book for the binding which is simple yet effective. Bound in brown cloth, the front board has a gilt rectangle with gilt lettering and a depiction of a gatling gun, also in gilt;..... More
Dean, Mallette (Book Designer and Decorator)
A Tribute to Else Staudinger
(New York): American Council for Emigres in the Professions, Inc. [Mallette Dean], (1966). Dean, Mallette. Hardcover. Quarto size, [12] pp. A memorial tribute to humanitarian Else Staudinger (1889-1966), who founded and lead The American Council for Emigrés in the Professions, Inc., and worked tirelessly to rescue liberal scholars from Nazi..... More
Deutsch, Monroe E., Dr.
Tribute to Albert Bender
San Francisco: Privately printed by the Grabhorn Press, (1941). Limited Edition. Hardcover. One of 300 copies, quarto size, [7] pp. Albert Maurice Bender (1866-1941), was a noted San Francisco patron of the arts and bibliophile; in addition to being a prominent member of Bay Area book clubs such as the..... More
Dickens, Charles; Smith, Harry B. (Editor)
The Dickens-Kolle Letters
Boston: The Bibliophile Society, 1910. 1st Edition. Hardcover. Octavo size, 102 pp. Limited edition of 483 copies printed for members of the Bibliophile Society, plus a "limited number" printed for the editor. An illuminating correspondence between a young Charles Dickens and his friend, William Henry Kolle, who conducted notes back..... More
Duchow, John Charles; Hammond, George P. (Foreword); Dean, Mallette (Decorations)
The Duchow Journal; A Voyage from Boston to California
[Kentfield, California]: Mallette Dean (laid out at The Allen Press), 1959. Dean, Mallette. Limited Edition. Hardcover. One of 200 copies., quarto size, [66] pp. The lively firsthand account of John Charles Duchow's voyage on the Clipper Staffordshire from Boston around Cape Horn, on his way to California. After the Allens..... More
Emelson, Earl and Gloria (Editors)
Valenti Angelo, The Man and the Artist
Concord, California: Heron House, 2010. First and Limited Edition. Hardcover. One of 250 copies, quarto size, 161 pp. Valenti Angelo (1897-1982) was a well-known and highly regarded twentieth-century illustrator who did so many books for the Grabhorn Press, the early Limited Editions Club books, and many more over a career..... More
Forbes, David W.; Sawkins, James Gay (Artist)
A Pictorial Tour of Hawaii 1850-1852: Watercolors, Paintings & Drawings by James Gay Sawkins; With an account of his life & travels by David W. Forbes
San Francisco: The Book Club of California, designed by Jack Stauffacher of The Greenwood Press, 1991. Sawkins, James Gay. Limited First Edition. Hardcover. One of 400 copies, oblong quarto, [126] pp., with prospectus. James Gay Sawkins (1806-1878) was a virtually unknown English artist who spent several years painting Hawaiian landscapes;..... More
© 2020 Swan's Fine Books. All rights reserved. Site Map | Site by Bibliopolis


text:
A deep dive into George Soros and Hungary's policy of conspiracy
Mar 28, 2018 by Otis Roffman	in Features
George Soros and Hungary's Policy of Conspiracy
B UDAPEST, Hungary—"STOP SOROS!" The backlit, bright blue posters decorate nearly every tram and bus stop in Hungary's capital city. Roughly translated, the signs warn that George Soros plans on letting millions of African and Middle-Eastern people into Hungary. They're the latest in a series of government sponsored billboard campaigns, first issued by the ruling Fidesz party last summer.
Soros is a Hungarian-American billionaire, international philanthropist and the focus of a conspiracy theory propagated by right-wing circles across the West. Hungarian Prime Minister Victor Orbán has transformed conspiracy into policy, using a campaign of misinformation to secure his power, and his success in doing so should concern the rest of the world.
Billionaire, Philanthropist, Conspiracy
George Soros is a multi-billion dollar investor, hedge fund manager, author, and philanthropist. Born a Hungarian Jew in 1930, Soros survived the Nazi occupation of his country and immigrated to England in 1947.
Since then, through deft financial maneuvering, he has risen to become one of the most economically influential figures in the world. The extent of his influence has been more widely known since he made $1 billion by shorting the British pound in 1992. The Open Society Foundations (OSF), Soros' philanthropic organization, ranks second largest behind the Gates Foundation.
Together, the OSF have given away $14 billion over the past thirty years, and operate in 100 countries. Soros funded scholarships for black South Africans in 1979. In 1984, Soros established a foundation to promote the growth of liberalism in Hungary as the Communist era came to a close.
After the fall of the Berlin wall, Soros financed travel grants for students and worked to support watchdog organizations that promoted good governance and a preservation of human rights in Eastern Europe. In 1991, he founded the Central European University in Budapest.
In America, Soros is one of the largest funders of the Democratic Party. He spent $27 million in an effort to prevent the re-election of George W. Bush, angering conservatives.
George Soros is an easy target for paranoia and suspicion. He's a wealthy Jewish globalist, capitalist and progressive. A common anti-Semitic myth is that of the "Humanitarian Jew" whose philanthropy is just a disguised plan for global domination. His antagonists rarely invoke his Jewish identity outright, but they don't need to. Describing him as "cosmopolitan," a term invoked by Stalin as part of a year-long campaign to purge the Soviet Union of dissident voices, but which largely targeted Jews, is enough.
Suspicions about Soros' supposed agenda came first from American leftists opposed to globalization, but soon found a home on the opposite end of the spectrum. Right-wing pundits have held up Soros as the perfect example of a radical, leftist infiltrator seeking to undermine Western American values.
Glenn Beck referred to Soros as the "Puppet Master" of a "One World Government," recently hypothesizing that he orchestrated the Women's March on Washington. Soros has been accused of funding Black Lives Matter and Antifa movements in order to undermine American society.
Congressman Paul Gosar of Arizona accused Soros of orchestrating the events at Charlottesville last August. Rush Limbaugh claimed Soros was behind Colin Kaepernick's NFL protest. Roy Moore alleged that Soros was paying women to falsely accuse him of sexual assault. Breitbart referred to the OSF as the "Death Star."
On a global scale, Soros' demonization has coincided with the rise of "illiberal democracies," governments that are democratically elected but largely ignore the constitutional limits of their own power.
These countries have strategically used the Soros conspiracy to delegitimize dissent and justify stringent nationalism. Uprisings and grassroots efforts against the rule of law are painted not as expressions of popular opinion, but rather the work of a shadowy, globalist puppet-master seeking to destabilize democracy.
Such a narrative encourages suspicion, engenders confusion and validates authoritarian action. Nowhere has the Soros conspiracy been more heavily mobilized than in Hungary, the country of his birth.
Orbán's Conspiracy Policy
Prime Minister Viktor Orbán is head of the ruling Fidesz party in Hungary. Elected in 2010, Orbán and Fidesz party have been consistent in their opposition to refugee immigration and their disdain for the European Union.
In 2016, Hungary granted protection to just 550 immigrants, one of the lowest rates on the continent, and refused to accept a single person from the 1,294 quota allocated by the European Commission in 2015.
Soros, meanwhile, has made a series of statements over the past few years in favor of refugee resettlement in the US, Canada and Europe. In an article in the journal Foreign Policy, Soros suggested Europe take 300,000 immigrants from Jordan, Lebanon and Turkey per year. Like the Hungarian government, he stresses that countries' participation in such a program would need to be voluntary, not compulsory.
Regardless, Orbán has since worked hard to demonize Soros in the public eye. The aim has largely been to paint Soros as the engineer of a master plan to abolish immigration quotas to flood and overwhelm Hungarian culture with Middle Eastern and African migrants.
Orbán is up for re-election in April, and has made the so-called Soros plan a central pillar of his campaign.
The Hungarian government spent 5.7bn forints ($21 million) on its first anti-Soros poster campaign last year. Posters displayed a grinning Soros with the text, "Don't let Soros have the last laugh." Many were soon covered in anti-Semitic graffiti.
The Fidesz government came under fire for a "national consultation survey" that many human rights groups and E.U. officials accused of being "hatemongering" propaganda. The seven-question survey asked Hungarians whether or not they agreed with different parts of the Soros plan. The statements made about the supposed plan are leading, if not outright negative.
Human Rights Watch, partly funded by the OSF, argued that the survey put forward a "distorted half-truth" about Soros and the E.U.'s policy objectives. The survey's fifth question claims that Soros wants immigrants to receive lighter sentences than Hungarians for committing the same crimes. The Human Rights watch said the claim was "incendiary and false."
Truth doesn't matter if you control the news. Since his election in 2010, Orbán has restructured Hungary's media landscape in order to skew coverage in favor of the Fidesz party. This has affected public, legacy and online media. The pro-government bias has only become more acute since the seizure of the print market and the closing of Hungary's largest opposition newspaper.
Orbán has created a pro-government media conglomerate fueled by lucrative state advertising contracts rather than the market. This has allowed Fidesz to spread misinformation about Soros and its other political opponents under the guise of government-sponsored public information campaigns.
A poster presently in circulation at public transit stops and billboards depicts Hungary's opposition leaders arm in arm with a grinning Soros. They are photoshopped together, holding wire-cutters beside a dismantled chain-link border fence.
Orbán has taken a far-right conspiracy theory and successfully transplanted it into the center of Hungary's public discourse. The Soros plan is now discussed within the government as a matter of serious factual concern.
In January, Fidesz proposed the "Stop Soros" bill. The legislation requires organizations determined to be in support of illegal immigration through use of foreign financial resources to register with and report their activity to the government. Such organizations' foreign funding would then be taxed by 25%, and the money used to fund border protection. The bill would also make it possible to issue restraining orders against those participating in illegal immigration.
Justice Ministry Undersecretary Pál Völner stated that Hungary's judiciary system needed to rid itself of foreign-financed self-styled rights groups. He claimed such groups were connected to the OSF and serving foreign interests by coercing judges to consider "outside factors." Völner stated that such organizations "are actually modern era mercenaries and many are connected to the American billionaire George Soros."
Minister of Agriculture Sándor Fazekas stated that the Soros plan would undermine the very traditional dietary habits of the Hungarian people. "Soros wants Hungarians to eat insects…Hungarians don't eat reptiles and insects."
Dividing (and Conquering) Reality
It is true that George Soros, like the Koch Brothers and Bill Gates, wields an immense amount of financial influence on the global market, and uses his Open Society Foundations to actively support liberal policy in countries around the world. The power multi-billionaires have over the international community is an important subject that's worthy of serious consideration.
But what Orbán is doing in Hungary is a distinctly separate issue. The prime minister is not leading a discussion on the usage of personal wealth at a global scale, but manipulating a conspiracy theory in order to further justify the nationalist actions of his party and its continued consolidation of power.
Fidesz is using misinformation as a weapon. This isn't a new strategy. Government propaganda and distortion of truth have long been used to rally support for ruling institutions. What's uniquely concerning is the newfound ease with which reality can be manipulated in the Internet age.
Most people get their news from social media sites like Facebook or Twitter, private companies under no obligation to screen their content for false information. Social media sites were never designed to be news sources, they were designed to cater to their customers, and they do so by using algorithms to deliver content they determine each user wants to see more of.
This has had a splintering effect on mass consciousness. People can no longer agree on the reality of facts because every circle of society has an entirely different idea of what the primary subject of discourse even is.
Venture into a part of the Internet populated by people who don't share your political views and you will be struck with just how alien the topics being discussed appear. There are events, ideas, and memes that are completely unfamiliar to you, yet are being spoken about as if they're the central concern of the culture at large.
With mass consciousness in such a splintered state, it's much easier now than ever before for misinformation to be spread and believed. Hungary's government controls the majority of all print and online press, and is able to flood the national discourse with their preferred narrative. If social media sites see profit in it, they will help spread the same misleading information to the Hungarian people throughout their daily life.
The effect of Orbán's misinformation policy on the Hungarian population is still not entirely clear. 59 percent of respondents in a survey of Hungarians across the political spectrum stated that they did not believe there was a Soros plan. However, another survey found that 4 out of 5 Fidesz voters believe that there is a George Soros party running in the upcoming election.
What's frightening is that it largely doesn't matter whether or not everyone in Hungary believes that Soros is a deep state mastermind. All that's important is that people are speaking about it enough so that it remains the center of political discourse, giving legitimacy to what was once a far-right conspiracy theory, functioning now as a tool for justifying increasingly stringent nationalist policy.
Hungary is not alone in its usage of the Soros narrative. Right-wing governments in Romania, Bulgaria, Macedonia, Serbia, and Slovakia have all used the conspiracy theory to rally support from their base, and to undermine pro-democracy organizations in the region that might threaten their continued power.
The United States is not exempt. In March 2017, a group of GOP Senators asked Secretary of State Rex Tillerson to investigate the extent to which US government funds were being given to the OSF, hypothetically allowing Soros' organization to "impress left-leaning policies on sovereign nations, regardless of their desire for self-determination."
The State Department declined to investigate, but the nationalist overtones in the request, made by elected officials, are cause for serious concern.
In November 2017, Rep. Steve King, a close congressional ally of President Trump, re-tweeted a quote from Orbán, in which the Hungarian Prime Minister argued, "a nation which expects its biological survival from immigrants won't survive."
King's tweet praised Orbán's statement. "Prime Minister Viktor Orbán has uttered an axiom of history and of humanity. Western Civilization is the target of George Soros and the Left."
There is no reason that a narrative as false as the Soros plan couldn't be employed in the United States. We've seen how easy it has become to corrupt and manipulate truth. Under Trump, America has joined in the global swing towards authoritarianism, and every citizen needs to be paying especially close attention to the source and inherent bias of accepted facts. If we continue to allow ourselves to be divided on the nature of reality, fighting with each other about what we should be talking about, we will be conquered by those whose only truth is power.
Tagged with: Conspiracy, Hungary, Soros
One thought on "A deep dive into George Soros and Hungary's policy of conspiracy"
Said says:
So you're praising Soros who you even admit has clear political bias?
Why doesn't he donate to help white South Africans today? Where are his NGOs to speak out against blatant discrimination that becomes more focused and harsh by the year in SA? He sides with the Democrats. He sides with far-left NGOs in places like Ukraine and Hungary. Think about what you're even saying. This is a billionaire. He should not have a right to interfere in other countries, especially to promote social change which harms other groups of people. He can promote social change in Hungary, which is ethnic-cleansing against Hungarians, but where will he be when this change he advocated for comes about and there are grooming gangs, terror, and hate like in western EU countries? Is he then going to flip sides and protect the Hungarians who live in their own homeland, but who are now victims because of his policies? He has no right whatsoever to trying and commit genocide against white people.
Categories Select Category Arts Features News Op-Ed Sports Uncategorized

redpajama_set_name: RedPajamaCommonCrawl


John's writing style is a cross between the wry humour of a Spike Milligan autobiography, and the introspection of the classic account of a westerner's journey in the Japanese martial arts given by C. W. Nicol's Moving Zen. We are lead easily through John's first martial arts training as a private in the NZ Army in the 1950, and later his time in the Canadian Army and a posting in Germany. For readers whose connection, and therefore interest in Jarvis Sensei relate to his role in karate, we have to wait until chapter 4 for his discharge from the army and his involvement in a London self defence club under the instruction of a brown belt in Judo. Lack of progress there prompted John to seek instruction at the London Judo Kai – where he was instead directed to a newly formed karate class. The tempo of training was soon raised with the arrival from Japan of Steve Arniel, and John gives us a fascinating account of the early days of Kyokushinkai in the UK.
With letters of introduction from Arniel, John departed for Japan to start his training in the Kyokushinkaikan under the Korean-born Mas Oyama. Full time karate training was apparently not enough and John also started training in jodo (short staff) and iaido (the art of drawing and cutting with the katana, the Japanese long sword). As one of the very few Westerners to be training under Oyama Sensei, John was often singled out for attention – from the Time Life photos of him meditating under winter waterfalls, to his appearance in early Kyokushinkai training manuals as the black gi-wearing recipient of numerous painful techniques, delivered of course by a Japanese karateka in a white gi. John's training in Japan concluded with his grading to sandan and his completion of the gruelling 100 man kumite – only the fifth person to do so.
His return to New Zealand marked the establishment of the Rembuden Institute of Martial Arts and Ways, and as well as overseeing this fledgling organisation and the gradual building up of membership in New Zealand and Australia through demonstrations and competitions, John somehow found enough hours in the days to maintain his 15 karate kata, 36 jodo kata, and 24 iaido kata. There is an interesting account of the break up within Kyokushikai with the departure of the organisation's number 2 Nakamura Sensei to form Seido Karate. By this time John had become concerned at the lack of a "central tree of knowledge" once Oyama had ceased formal training, and particularly by the way the Kyokushinkai kata were being subject to constant change by instructors; this, and the virtual absence of any bunkai (kata applications) practice, meant kata was being reduced to a meaningless dance. Clearly a change was necessary and John resigned from Kyokushinkai in October 1976 and turned his attention to finding a new style.
John is too much of a gentleman to write an exposé of the personalities and politics that seem to bedevil hierarchical organisations such as those in the martial arts. For example, of Chinen Teruo, once the number 2 Goju Ryu Sensei out of Japan, he writes only that he was suspended for trying too hard to be number 1. Nor do we hear much about politics of the New Zealand Okinawan Goju Ryu organisation, where he diplomatically restricts his comments to the effect that as head of the organisation the necessity of dealing with periodic "bushfires" left a bitter taste in his mouth. These, together with health concerns, lead to his decision to cease formal training and to leave the organisation.
Jarvis Sensei's autobiography is essential reading for anyone wanting to know more about the history of karate in New Zealand. In addition, his insights into aspects such as maai (combative distance), and his discussion of the distinction between bugei (martial arts) and budo (martial ways) are real gems, serving to remind us what we all lost following his decision some twenty years ago to stop karate teaching.


text:
Skip directly to: Main page content
Search UC Davis
UC Davis Stories
Who's Celebrating?
Year of Service
UC Davis Centennial
UC Davis Centennial > Looking Forward
Medical genomics:
Connecting genes to health
(Windows Media video, 10 min 24 sec)
In this video, Ray Rodriguez, professor of molecular and cellular biology and a pioneer in genetic engineering, talks about new discoveries scientists are making in regards to food and race. He discusses how our ancestry can determine how likely we are to suffer from some chronic, common diseases — and how future generations can change those risks. [ more… ]
See also: Medical genomics: Connecting genes to health (Quicktime, 10 min 24 sec)
Andy Fell, producer; Ken Zukin, videographer
Media player downloads and troubleshooting
Download: Windows Media Player (free)
Download: Quicktime (free)
If you are trying to use the Quicktime version for this web video but it reverts to RealPlayer and won't open, here's a solution: First right click (or control-click) on the Quicktime video link and copy the link location. Then start up your Quicktime application. Under "File" go to "Open URL" and paste in the Quicktime URL you have copied from this page.
Celebrating UC Davis' centennial is both about remembering the past and planning how our university can build on those successes for the next 100 years. Today UC Davis is leveraging its world-class programs in environmental sciences, engineering, management, the arts, law, viticulture and enology, life sciences and many more into a future that promotes rigorous scholarship while serving the public.
Here are some programs on our front burner.
'Waterproof' rice passes field tests
New flood-tolerant rice varieties, developed by an international team of researchers, including scientists at UC Davis, have passed field tests and are expected to soon be available to farmers in Bangladesh and India.
UC Davis rebuilding honey bee program
With California's honey-bee industry now facing challenges ranging from mites to small hive beetles to colony collapse disorder, UC Davis is revitalizing its honey-bee research program, the oldest such program in the nation.
Management students reap benefits from visiting executives
One of the nation's most influential money managers is investing in more than the markets — he's teaching a class of future business leaders in the Graduate School of Management.
Chancellor candidate named
UC President Mark Yudof has recommended Linda Katehi — an accomplished professor of electrical and computer engineering and provost of the University of Illinois at Urbana-Champaign — to become chancellor of UC Davis.
Strongest freshman class
Almost 100 years ago, the first class of 28 degree-seeking students began their studies at what became the University of California, Davis. And now, as UC Davis celebrates its centennial, the university boasts its strongest freshman class and overall record enrollment of 31,426.
Our future campus
The future look of UC Davis' nearly 100-year-old campus connects place to people — and to our academic vision, according to the new video, "Building a Connected Campus," created to tell the story.
Sustaining UC Davis
Produced under the direction of the campus's Sustainability Advisory Committee, this 20-minute video takes a look at the steps UC Davis has and will take down the path toward a sustainable college community.
Windows Media video, 20 min 2 sec
Quicktime, 20 min 2 sec
Underserved West Sacramento students get a new 'early college' school
Hoping to give underserved kids a head-start on college, UC Davis has joined with Sacramento City College and the Washington Unified School District in West Sacramento to launch an innovative charter school this fall.
Chevron gives $2.5 M to advance energy leadership
Chevron Corporation has given UC Davis $2.5 million to create a permanent leadership position for the campus's Energy Efficiency Center. The person appointed to the Chevron Chair in Energy Efficiency will direct the center, which was established in 2006.
UC Davis launches energy efficiency center
Gov. Arnold Schwarzenegger comes to UC Davis to celebrate the establishment of the country's first center of excellence in energy efficiency. The new center is dedicated to helping speed the transfer of new energy-saving products and services into the homes and lives of Californians.
$16M to solve malnutrition
Thanks to a $16 million grant from the Bill & Melinda Gates Foundation, UC Davis will lead a coordinated research to prevent childhood malnutrition in developing countries. Our researchers will lead an international network of researchers at public and private institutions in Burkina Faso, Finland, France, Ghana, Malawi and the United States.
The Betty Irene Moore School of Nursing is launched
Continuing its commitment to improving the quality of patient care and fostering nursing excellence through education, the Gordon and Betty Moore Foundation gives $100 million in founding support for a new nursing school.
Providing a bridge to college
Thanks to leadership from the Chicana/o Studies Program, UC Davis students are providing a bridge to college for low-income students in Davis. The college students, most of whom are humanities majors, are improving the academic achievement for elementary school students — thus paving the way for a better-educated workforce in the 21st century.
Experts detail new standard for cleaner transportation fuels
University of California experts, including transportation-energy leader Dan Sperling of UC Davis, create a much-anticipated blueprint for fighting global warming by reducing the amount of carbon emitted when transportation fuels are used in California.
Copyright © The Regents of the University of California, Davis campus, 2009. All Rights Reserved.


You probably realized by now that I'm in love with our planet and that I keep looking for ways to protect it. Well here's a very useful item that I discovered while I was doing research for my bachelor's thesis.
I'm talking about beeswax wraps. They can be used to wrap up food or to cover bowls instead of using plastic wrap and you can reuse them up to a whole year! Now here's a great invention to help us become more eco-friendly in our everyday lives.
I've never been a plastic wrap person, I try to use containers with lids or to simply put a second bowl on top of the first one. But those beeswax wraps… they're taking it to a whole new level. They're not only very handy, but they also look nice.
Plus they're made of beeswax and I LOVE bees. They are the most interesting and extraordinary little beings and so important, not to say indispensable, regarding biodiversity. Sadly, some bee species are nowadays endangered, the exact reasons remain debatable. We can say for sure that the so-called Varoa mite as well as pesticides play an important role in the extinction of bees. I'd like to raise awareness, since a lot of people are not aware to the importance of bees and potential consequences resulting from their disappearance.
I actually took beekeeping trial courses and I will keep bees as soon as I've finished my master's degree. If you're interested in this subject, you might look for apiarists in your area, sometimes there are even clubs that organize free courses.
But let's get back to those awesome wraps. They're quite expensive so I decided to make my own. There's this really nice organic beekeeper who gave me a big piece of organic beeswax in exchange for some ice cream lol. If you don't know such a nice person, don't worry, you can buy it here.
Preheat oven to 100° C.
Cut fabric in shape. I chose three different ones and I cut them into 4, 8 and 12 inch squares.
Place parchment paper on a baking tray. Place your fabric on the parchment paper. You don't want them to overlap.
Now grate your organic beeswax and trickle evenly on top of your fabric.
Put it all into the oven until the wax has melted. This should only take about 5 minutes.
Take out of the oven, remove your beeswax wraps from the tray and let them cool in the air (you'll only need to hold them for a few seconds and they'll be dry and ready to use).
Et voilà! Use them to wrap your food or to cover bowls. I also think that these beeswax wraps are a very special gift idea.


Michael Tarnat (born 27 October 1969 in Hilden) is a German former footballer, formerly employed by Bayern Munich as the leader of the U12–U16 youth teams for five years. He is a left-footed full-back who has also played wingback and defensive midfield. His nickname is "Tanne", meaning "fir" in English. He ended his career with Hannover 96, having previously played for MSV Duisburg, Karlsruher SC, Bayern Munich and Manchester City. A veteran of 19 caps for Germany, Tarnat also participated in the 1998 FIFA World Cup. He is renowned for his powerful free kicks and similar long-shots with his strong left foot.
Tarnat started to play football at the club SV Hilden-Nord before starting his professional career in 1990 when he joined MSV Duisburg. He made his Bundesliga debut on 2 August 1991 in Duisburg's home match against VfB Stuttgart and went on to make 58 appearances for the club in the league in the following two seasons, also scoring his first Bundesliga goal in Duisburg's 1–1 draw away against VfB Leipzig on 27 August 1993. He moved to Karlsruher SC in the summer of 1994 and grew into one of the team's stars, alongside Thomas Häßler and Thorsten Fink. His trademark was his super-hard left-footed shot, which made him one of the best free-kick takers of his Bundesliga generation. In three seasons with KSC, Tarnat made 81 Bundesliga appearances and scored seven goals for the club in the league. In 1996, he was called up into the German national team for the first time and made his international debut in Germany's 1998 World Cup qualifier against Armenia on 9 October 1996.
In the late years, however, Tarnat became somewhat injury-prone, which effectively ended his international career in 1998 after 19 caps and caused Bayern to bench him often. In six seasons of playing for Bayern, Tarnat made 122 Bundesliga appearances and scored eight goals for the club in the league. He made a move to Manchester City in 2003, making 32 Premier League appearances and scoring three goals for the club in the league before returning to Germany by signing with Hannover 96 a year later, where he has been playing ever since. Even at an advanced age, Tarnat is still a respected full-back and has always been a regular in the Hannover 96 team. On 17 May 2009, it was announced that he would retire from professional football. He played his last game for Hannover on 29 July 2009 vs Arsenal. In August 2009, Tarnat returned to FC Bayern Munich as a talent scout.
I found an interesting interview that reveals something about our youth program changes. He seems to have a close relationship with Hoeness and said that Uli wanted to keep him.
"strong left foot" where are such players in today's football?
ramsej84 wrote: "strong left foot" where are such players in today's football?
I was refering to the hammer type..
so and so ... still different.
00:46 - will never forget that goal... even if Effenberg's in the same game tops it. Jancker scoring from the ground, classic.
Yup..but you have not seen his left foot shots?


text:
Roof Recoating Enhances Workplace Safety
Applying traditional protective coatings to machinery can stop production for days, since they usually require substantial metal surface preparation, a prime coat and two topcoats with at least eight hours of drying time, also known as minimum recoat time, between each.
Seeking a better solution, Harris recently used a newly approved corrosion coating to enhance workplace safety and return an approximatley 35,00 sq. ft. floating roof tank to service.
Many industrial facilities such as petrochemical plants must remain safely, efficiently operational with virtually no downtime, unplanned maintenance, or replacement for decades. To protect equipment from corrosion, traditionally three separate coatings are used, often in a zinc, epoxy, urethane combination. While this is common, it is far from optimal.
From a safety standpoint, urethane topcoats can be slick walking surfaces, prone to slip and fall hazard, particularly if walking surfaces are wet, moist, or humid. Typical exterior coatings such as epoxy and urethane can emit hazardous VOCs, HAPs, and odors during application, and may have flash points that require extra distance or caution when used around hot or flammable processes.
Applying traditional coatings can stop production for days, since they usually require substantial metal surface preparation, a prime coat and two topcoats with at least eight hours of drying time, also known as minimum recoat time, between each. Bad weather or environmental conditions can further delay production, and even require re-blasting the surface if an environmental delay exceeds the coating's maximum recoat time.
"Traditional coatings in some cases have significant drawbacks," says Jay Harris, a project manager for Mobley Industrial Services, a multi-service specialty contractor serving the chemical, petrochemical, and refining industries. "They need extensive surface prep and must meet specific conditions, including minimum and maximum recoat times, before they can be applied. Due to this, it can take a few days or longer to apply three coats, depending on the size of the job and environmental conditions. To avoid overspray issues, hand rolled application is often required."
Seeking a better solution at a petrochemical facility on the Gulf Coast, Harris recently used a newly approved corrosion coating called EonCoat, by a Wilson, N.C.-based company of the same name, to enhance workplace safety and return an approximately 35,000 sq. ft. floating roof tank to service in about half the time required by typical coatings.


If you are a guest attending a wedding and decide that you might like to bring to the wedding or perhaps if you decide you would like some flowers to wear, then Inverness flowers is just the right place for you. There are lots of lovely bouquets and flower arrangements on offer – both in the engaging purples range as well as all our other floral designs – which are the perfect gift to give to the newlyweds on this special day. As part of the engaging purples collection, there are several lovely flowers to wear pieces which include boutonnieres, corsages – including a charming wrist corsage with a dendrobium orchid – and even garlands. So no matter what it is you're after, Inverness flowers have the perfect floral designs for you. All the flowers used for the engaging purples range have been cultivated and then imported to us all the way from Holland, the western region of the Netherlands. This region is renowned worldwide for cultivating some of the best flowers in the world so you can be rest assured that you'll receive nothing but the best quality from Inverness flowers. Once your order has been placed with Inverness flowers, we take care of the rest. One such way Inverness flowers aims to take care of your orders as fully as possible is by offering all customers several different flower delivery services. There are three primary flower delivery services on offer at Inverness flowers which are standard, same day and next day. All flower delivery services are available Monday to Saturday, from 9:00AM to 5:30PM. The same day flower delivery is particularly handy if you decide last minute that you require gifts as it allows your order on the day of the delivery. Please note that in order to use this service your order must be placed before 3PM.
Not only does Inverness flowers provide you with stunning flowers but there is also a gifts range on offer which can be used for the wedding itself as well as by guests. There is a delectable range of wines and champagnes all rich in taste available at Inverness flowers which can be ordered for the wedding ceremony or as a gift to bring with you to the wedding. There are also deliciously scrumptious chocolates available as well as vibrant balloons. The Inverness florists are able to arrange charming balloon bouquets which usually feature three blank balloons and three with a message fitting for your occasion such as 'Congratulations!'. The gifts range also includes some lovely, fluffy teddy bears; candles which can also be incorporated into arrangements and fruits baskets which the Inverness florists arrange, using fresh and succulent fruits. If you are unsure as to what you would like – whether it be a gift, bouquet, arrangement or any other items we offer – please do not hesitate to get in touch with the shop. You are able to email or phone Inverness flowers as there is always someone available to answer any and all of your questions. You are also able to book a wedding consultation where you can discuss and plan what sort of flowers you would like to be used for your wedding and so on. You, the customer, are the number one priority at Inverness flowers which is why Inverness flowers do everything within our power to provide you with nothing but the best possible service.


Samsung Galaxy Gear S3 is a smartwatch that goes big in a bid to dethrone the Apple Watch in the industry. This is an impressive timepiece that offers all the basic things nearly any other smartwatch out there can, but it comes with a much longer battery life, the high resolution OLED screen and GPS.
Samsung India today launched its latest smartwatches Gear S3 classic and Gear S3 Frontier in the country and the new wearable devices will be available for $428 from January 18. Previously, the Tizen-powered smartwatches went on sale in the US in November 2016.
Samsung Galaxy Gear S3 combines perfectly timeless watch design and high-end wearable technology including water resistance, dedicated app store, built-in speaker, making it one of the most desired smartwatches in the market and Samsung's strategic plan to conquer the success of the Apple Watch. Both Gear S3 versions come with a 1.3 inch circular Super AMOLED display, dual-core processor, 768 GB of RAM and 4GB of internal storage.
The Classic Gear S3 sports an elegant look with its stainless steel body and circular buttons, making it one of the best smartwatches for the office and business meeting. The Frontier Gear S3, on the other hand, is more sporty thanks to its 3G/LTE capabilities and microphone for voice calls. One of the best things when it comes to Samsung Gear S series of smartwatches is its rotating bezel. With Gear S3, Samsung has made it even more useful; you now can rotate the bezel to answer or reject calls right from your wrist, eliminating the need to use your phone, but what makes the Gear S3 stand out is that it's now open to third-party developers, meaning that you now can rotate the bezel to control more things with your watch.
Samsung Gear S3 now supports iOS platform thanks to new accompanying apps, meaning that you can pair your iPhone with one of Samsung's wearable devices to track your fitness like route traveled, running pace, calories burned and heart rate.
Are you planning on getting the Gear S3 when it goes on sale in India? Let us know in the comments below.


text:
Book Review: We Have Always Been Here by Lena Nguyen
July 15, 2021 September 4, 2021 ~ kat
Title: We Have Always Been Here
Author: Lena Nguyen
One Sentence Summary: Grace Park is one of thirteen humans sent to a new planet called Eos alongside an android crew, but, when they arrive, humans and androids alike begin to change, and it might have to do with odd metallic structures that may or may not actually exist on the surface
We Have Always Been Here combines science fiction with psychological thriller to get a wholly eerie and horrifying story of humanity and the lengths people and synthetics alike with go to in order to be free. There are shifting alliances and androids becoming almost human, secrets at every turn and in every heart, and a desperate psychologist who is out of the loop, but may be the only one who can save them. Frankly, this is a strange story, but was so impeccably done that it feels plausible, that it could be our future. There's a a dystopian feel to it with the Interstellar Frontier (ISF) regulating most of the population on Earth and across the interplanetary colonies, but it's mostly about a confined group of humans and androids who experience some very disturbing things.
Extended Thoughts
Psychologist Grace Park has always been more than a little different. In a time where most of space has been colonized and all of it run by the ISF, she is Earth-born and not conscripted to serve the ISF. But she also has a strong affinity with the androids, preferring their company to that of humans.
When a new planet is discovered, a crew of thirteen humans as well as an android crew are sent to Eos to survey it for suitability for colonization. That's all Park knows, making her an outsider. But even before they each the space around Eos, strange things start happening on board and it turns out quite a lot is on a need-to-know basis, and Park is not one of the ones who needs to know, creating friction between her and the rest of the crew, almost all of whom are conscripted.
The humans begin to experience strange waking nightmares. The androids seem to be taking on human characteristics. There's fear and paranoia and shifting ship hallways. And the only one who has a chance of piecing it together is Park, someone the androids trust, but the humans don't.
We Have Always Been Here is a seriously creepy read. It certainly freaked me out several times, but didn't scare me to the point of needing to turn on all the lights (I scare far too easily). It's the kind of story that takes hold of you and refuses to let go. There are some slow parts, but the story relentlessly drives forward, the story altering alongside the crew and the ship. There are twists and turns and literally no idea of who to trust.
The world is far flung, encompassing all known space. It's rare to come across a completely unknown and new planet. There are colonies seemingly just about everywhere, and they are the future for humanity to survive. The history behind it and how Earth is becoming increasingly uninhabitable is incredible, giving We Have Always Been Here a dystopian edge without actually really seeming dystopian with the ISF ruling the universe with an iron fist. Since moving off Earth is extraordinarily expensive, one can become conscripted to the ISF so they basically own you and keep your family hostage. This created an amazing tension between Park and the rest of the crew, who are almost all conscripted. They don't understand her and she doesn't understand them.
While the world is incredible, I really enjoyed the smaller world created on the ship. There's distrust for the androids and Park's affinity to them is noted as suspicious, so, by extension, the crew doesn't trust her. She's an outsider from whom everything about why they're on Eos is kept. It made for something of a mind bending experience as she seems to be chasing smoke and mirrors, but there's also really something there. The characters all really came to life, even the androids. All of them had distinct personalities and feelings and beliefs. I loved that they all kept something close to their chests, that some knew more than others, that they were so divided from the very beginning. I loved that I could understand them all, feel sympathetic towards them all, and feel indignant on Park's behalf. Even the androids were incredible. As the story went on, they each developed distinct personalities and beliefs. They created a society of their own, which was really incredible, though also kind of freaky.
Told as something of a three-pronged story, it focuses on the main story of what's happening to the crew as well as Park's upbringing to showcase where she comes from and the division between Earth-born and conscripted, and transcripts of the two men who originally discovered Eos. They're loosely wound together, but it becomes tighter by the last third, so the first two-thirds sometimes felt a little slow and not quite as interesting. There were long interludes about Park's childhood that felt necessary, but maybe a little too long at the same time as I often lost the thread of the main story. But I did really enjoy seeing how it all came together to be perfectly braided.
We Have Always Been Here is an incredible story of what it means to be human, of how humans can be divided against each other. It's creepy and atmospheric and so full of twists and turns it sometimes made my brain spin. But it's also an incredible read, one that continually moves, continually offers tidbits while also holding all the cards close. I loved never being a step in front of Park, of having to explore and learn things with her. In many ways, I feel like Nguyen drops the reader into Park's mind and body in order to lead us through this crazy, creepy, incredible story.
How many cups of tea will you need?
Get your copy (The Lily Cafe is NOT an Amazon affiliate)
Thank you to NetGalley and the publisher for a review copy. All opinions expressed are my own.
Head over to the Bookshelf to check out my reviews of books from the Big 5 and self-published, indie, and small press books.
Posted in Reviews	Book reviewDawlena nguyenpsychological thrillerScience FictionThe Lily cafewe have always been here
‹ PreviousSisters of String and Glass, Part 26
8 thoughts on "Book Review: We Have Always Been Here by Lena Nguyen"
bitsanddragons says:
Fascinating. The plot reminds me to "The Book of Strange New Things" https://www.theguardian.com/books/2014/oct/26/book-of-strange-new-things-michel-faber-review
I'd have to say it does sound like there are some similarities between them. It does sound interesting.
Bertie | LuminosityLibrary says:
I'm really excited to read this one!
I hope you enjoy it! It's a really crazy story, but so good!
great review! i'm glad you enjoyed this one.
Thank you so much! I'm glad I enjoyed it, too.
psychological thriller on space ship! That is something I haven't heard before. It sounds fantastic concept and I already like the sound of main characters and androids. Amazing review!
It's amazing! So creepy, but so good with a concept I completely didn't see coming.


text:
require "helper"

describe Google::Cloud::Firestore::Convert, :writes_for_update do
  # These tests are a sanity check on the implementation of the conversion methods.
  # These tests are testing private methods and this is generally not a great idea.
  # But these conversions are so important that it was decided to do it anyway.

  let(:document_path) { "projects/projectID/databases/(default)/documents/C/d" }
  let(:field_delete) { Google::Cloud::Firestore::FieldValue.delete }
  let(:field_server_time) { Google::Cloud::Firestore::FieldValue.server_time }

  it "basic update" do
    data = { a: 1 }

    expected_writes = [
      Google::Firestore::V1beta1::Write.new(
        update: Google::Firestore::V1beta1::Document.new(
          name: document_path,
          fields: {
            "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
          }
        ),
        update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a"]),
        current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
      )
    ]

    actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

    actual_writes.must_equal expected_writes
  end

  it "nested empty hashes create writes" do
    data = { "i.j" => { l: {} } }

    expected_writes = [
      Google::Firestore::V1beta1::Write.new(
        update: Google::Firestore::V1beta1::Document.new(
          name: document_path,
          fields: {
            "i" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
              "j" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                "l" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {}))
              }))
            }))
          }
        ),
        update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["i.j"]),
        current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
      )
    ]

    actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

    actual_writes.must_equal expected_writes
  end

  it "complex update" do
    data = { a: [1, 2.5], b: { c: ["three", { d: true }] } }

    expected_writes = [
      Google::Firestore::V1beta1::Write.new(
        update: Google::Firestore::V1beta1::Document.new(
          name: document_path,
          fields: {
            "a" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [
              Google::Firestore::V1beta1::Value.new(integer_value: 1),
              Google::Firestore::V1beta1::Value.new(double_value: 2.5)
            ])),
            "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
              "c" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [
                Google::Firestore::V1beta1::Value.new(string_value: "three"),
                Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                  "d" => Google::Firestore::V1beta1::Value.new(boolean_value: true)
                }))
              ]))
            }))
          }
        ),
        update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b"]),
        current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
      )
    ]

    actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

    actual_writes.must_equal expected_writes
  end

  it "invalid character" do
    data = { "a~b" => 1 }

    error = expect do
      Google::Cloud::Firestore::Convert.writes_for_update document_path, data
    end.must_raise ArgumentError
    error.message.must_equal "invalid character, use FieldPath instead"
  end

  it "empty field path component" do
    data = { "a..b" => 1 }

    error = expect do
      Google::Cloud::Firestore::Convert.writes_for_update document_path, data
    end.must_raise ArgumentError
    error.message.must_equal "empty paths not allowed"
  end

  it "no paths" do
    data = {}

    error = expect do
      Google::Cloud::Firestore::Convert.writes_for_update document_path, data
    end.must_raise ArgumentError
    error.message.must_equal "data is required"
  end

  it "prefix #1" do
    data = { "a.b" => 1, a: 2 }

    error = expect do
      Google::Cloud::Firestore::Convert.writes_for_update document_path, data
    end.must_raise ArgumentError
    error.message.must_equal "one field cannot be a prefix of another"
  end

  it "prefix #2" do
    data = { "a" => 1, "a.b" => 2 }

    error = expect do
      Google::Cloud::Firestore::Convert.writes_for_update document_path, data
    end.must_raise ArgumentError
    error.message.must_equal "one field cannot be a prefix of another"
  end

  it "prefix #3" do
    data = { a: { b: 1 }, "a.d".to_sym => 2 }

    error = expect do
      Google::Cloud::Firestore::Convert.writes_for_update document_path, data
    end.must_raise ArgumentError
    error.message.must_equal "one field cannot be a prefix of another"
  end

  it "quotes paths starting with non-letter starting chars, except underscore" do
    data = { "_0.1.+2" => 1 }

    expected_writes = [
      Google::Firestore::V1beta1::Write.new(
        update: Google::Firestore::V1beta1::Document.new(
          name: "projects/projectID/databases/(default)/documents/C/d",
          fields: {
            "_0" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
              "1" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                "+2" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
              }))
            }))
          }
        ),
        update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["_0.`1`.`+2`"]),
        current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
      )
    ]

    actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

    actual_writes.must_equal expected_writes
  end

  it "splits on dots" do
    data = { "a.b.c" => 1 }

    expected_writes = [
      Google::Firestore::V1beta1::Write.new(
        update: Google::Firestore::V1beta1::Document.new(
          name: "projects/projectID/databases/(default)/documents/C/d",
          fields: {
            "a" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
              "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                "c" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
              }))
            }))
          }
        ),
        update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a.b.c"]),
        current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
      )
    ]

    actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

    actual_writes.must_equal expected_writes
  end

  it "splits on dots for top-level keys only" do
    data = { "h.g" => { "j.k" => 6 } }

    expected_writes = [
      Google::Firestore::V1beta1::Write.new(
        update: Google::Firestore::V1beta1::Document.new(
          name: "projects/projectID/databases/(default)/documents/C/d",
          fields: {
            "h" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
              "g" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                "j.k" => Google::Firestore::V1beta1::Value.new(integer_value: 6)
              }))
            }))
          }
        ),
        update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["h.g"]),
        current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
      )
    ]

    actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

    actual_writes.must_equal expected_writes
  end

  it "sends update_time as precondition" do
    last_updated_at = Time.now - 42 #42 seconds ago
    data = { a: 1 }

    expected_writes = [
      Google::Firestore::V1beta1::Write.new(
        update: Google::Firestore::V1beta1::Document.new(
          name: "projects/projectID/databases/(default)/documents/C/d",
          fields: {
            "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
          }
        ),
        update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a"]),
        current_document: Google::Firestore::V1beta1::Precondition.new(
          update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(last_updated_at)
        )
      )
    ]

    actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data, update_time: last_updated_at

    actual_writes.must_equal expected_writes
  end

  describe "data using field paths" do
    it "empty field path component" do
      data = { ["a", "", "b"] => 1 }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "empty paths not allowed"
    end

    it "prefix #1" do
      data = { [:a, :b] => 1, [:a] => 2 }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "one field cannot be a prefix of another"
    end

    it "prefix #2" do
      data = { ["a"] => 1, ["a", "b"] => 2 }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "one field cannot be a prefix of another"
    end

    it "prefix #3" do
      data = { a: { b: 1 }, ["a", "d"] => 2 }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "one field cannot be a prefix of another"
    end

    it "quotes paths starting with non-letter starting chars, except underscore" do
      data = { ["_0", 1, "+2"] => 1 }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "_0" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                "1" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                  "+2" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
                }))
              }))
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["_0.`1`.`+2`"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "uses field paths" do
      data = { ["a", "b", "c"] => 1 }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                  "c" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
                }))
              }))
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a.b.c"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "uses field paths for top-level keys only" do
      data = { [:h, :g] => { "j.k" => 6 } }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "h" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                "g" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                  "j.k" => Google::Firestore::V1beta1::Value.new(integer_value: 6)
                }))
              }))
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["h.g"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end
  end

  describe :field_delete do
    it "with data" do
      data = { a: 1, b: field_delete }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "alone" do
      data = { a: field_delete }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d"
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "with a dotted field" do
      data = { a: 1, "b.c" => field_delete, "b.d" => 2 }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1),
              "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: {
                "d" => Google::Firestore::V1beta1::Value.new(integer_value: 2)
              }))
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b.c", "b.d"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "DELETE cannot be nested" do
      data = { a: { b: field_delete } }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "DELETE cannot be nested"
    end

    it "DELETE cannot be anywhere inside an array value" do
      data = { a: [1, { b: field_delete }] }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "cannot nest delete under arrays"
    end

    it "DELETE cannot be in an array value" do
      data = { a: [1, 2, field_delete] }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "cannot nest delete under arrays"
    end
  end

  describe :field_server_time do
    it "SERVER_TIME alone" do
      data = { a: field_server_time }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "a",
                set_to_server_value: :REQUEST_TIME
              )
            ]
          ),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "SERVER_TIME with data" do
      data = { a: 1, b: field_server_time }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b",
                set_to_server_value: :REQUEST_TIME
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "SERVER_TIME with dotted field" do
      data = { "a.b.c" => field_server_time }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "a.b.c",
                set_to_server_value: :REQUEST_TIME
              )
            ]
          ),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "multiple SERVER_TIME fields" do
      data = { a: 1, b: field_server_time, c: { d: field_server_time } }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "c"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b",
                set_to_server_value: :REQUEST_TIME
              ),
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "c.d",
                set_to_server_value: :REQUEST_TIME
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "nested SERVER_TIME field" do
      data = { a: 1, b: { c: field_server_time } }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b.c",
                set_to_server_value: :REQUEST_TIME
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "SERVER_TIME cannot be anywhere inside an array value" do
      data = { a: [1, { b: field_server_time }] }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "cannot nest server_time under arrays"
    end

    it "SERVER_TIME cannot be in an array value" do
      data = { a: [1, 2, field_server_time] }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "cannot nest server_time under arrays"
    end
  end

  describe :field_array_union do
    let(:field_array_union) { Google::Cloud::Firestore::FieldValue.array_union 1, 2, 3 }

    it "ARRAY_UNION alone" do
      data = { a: field_array_union }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "a",
                append_missing_elements: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 1),
                    Google::Firestore::V1beta1::Value.new(integer_value: 2),
                    Google::Firestore::V1beta1::Value.new(integer_value: 3)
                  ]
                )
              )
            ]
          ),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "ARRAY_UNION with data" do
      data = { a: 1, b: field_array_union }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b",
                append_missing_elements: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 1),
                    Google::Firestore::V1beta1::Value.new(integer_value: 2),
                    Google::Firestore::V1beta1::Value.new(integer_value: 3)
                  ]
                )
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "ARRAY_UNION with dotted field" do
      data = { "a.b.c" => field_array_union }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "a.b.c",
                append_missing_elements: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 1),
                    Google::Firestore::V1beta1::Value.new(integer_value: 2),
                    Google::Firestore::V1beta1::Value.new(integer_value: 3)
                  ]
                )
              )
            ]
          ),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "multiple ARRAY_UNION fields" do
      data = { a: 1, b: field_array_union, c: { d: field_array_union } }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "c"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b",
                append_missing_elements: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 1),
                    Google::Firestore::V1beta1::Value.new(integer_value: 2),
                    Google::Firestore::V1beta1::Value.new(integer_value: 3)
                  ]
                )
              ),
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "c.d",
                append_missing_elements: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 1),
                    Google::Firestore::V1beta1::Value.new(integer_value: 2),
                    Google::Firestore::V1beta1::Value.new(integer_value: 3)
                  ]
                )
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "nested ARRAY_UNION field" do
      data = { a: 1, b: { c: field_array_union } }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b.c",
                append_missing_elements: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 1),
                    Google::Firestore::V1beta1::Value.new(integer_value: 2),
                    Google::Firestore::V1beta1::Value.new(integer_value: 3)
                  ]
                )
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "ARRAY_UNION cannot be anywhere inside an array value" do
      data = { a: [1, { b: field_array_union }] }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "cannot nest array_union under arrays"
    end

    it "ARRAY_UNION cannot be in an array value" do
      data = { a: [1, 2, field_array_union] }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "cannot nest array_union under arrays"
    end
  end

  describe :field_array_delete do
    let(:field_array_delete) { Google::Cloud::Firestore::FieldValue.array_delete 7, 8, 9 }

    it "ARRAY_DELETE alone" do
      data = { a: field_array_delete }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "a",
                remove_all_from_array: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 7),
                    Google::Firestore::V1beta1::Value.new(integer_value: 8),
                    Google::Firestore::V1beta1::Value.new(integer_value: 9)
                  ]
                )
              )
            ]
          ),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "ARRAY_DELETE with data" do
      data = { a: 1, b: field_array_delete }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b",
                remove_all_from_array: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 7),
                    Google::Firestore::V1beta1::Value.new(integer_value: 8),
                    Google::Firestore::V1beta1::Value.new(integer_value: 9)
                  ]
                )
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "ARRAY_DELETE with dotted field" do
      data = { "a.b.c" => field_array_delete }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "a.b.c",
                remove_all_from_array: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 7),
                    Google::Firestore::V1beta1::Value.new(integer_value: 8),
                    Google::Firestore::V1beta1::Value.new(integer_value: 9)
                  ]
                )
              )
            ]
          ),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "multiple ARRAY_DELETE fields" do
      data = { a: 1, b: field_array_delete, c: { d: field_array_delete } }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "c"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b",
                remove_all_from_array: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 7),
                    Google::Firestore::V1beta1::Value.new(integer_value: 8),
                    Google::Firestore::V1beta1::Value.new(integer_value: 9)
                  ]
                )
              ),
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "c.d",
                remove_all_from_array: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 7),
                    Google::Firestore::V1beta1::Value.new(integer_value: 8),
                    Google::Firestore::V1beta1::Value.new(integer_value: 9)
                  ]
                )
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "nested ARRAY_DELETE field" do
      data = { a: 1, b: { c: field_array_delete } }

      expected_writes = [
        Google::Firestore::V1beta1::Write.new(
          update: Google::Firestore::V1beta1::Document.new(
            name: "projects/projectID/databases/(default)/documents/C/d",
            fields: {
              "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1)
            }
          ),
          update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b"]),
          current_document: Google::Firestore::V1beta1::Precondition.new(exists: true)
        ),
        Google::Firestore::V1beta1::Write.new(
          transform: Google::Firestore::V1beta1::DocumentTransform.new(
            document: "projects/projectID/databases/(default)/documents/C/d",
            field_transforms: [
              Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
                field_path: "b.c",
                remove_all_from_array: Google::Firestore::V1beta1::ArrayValue.new(
                  values: [
                    Google::Firestore::V1beta1::Value.new(integer_value: 7),
                    Google::Firestore::V1beta1::Value.new(integer_value: 8),
                    Google::Firestore::V1beta1::Value.new(integer_value: 9)
                  ]
                )
              )
            ]
          )
        )
      ]

      actual_writes = Google::Cloud::Firestore::Convert.writes_for_update document_path, data

      actual_writes.must_equal expected_writes
    end

    it "ARRAY_DELETE cannot be anywhere inside an array value" do
      data = { a: [1, { b: field_array_delete }] }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "cannot nest array_delete under arrays"
    end

    it "ARRAY_DELETE cannot be in an array value" do
      data = { a: [1, 2, field_array_delete] }

      error = expect do
        Google::Cloud::Firestore::Convert.writes_for_update document_path, data
      end.must_raise ArgumentError
      error.message.must_equal "cannot nest array_delete under arrays"
    end
  end
end

redpajama_set_name: RedPajamaGithub