--- layout: post title: Testing Against Multiple Rails Versions date: '2013-05-21T11:03:00-05:00' tags: permalink: /post/50991826838/testing-against-multiple-rails-versions ---
Upgrading major versions of Rails sucks. I was there from 0.9 to 1.0, all the way to the famed 2 to 3 release, and they were all painful. I have good news though: Rails 4 is right around the corner, and it’s a much cleaner upgrade. While you are getting your app ready for Rails 4 are you bringing the gems you’ve written up to speed? If the answer is “I don’t know”, keep reading - we will cover how to test the gems you’ve written against multiple versions of Rails including Rails 4 release candidate.
Believe it or not, there are still a large number of people who are using Rails 2 in production today. While the path to upgrading to Rails 3 is well documented (there’s a whole book on it) often the biggest blocker isn’t the app, or even the Rails frameworks. The largest hurdle to upgrading is often an un-maintained third party library that can’t be replaced or re-written and doesn’t work in the latest versions. There are many reasons to quit maintaining a library, but “I didn’t know how to upgrade it to support the latest rails release” shouldn’t be one of them.
Step zero is writing tests for your gem if you don’t already have them, once you’re done with that: make sure you’ve got a CI server running, I prefer Travis CI which is free for open source repos. Once you’ve got your CI build green, you’ll need to re-work your Gemfile so you can install multiple versions of Rails and then set up Travis to run your tests multiple times, each with a different version of Rails.
If you you haven’t already, delete your Gemfile.lock
and remove it from git:
$ git rm Gemfile.lock
Then ignore the file by adding it to your .gitignore
. On a stable app - Gemfile.lock stores the exact version of libraries that are used such as Rails 3.2.13. This is good for normal use, but we need our gem to be able to be bundle install
-ed against any version of Rails. Removing the Gemfile.lock
allows us to do that.
The next thing we will need to do is to enable your app to load multiple Rails versions from the environment open up your libraries’ Gemfile
remove where you’re specifying Rails or railties version:
gem 'rails' '>= 3.1.0'
If it’s in the gemspec, you don’t need to do anything, now add this code
rails_version = ENV["RAILS_VERSION"] || "default"
rails = case rails_version
when "master"
{github: "rails/rails"}
when "default"
">= 3.1.0"
else
"~> #{rails_version}"
end
gem "rails", rails
Make sure to replace the numeric value in the "default"
case with whatever you had specified previously.
Now you can bundle different versions of Rails like so
$ RAILS_VERSION=3.1.0 bundle update
$ RAILS_VERSION=3.1.0 bundle exec rake test
or if you want a one liner:
$ export RAILS_VERSION=3.1.0; bundle update; bundle exec rake test
This will install Rails version ~> 3.1.0
and run tests against it. To run tests against Rails master you can use RAILS_VERSION=master
and to run against the betas or release candidates you can run RAILS_VERSION=4.0.0.pre
Now you can run tests on your app using different versions of Rails. You can see an example of this style of Gemfile in my projects oPRO and Wicked. The next step is to configure Travis to automatically do this for you.
In your project create a .travis.yml
file if you haven’t already. You can add environment variables to your travis run matrix by adding them to this file:
env:
- "RAILS_VERSION=3.1.0"
- "RAILS_VERSION=3.2.0"
- "RAILS_VERSION=4.0.0.pre"
- "RAILS_VERSION=master"
This will make travis run your project 4x more times each with a different environment specified. While we’re testing against different versions of Rails, let’s make sure we’re testing against different versions of Rubies too. This will test MRI 1.9.3, 2.0.0, and master as well as JRuby in 1.9 mode:
rvm:
- 1.9.3
- 2.0.0
- ruby-head
- jruby-19mode
Any time you’re testing the head or master of a project such as ruby-head
you may see failres because the branch is unstable and not due to your project. For these cases you may want to allow failures until official versions are released.
matrix:
allow_failures:
- env: "RAILS_VERSION=master"
- rvm: ruby-head
This will still run tests against the most recent version of Ruby and against Rails master, but if they fail Travis will not flag the build as failed. If you want to fine tune your allowed failures you can add them together like this:
matrix:
allow_failures:
- env: "RAILS_VERSION=3.0.0"
rvm: 2.0.0
This will allow failures against Rails version 3.0.0 only when using Ruby 2.0.0. You can see the .travis.yml
files for oPRO and wicked for full examples.
Once you’ve got your .travis.yml
and Gemfile
set up. Commit your results to a branch and push to github:
$ git checkout -b test-rails-4
$ git add .
$ git commit -m "testing rails 4"
$ git push origin test-rails-4
Now go to your repo on Github and open up a pull request like this PR on wicked. If you have PR testing turned on with Travis, this will kick off a build. If you’re lucky all of your required tests will pass and it will look like this:
You can see the Travis build for Wicked here.
If your tests don’t all pass, not to worry: look at the output, reproduce locally, fix, push, and repeat. Sometimes you might not be able to get one version of your library to support all the versions of Rails/Ruby that you need, when that happens you’ve got diverging stable versions.
Rails no longer supports version 2.0, but maintains version 3.2+ and 4.0.0.RC1+. Most of your users won’t immediately upgrade to the latest version, and while you’re waiting for them to do so, you may find security or other bugs in a version of your library. For this reason I encourage you to consider supporting at least two versions of Rails. There are a few different strategies you can take in order to accomplish this goal:
Do nothing: If you’re lucky your gem will work unmodified in all versions of Rails, and you’re good to go. Make sure you’re tested, and don’t introduce backwards incompatable changes later down the road.
Branch: Rails uses versions and branches to manage it’s codebase and you can too. If your gem needs a major overhaul to be compliant with the latest release you may want to branch out your codebase. One branch for Rails 3 and you can likely leave Rails 4 on master.
$ git checkout -b rails3
You’ll want to have clearly defined versions for your which version of rails your gem supports. You may want to break semver for a single gem push and shadow Rails versioning. So the Rails3 compatible code can be found in version 3.X and the Rails4 compatible code can be found in 4.X of your gem. Once you do this, release as normal using semver, just avoid rev-ing the major version if you don’t have to.
Separate Gems: If keeping different copies of code in different branches seems too hard, break out your gems into multiple libraries. If your library is named foo
consider forking it and making a foo_rails4
gem. While easier on you, it makes it harder for your users to upgrade since they’ll have to know you released a separate gem.
You may find that your library depends on other libraries for development or production that have their own dependencies on Rails. One popular library is Devise. You can see how we conditionally change the version of Devise based on our Rails version in oPRO’s Gemfile, I got this little trick from Steve’s work on Draper’s Gemfile.
While upgrading to Rails 4 I found that a JRuby Gem doesn’t play nice. If you’re testing JRuby and using the JRuby Sqlite3 adapter for ActiveRecord you may need to specify this in your gemfile:
gem "activerecord-jdbcsqlite3-adapter", '>= 1.3.0.beta', :platform => :jruby
If you’re not testing JRuby in your Travis matrix, why aren’t you? You can always set it as an allowed failure and then at least people who want to know if your lib works with JRuby can check the Travis builds.
Now that you know how to write tests for your libraries to run on multiple versions of Rails, you have no excuse for not having them fully tested and compatible when Rails4 is fully released. It’s important to test against the Release Candidates (RC’s) because these are what will eventually become the fully released version of Rails. If you wait till Rails4 is released and find a bug in the framework, it may be too late to fix. When I was testing Wicked, I found and fixed a regression from 3.2.13 to 4.0.0.RC1. Even better, you’ll know if your library works with Rails4 or not, and so will your users.
If are a connoisseur of Hacker News, you may remember a set of inflammatory comments towards the Rails release team over a number of regressions introduced in a minor version bump of the library. The thing is: there was a release candidate for that minor version and for all of the people who wailed and gnashed their teeth at the regressions, few if any bothered to test against the release candidate. Next time your app gets caught by a regression, ask why you didn’t catch it before that version was released. You could be part of the solution.
With bundler and Travis CI testing against multiple versions of Rails couldn’t be easier. Don’t be the maintainer of that one Gem that isn’t compatible with the Rails4 release. Test your gems against all the Rails versions today.
update: @shime_rb pointed out there is a gem called Appraisal to help if you don’t want to manage your Gemfile manually. You can see an example travis.yml with appraisal. Travis also supports specifying multiple gemfiles which is how devise tests against multiple versions of rails .
Richard Schneeman works for Heroku and is married to Ruby, literally. He wrote the easiest way to get started helping in open source: Code Triage. Follow him on twitter @schneems.


Nigeria Needs Right Investment Climate To Drive Petroleum Sector –Shell Boss
Okunbor made the remarks while speaking at the 37th Annual International Conference and Exhibition of National Association of Petroleum Explorationists’ in Lagos where he was represented by Shell’s General Manager, Exploration, Mr Dan Agbaire.
by SaharaReporters, New York
Chairman of Shell Development Company of Nigeria, Mr Osagie Okunbor has emphasised the need for a right investment climate to help enhance the expansion of Nigeria’s petroleum landscape.
Okunbor made the remarks while speaking at the 37th Annual International Conference and Exhibition of National Association of Petroleum Explorationists’ in Lagos where he was represented by Shell’s General Manager, Exploration, Mr Dan Agbaire.
Pointing that the development would enable the increase of Nigeria’s oil production from the current average of 2.3 million b/d to 3 million b/d, Okunbor said that the development will also boost the country’s proven oil reserves to about 40 billion barrels through further exploration and appraisal.
He said, “The right investment climate would also include strengthening our regulatory bodies, giving priorities to research and further enabling the industry’s financials.
“I believe that where the investment climate is right, digitalisation and deployment of emerging technologies will enable incremental value creation over the coming years.”


Collectively - as a group or as a whole - we strive to bring everything together. The shopping experience should be easy and enjoyable. That means you deserve the tools to make things simple. We utilize the latest in technology and best in service to cater to you. The means by which you discover, search, and purchase products should be consistent and integrated across your daily routine. Collectively joins channels, people, and information to form your ideal shopping experience. Whether on-the-go, at your computer, or speaking with us in-store, our resources work as parts of a whole to truly unify the shopping journey.
Want to stay up to date?
Sign up below to receive email notifications about potential developments or to get involved.


Tutorial
Run Hive in local mode
By default, Hive uses the Hadoop configuration to decide how to run the jobs (normally it is YARN). If you are trying to run interactive queries on very small data, that might have been an overkill and will slow you down. In order to use the local mode, you need to set the following configuration:
SET mapreduce.framework.name=local;
SET hive.exec.mode.local.auto=false;
For more information, refer to Apache Hive Wiki
Tutorial
How to make Hive to write compressed files
You need to set the following properties in your Hive session. Run the commands in your CLI or JDBC connection:
set hive.exec.compress.output=true;
set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.GzipCodec;
If you are using Hue, you can set settings in your editor:
Practice sharding in Hive
Sharding is implemented in Hive using partitioning concept.
The sample files are taken from Stanford Online Educatio.
Prerequisite
In this section we assume you have practiced Hive Encoding. We will re-use
- The database we created called
your_name
- The table we created called
enriched_movie
(doesn't matter what encodings you have used) - The schema-on-read concept
Create a partitioned table
CREATE TABLE your_name.enriched_movie_p (
mid int,
title STRING,
director STRING,
rid int,
stars int,
ratingdate STRING
)
PARTITIONED BY (year int)
stored as PARQUET
tblproperties("parquet.compression"="GZIP");
As you can see, it has exactly the same structure of "parquet" encoding table with "GZip" compression
except the year
column that now is in the new clause of PARTITIONED BY
.
Let's check the structure:
DESCRIBE FORMATTED TABLE your_name.enriched_movie_p
Populate the partitioned table
INSERT INTO your_name.enriched_movie_p PARTITION(year)
SELECT * FROM enriched_movie
The query won't work if Hive is configured in the strict mode. It makes sens as we do partitioning (sharding) when the table is big. Hence, you wouldn't normally run a query that works possibly on all the partitions. However, if you really know what you're doing, in this case we know the data is not "big", you could turn it off.
In a Hive connection (CLI or JDBC) just run the following commands:
set hive.exec.dynamic.partition=nonstrict;
set hive.exec.dynamic.partition.mode=nonstrict;
set hive.mapred.mode=nonstrict;
If you are working with Hue, you could set the settings in your current session by clicking on the gear icon on top-right of the page right above the query text box.
Now run the query again. If successful, then run the following command to list the partitions.
SHOW PARTITIONS your_name.enriched_movie_p
As you observe, the list of partitions is not quite right. Try to identify the problem!
The problem is due the mismatch in the list of columns. As discussed in the encoding section, Hive follows the concept of schema on read. Hence, it won't apply the schema during the write operation. The query, will use the following mapping of the columns:
| source | destination |
|---|---|
| mid | mid |
| title | title |
| director | director |
| year | rid |
| rid | stars |
| stars | ratingdate |
| ratingdate | year |
That makes Hive to partition data based on ratingdate
when it writes the data not the year
.
You will also see null
values in incompatible fields. Find them.
In Hive, the proper way is to mention all the fields explicitly to avoid such bugs.
Drop the table and create it again. This time, we will create table as text in order to be able to see the contents.
DROP TABLE your_name.enriched_movie_p;
CREATE TABLE your_name.enriched_movie_p (
mid int,
title STRING,
director STRING,
rid int,
stars int,
ratingdate STRING
)
PARTITIONED BY (year int)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS TEXTFILE;
Now we can populate the table. Note that WHERE year<2000
filters data. We will practice another
concept in the next section with the rest of the data.
INSERT OVERWRITE TABLE your_name.enriched_movie_p PARTITION(year)
SELECT
mid,
title,
director,
rid,
stars,
ratingdate,
year
FROM enriched_movie
WHERE year<2000
Run SHOW PARTITIONS your_name.enriched_movie_p
to see if the procedure was successful.
File structure
Run hdfs dfs -ls /user/hive/warehouse/your_name.db/enriched_movie_p/
to see the list of partitions.
Go through the sub-directories to get to the files. You could validate the content of the files
using hdfs dfs -cat ...
command.
Load data to the table directly
In the previous section, we loaded the data using a SQL query ran directly through Hive server.
You were able to see the files and metadata (using SHOW PARTITIONS
) right after.
Another way is to load the data directly through the HDFS. As discussed, the path to the
partition is /path/to/hive/warehouse/db_name.db/table_name/partition_column_name=partition_column_value
.
For example, /user/warehouse/your_name.db/enriched_movie_p/year=1976
.
The files under this firectory on HDFS hold the data of the partition.
In order to load the data using HDFS, let's create a directory for a partition that doesn't exist and upload the data. Note the data that have the same schema without year.
hadoop fs -mkdir -p /user/hive/warehouse/iraj.db/enriched_movie_p/year=2009
cat <<EOF >~/enriched_movie_2009
107,Avatar,James Cameron,206,3,2011-01-15
107,Avatar,James Cameron,207,5,2011-01-20
EOF
hadoop fs -copyFromLocal ~/enriched_movie_2009 /user/hive/warehouse/iraj.db/enriched_movie_p/year=2009/
Try to see if the partition is detected by the Hive:
SHOW PARTITIONS your_name.enriched_movie_p;
SELECT * FROM your_name.enriched_movie_p;
You won't be able to see the new set of data. The reason is that you have updated the underlying data storeage but not the Hive metastore and partitions are part of the metadata. In order to get the partitions:
MSCK REPAIR TABLE your_name.enriched_movie_p;
SHOW PARTITIONS your_name.enriched_movie_p;
SELECT * FROM your_name.enriched_movie_p;
Collection Data Types
| Type | Size | Literal Syntax Examples |
|---|---|---|
| STRUCT | Analogous to a C struct or an “object.” Fields can be accessed using the “dot” notation.
For example, if a column name is of type STRUCT {first STRING; last STRING} ,
then the first name field can be referenced using name.first .
|
struct(‘John’, ‘Doe’) |
| MAP | A collection of key-value tuples, where the fields are accessed using array notation
(e.g., ['key'] ). For example, if a column name is of type MAP with
key→value pairs'first'→'John' and 'last'→'Doe' ,
then the last name can be referenced using name['last'] .
|
map(‘first’, ‘John’, ‘last’, ‘Doe’) |
| ARRAY | Ordered sequences of the same type that have zero-based indices. For example,
if a column name is of type ARRAY of strings with the value ['John', 'Doe'] ,
then the second element can be referenced using name[1] .
|
array(‘John’, ‘Doe’) |


Monarch Butterfly
Arctic Fox
Shenandoah
Salamander
Whooping
Crane
Emperor
Penguin
Beluga
Whale
Ivory Gull
Koala
Polar Bear
Hawksbill
Sea Turtle
American
Pika
Rusty Patched
Bumble Bee
Common Clownfish
Leatherback
Sea Turtle
Ringed Seal
Ringed Seal
Bicknell's Thrush
American Pika
Click an animal to learn more.
Latin name: Ochotona Princeps
Conservsation status: least concern (population is decreasing)
Pikas, also called "whistling hares" because of their loud song, feed in winter on dried haypiles they have stored near the entrance to their burrows. When haying during late summer they may make up to 100 trips a day.
American pikas occupy talus—rock piles that accumulate at the base of a slope—at high elevations in western mountains. Pikas are thought to be a prime example of the potential effects of climate change because they are sensitive to warm temperatures and rely on insulation provided by snow to survive cold winter temperatures. However, several recent studies indicate that pikas can be resilient to each of these factors. Most pikas in the Sierra Nevada
survived the winter of
2014, when there was almost no snowpack. Pikas persist in many hot localities as well, demonstrating their ability to cope with high temperatures.


BMesh Module (bmesh)¶
This module provides access to blenders bmesh data structures.
Submodules:
Introduction¶
This API gives access the blenders internal mesh editing api, featuring geometry connectivity data and access to editing operations such as split, separate, collapse and dissolve.
The features exposed closely follow the C API, giving python access to the functions used by blenders own mesh editing tools.
For an overview of BMesh data types and how they reference each other see: BMesh Design Document .
Note
Disk and Radial data is not exposed by the python api since this is for internal use only.
Warning
TODO items are…
- add access to BMesh walkers
- add custom-data manipulation functions add/remove/rename.
Example Script¶
# This example assumes we have a mesh object selected
import bpy
import bmesh
# Get the active mesh
me = bpy.context.object.data
# Get a BMesh representation
bm = bmesh.new() # create an empty BMesh
bm.from_mesh(me) # fill it in from a Mesh
# Modify the BMesh, can do anything here...
for v in bm.verts:
v.co.x += 1.0
# Finish up, write the bmesh back to the mesh
bm.to_mesh(me)
bm.free() # free and prevent further access
Stand-Alone Module¶
The bmesh module is written to be standalone except for mathutils
which is used for vertex locations and normals.
The only other exception to this are when converting mesh data to and from bpy.types.Mesh
.
Mesh Access¶
There are 2 ways to access BMesh data, you can create a new BMesh by converting a mesh from
bpy.types.BlendData.meshes
or by accessing the current edit mode mesh.
see: bmesh.types.BMesh.from_mesh
and bmesh.from_edit_mesh
respectively.
When explicitly converting from mesh data python owns the data, that is to say - that the mesh only exists while python holds a reference to it, and the script is responsible for putting it back into a mesh data-block when the edits are done.
Note that unlike bpy
, a BMesh does not necessarily correspond to data in the currently open blend file,
a BMesh can be created, edited and freed without the user ever seeing or having access to it.
Unlike edit mode, the bmesh module can use multiple BMesh instances at once.
Take care when dealing with multiple BMesh instances since the mesh data can use a lot of memory, while a mesh that
python owns will be freed in when the script holds no references to it,
its good practice to call bmesh.types.BMesh.free
which will remove all the mesh data immediately and disable
further access.
EditMode Tessellation¶
When writing scripts that operate on editmode data you will normally want to re-calculate the tessellation after running the script, this needs to be called explicitly.
The BMesh its self does not store the triangulated faces, they are stored in the bpy.types.Mesh
,
to refresh tessellation triangles call bpy.types.Mesh.calc_loop_triangles
.
CustomData Access¶
BMesh has a unified way to access mesh attributes such as UV’s vertex colors, shape keys, edge crease etc.
This works by having a layers property on bmesh data sequences to access the custom data layers which can then be used to access the actual data on each vert/edge/face/loop.
Here are some examples …
uv_lay = bm.loops.layers.uv.active
for face in bm.faces:
for loop in face.loops:
uv = loop[uv_lay].uv
print("Loop UV: %f, %f" % uv[:])
vert = loop.vert
print("Loop Vert: (%f,%f,%f)" % vert.co[:])
shape_lay = bm.verts.layers.shape["Key.001"]
for vert in bm.verts:
shape = vert[shape_lay]
print("Vert Shape: %f, %f, %f" % (shape.x, shape.y, shape.z))
# in this example the active vertex group index is used,
# this is stored in the object, not the BMesh
group_index = obj.vertex_groups.active_index
# only ever one deform weight layer
dvert_lay = bm.verts.layers.deform.active
for vert in bm.verts:
dvert = vert[dvert_lay]
if group_index in dvert:
print("Weight %f" % dvert[group_index])
else:
print("Setting Weight")
dvert[group_index] = 0.5
Keeping a Correct State¶
When modeling in blender there are certain assumptions made about the state of the mesh.
- hidden geometry isn’t selected.
- when an edge is selected, its vertices are selected too.
- when a face is selected, its edges and vertices are selected.
- duplicate edges / faces don’t exist.
- faces have at least 3 vertices.
To give developers flexibility these conventions are not enforced, however tools must leave the mesh in a valid state else other tools may behave incorrectly.
Any errors that arise from not following these conventions is considered a bug in the script, not a bug in blender.
Selection / Flushing¶
As mentioned above, it is possible to create an invalid selection state (by selecting a state and then de-selecting one of its vertices’s for example), mostly the best way to solve this is to flush the selection after performing a series of edits. this validates the selection state.
Module Functions¶
-
bmesh.
from_edit_mesh
(mesh)¶ Return a BMesh from this mesh, currently the mesh must already be in editmode.
Parameters: mesh ( bpy.types.Mesh
) – The editmode mesh.Returns: the BMesh associated with this mesh. Return type: bmesh.types.BMesh
-
bmesh.
new
(use_operators=True)¶ Parameters: use_operators (bool) – Support calling operators in bmesh.ops
(uses some extra memory per vert/edge/face).Returns: Return a new, empty BMesh. Return type: bmesh.types.BMesh
-
bmesh.
update_edit_mesh
(mesh, loop_triangles=True, destructive=True)¶ Update the mesh after changes to the BMesh in editmode, optionally recalculating n-gon tessellation.
Parameters: - mesh (
bpy.types.Mesh
) – The editmode mesh. - loop_triangles (boolean) – Option to recalculate n-gon tessellation.
- destructive (boolean) – Use when geometry has been added or removed.
- mesh (


We don't have access to this password. If you lose it we won't be able to recover your encrypted messages!
Mailbox password is required.
Passwords don't match.


Being a real-time client, you will use event listeners to respond to events sent from the Coronium Chatterbox instance. You can learn more about the events received in Client Events.
Rooms
Coronium Chatterbox allows you to group clients into "rooms" of your choosing. Using the joinRoom action you can switch between rooms. By default, the client is connected to the "Lobby" room. If you want to join a different room when connecting, supply the room parameter to the cs:connect method (see Connecting The Client).
When a client joins a new room, they are automatically removed from the previous room they were in. A client can only be a member of one room at any given time.
At anytime a client joins a room , a OnJoined event will be triggered in the room with the newly joined members information. The joining client also receives this message.
If a user leaves a room, the OnLeft event is triggered in the room. The leaving client will not receive this message.
Tip
Whenever a client joins or leaves a room (or changes their name), an OnClientList event will be triggered. This is a good time to store and update your client display list.
To retrieve a list of the active rooms on your Coronium Chatterbox instance use the getRooms action along with the OnRoomList event.
Client List
The client list is broadcast to the room on any OnJoined, OnLeft, or OnNameChange event. You can use this list to create a user display component, and to also gather the ID needed for the sendWhisper action. To listen for the client list use the OnClientList event.
Messages
The client can send (and receive) three different types of messages; a "room" message, a "private" message, and a "system" message.
Room Message
A room message is sent using the sendMessage action. This message type is broadcast to all members of the current room the client is in. You can receive this message type using the OnMessage event. This message type works well for chat messaging.
Important
All clients in the room receive this message type, including the client sending the message.
Private Message
You can send a message directly to any user in the room using the sendWhisper action. This message type is sent directly to the recipient. It is not broadcast into the room. To send a "whisper" you need to supply the recipient ID. You can get ahold of these ids within the OnClientList event.
System Message
To send control type messages within the room, you can use a "system" message. This message type is broadcast to the room, and can be captured outside of your "chat" layer. To receive these use the OnSystemMessage event.
System messages can be used for controlling aspects of your application state. For example, opening a webview with a passed url.
Important
All clients in the room receive this message type, including the client sending the message.
Status Events
To listen for error events from both the Coronium Chatterbox instance, as well as, the local client use the OnError event.
To listen for client timeout (if any) use the OnTimeout event.
To listen for the client disconnecting from the instance, use the OnClosed event.


←
Home
About
Archive
RSS
Dave
Follow
@fossil12 on Micro.blog
.
Done. 📚📖🎓🎉
TIL: cmd+[ jumps one animation step backwards in Keynote. (Instead of jumping to the first animation on the current slide with the back arrow.)
These stressful times when I can relay on my big network of friends is just amazing! 📚📝
The new iPad Pro is only 0.2 mm thinner than the previous 10.5” and 9.7” iPad Pros.
The new iPad pro and Apple Pencil look really great and I’m really tempted. But I question if the new iPad gives me any advantages I’d use. (It sounds fancy to be able to conenct it to my 5K display, but would I get anything out of it? 🤷🏼♂)
Oh, no. Who approved this horizontally socrolling iPad Pro website when you scroll vertically? 😩
I need to get this new iPad Pro. I can connect it to the same display as my MBP. 😱
How about also allow FaceID in each orientation on the iPhones…?
This new iPad design looks more similar to the first generation iPad than any other before.
So the iPad is a Computer, but the iPhone not.
This Apple Store promotion is like demos in other keynotes. yawn
Tim is great, the crowd is too crazy for me…
This Mac Mini intro sounds a bit like the intro of the current Mac Pro. But I hope they did not put themself in a thermal corner again.
Today is the keynote of the “forgotten” Macs.
I like todays Tim Cook. He’s way more fun than in previous keynotes.
Wow. Thunderbold 3, not just USB-C in the new MBA.
The missing Touch Bar in the new MBA sounds like a feature. 🎉
The new Air looks like a new MB with a second USB-C port.
Why does the tag say “Green” instead of “Work”?
So the video showed mostly laptops. 💻
What a crazy crowd… 🤔
I expect a big leap fo the iPad with new hardware including a pencil and I hope there will be also some nice updates to the Mac lineup.
New month, Apple event. Expect lot of posts from me in the jetzt two hours…
I have about 1 day to contribute to an existing open source app or work on my own project. So hard to choose what to do… 🤔
It was time for an upgrade. My new Apple Watch was already in active/ty use.


Welcome in Software and Data Technology Inc.
Software and Data Technology provide future generation software product. SDT's all product is AI based.
Take Care and Good Bye.


{{shadow}} ]]>
The mdl-shadow directive is a convenient directive that let's you create drop shadows on container. It's a shorthand for css classes like mdl-shadow--2dp


---
layout: default
lang: en
pageId: how-to-use-design-system
title: How to use the Design System
description: Some general rules about how to use the Design System
keywords: structure, design system, proton
section: fe-conventions
---
The Proton Design System is splitted into 2 sections:
- Design: brand colors/buttons/etc. all elements used for the project.
- Front-End Guidelines: guidelines to understand project structures, existing classes, helpers, available frameworks, recommendations, etc.
About CSS structure and re-use
Sass files of the Design System are splitted for the moment into 3 folders:
- reusable-components: a set of files/framework/etc. that can be used on every project, including static websites.
{% assign sassFiles = "design-system-config#design-system-reset#design-system-colors#design-system-util-typo-fix#design-system-links-icons#design-system-layout-modules#design-system-forms#design-system-responsive#design-system-print#design-system-reduced-motion#design-system-viewport-fix" | split: '#' %}
{% assign sassDescription = "config, Sass functions and variables#resets#colors classes (background
, color
, and fill
)#typo fixes and typo helpers#icons sizes/links#all positionning helpers (including flex)#all resets for forms#all helpers for responsive#all helpers/resets for print#a media-query prefers-reduced-motion: reduce
to cancel transitions/animations for users that need it (vestibular disorder)#some viewport fixes" | split: '#' %}
{% for file in sassFiles %}
- _{{ file }}.scss: {{ sassDescription[forloop.index0] }}
{% endfor %}
- pm-styles: styles used for ProtonMail V4.
{% assign sassFiles = "pm-cursors|pm-badges|pm-layout|pm-notifications|pm-table|pm-table-plans|pm-modal|pm-buttons|pm-forms|pm-dropdown|pm-meterbar|pm-loadingcontent|pm-loader-animation|pm-containers|pm-breadcrumb-domain|pm-panels|pm-conversations|pm-tooltips|pm-theme-config|pm-blue-theme|pm-light-theme|pm-wizard|pm-sticky-header|pm-dropdown-logout|pm-progress-contact|pm-custom-scrollbar|pm-tabs|pm-circlebar|pm-header-searchbar|pm-header-topnav|pm-aside|pm-toolbar|pm-treeview|pm-navigation|pm-subnav|pm-overview-grid|pm-settings-home|pm-choose-account|pm-label-stack" | split: '|' %}
{% assign links = "/helpers/|/badges/|/containers/|/notifications/|/table/|/table/#plans-table|/modals/|/buttons/|/forms/|/drop-down/|/|/loading-content/|/loading-content/|/containers/|/containers/|/containers/|/conversations/|/tooltips/|/themes/|null|null|/containers/#wizard/|/sass-variables/|null|/loading-content/|/containers/|/containers/#tabs|/drop-down/|null|null|null|null|null|null|/sass-variables/|/settings/|/signup-elements/|/badges/" | split: '|' %}
{% assign linksDesc = "cursors|badges|containers|notifications|tables|tables (plans)|modals|buttons|forms|drop downs|on each page on the left bottom|loading content|in Loading content section|containers|breadcrumb domain|information panels|conversations|tooltips|config for themes|blue theme|light theme|in containers|example on Sass variables page|example on the top|in Loading content section|containers|in containers|in drop downs|search bar on top|nav on top|on the left|the toolbar|tree view React component|on the left|example on Sass variables page|settings|Signup elements|label stack" | split: '|' %}
{% for file in sassFiles %}
- _{{ file }}.scss:
{% if links[forloop.index0] != 'null' %}{% endif %}
see {{ linksDesc[forloop.index0] }}
{% if links[forloop.index0] != 'null' %}{% endif %}
{% endfor %}
- pv-styles: styles coming from ProtonVPN.
- _pv-indicator.scss: state indicator for servers
- react-styles: these are dedicated styles for some react components (separated because of special needs incompatible between Design System website/React/Angular).
{% assign sassFiles = "pm-dropdown#pm-tooltip#snow#react-intl-tel-input" | split: '#' %}
{% assign sassDescription = "Drop downs#Tooltips#Snow theme for Quill#Files for React Intl Tel input" | split: '#' %}
{% for file in sassFiles %}
- _{{ file }}.scss: {{ sassDescription[forloop.index0] }}
{% endfor %}
- design-system-website: styles for global interface, including styles only needed to make this website work properly.
See in /assets/css/styles-pm.scss
to see an example of import of all of these.
Design system images (assets)
- Images used by the Design System CSS and which could be re-used on other projects are in the folder
/assets/img/shared
.
- Images only used by the Design System website are in the folder
/assets/img/design-system-website
.
Overwriting variables
If you need (for your project) to set up different values than the default one of the design system, do overwrite variables in styles-pm.scss
, not in Sass partials.
Read more about this in Sass variables section.


Interesting facts
- The Pokemon of the series is a metapod .
- Misty first appears in a swimsuit
- The samurai blames Ash for everything, even though it was because of his interference and threats that Ash missed Vidl, who called help Bedrill.
- This episode was banned in South Korea due to the image of a samurai.


Contains UI components and utilities for Jmix React client.
Documentation and API reference can be found here.
Generated using TypeDoc


I went MOHI Primary School in Machakos and later proceeded to BUTULA SCHOOL
Currently a student with leading coding school in East and Central Africa MORINGA SCHOOL
I hope to be a data scientist come a Web developer and moringa is fully supporting me in acqusition of the appropriate skills in my line of career
I believe quality in whatever i do and by this i cline to the cognitive saying “Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live”
Interests
Am in love with coding
I enjoy reading novels
I like adventuring
I do Yoga
Finally i am a religious gentleman i love reading the message
This was my very first project. click below to see it


CREATE RULE
Section: SQL Commands (7)Updated: 2012-08-14
Index Return to Main Contents
NAME
CREATE RULE - define a new rewrite ruleSYNOPSIS
CREATE [ OR REPLACE ] RULE name AS ON event TO table [ WHERE condition ] DO [ ALSO | INSTEAD ] { NOTHING | command | ( command ; command ... ) }
DESCRIPTION
CREATE RULE defines a new rule applying to a specified table or view. CREATE OR REPLACE RULE will either create a new rule, or replace an existing rule of the same name for the same table.
The PostgreSQL rule system allows one to define an alternative action to be performed on insertions, updates, or deletions in database tables. Roughly speaking, a rule causes additional commands to be executed when a given command on a given table is executed. Alternatively, an INSTEAD rule can replace a given command by another, or cause a command not to be executed at all. Rules are used to implement table views as well. It is important to realize that a rule is really a command transformation mechanism, or command macro. The transformation happens before the execution of the commands starts. If you actually want an operation that fires independently for each physical row, you probably want to use a trigger, not a rule. More information about the rules system is in in the documentation.
Presently, ON SELECT rules must be unconditional INSTEAD rules and must have actions that consist of a single SELECT command. Thus, an ON SELECT rule effectively turns the table into a view, whose visible contents are the rows returned by the rule's SELECT command rather than whatever had been stored in the table (if anything). It is considered better style to write a CREATE VIEW command than to create a real table and define an ON SELECT rule for it.
You can create the illusion of an updatable view by defining ON INSERT, ON UPDATE, and ON DELETE rules (or any subset of those that's sufficient for your purposes) to replace update actions on the view with appropriate updates on other tables. If you want to support INSERT RETURNING and so on, then be sure to put a suitable RETURNING clause into each of these rules.
There is a catch if you try to use conditional rules for view updates: there must be an unconditional INSTEAD rule for each action you wish to allow on the view. If the rule is conditional, or is not INSTEAD, then the system will still reject attempts to perform the update action, because it thinks it might end up trying to perform the action on the dummy table of the view in some cases. If you want to handle all the useful cases in conditional rules, add an unconditional DO INSTEAD NOTHING rule to ensure that the system understands it will never be called on to update the dummy table. Then make the conditional rules non-INSTEAD; in the cases where they are applied, they add to the default INSTEAD NOTHING action. (This method does not currently work to support RETURNING queries, however.)
PARAMETERS
- name
- The name of a rule to create. This must be distinct from the name of any other rule for the same table. Multiple rules on the same table and same event type are applied in alphabetical name order.
- event
- The event is one of SELECT, INSERT, UPDATE, or DELETE.
- table
- The name (optionally schema-qualified) of the table or view the rule applies to.
- condition
- Any SQL conditional expression (returning boolean). The condition expression cannot refer to any tables except NEW and OLD, and cannot contain aggregate functions.
- INSTEAD
- INSTEAD indicates that the commands should be executed instead of the original command.
- ALSO
-
ALSO indicates that the commands should be
executed in addition to the original
command.
If neither ALSO nor INSTEAD is specified, ALSO is the default.
- command
- The command or commands that make up the rule action. Valid commands are SELECT, INSERT, UPDATE, DELETE, or NOTIFY.
Within condition and command, the special table names NEW and OLD can be used to refer to values in the referenced table. NEW is valid in ON INSERT and ON UPDATE rules to refer to the new row being inserted or updated. OLD is valid in ON UPDATE and ON DELETE rules to refer to the existing row being updated or deleted.
NOTES
You must be the owner of a table to create or change rules for it.
In a rule for INSERT, UPDATE, or DELETE on a view, you can add a RETURNING clause that emits the view's columns. This clause will be used to compute the outputs if the rule is triggered by an INSERT RETURNING, UPDATE RETURNING, or DELETE RETURNING command respectively. When the rule is triggered by a command without RETURNING, the rule's RETURNING clause will be ignored. The current implementation allows only unconditional INSTEAD rules to contain RETURNING; furthermore there can be at most one RETURNING clause among all the rules for the same event. (This ensures that there is only one candidate RETURNING clause to be used to compute the results.) RETURNING queries on the view will be rejected if there is no RETURNING clause in any available rule.
It is very important to take care to avoid circular rules. For example, though each of the following two rule definitions are accepted by PostgreSQL, the SELECT command would cause PostgreSQL to report an error because of recursive expansion of a rule:
CREATE RULE "_RETURN" AS ON SELECT TO t1 DO INSTEAD SELECT * FROM t2; CREATE RULE "_RETURN" AS ON SELECT TO t2 DO INSTEAD SELECT * FROM t1; SELECT * FROM t1;
Presently, if a rule action contains a NOTIFY command, the NOTIFY command will be executed unconditionally, that is, the NOTIFY will be issued even if there are not any rows that the rule should apply to. For example, in:
CREATE RULE notify_me AS ON UPDATE TO mytable DO ALSO NOTIFY mytable; UPDATE mytable SET name = 'foo' WHERE id = 42;one NOTIFY event will be sent during the UPDATE, whether or not there are any rows that match the condition id = 42. This is an implementation restriction that might be fixed in future releases.
COMPATIBILITY
CREATE RULE is a PostgreSQL language extension, as is the entire query rewrite system.
Index
This document was created by man2html, using the manual pages.
Time: 05:34:14 GMT, December 24, 2015


Avoid importing anything from the package 'java.lang'. These classes are automatically imported (JLS 7.5.3).
This rule is deprecated, use {rule:squid:UselessImportCheck} instead.


Editing {{editorCtrl.card.title}}
{{(editorCtrl.activeVersion.title || 'Version ' + (editorCtrl.versionIndex + 1)) + ' : ' + (editorCtrl.activeHitbox.title || 'Hitbox ' + (editorCtrl.hitboxIndex + 1))}}
delete
Delete Coordinate
more_vert
Delete All
This text is displayed if your browser does not support HTML5 Canvas.
Details
Point
Color
Size
Position
X
Y
Side
keyboard_arrow_up
keyboard_arrow_left
keyboard_arrow_right
keyboard_arrow_down
Lock Canvas
Unlock Canvas
Cancel
Save Changes


Using Te En Dictionary
How to use:
Type in the word to lookup.
Select the translation you want and hit enter.
Meaning will be displayed in the meaning panel.


Restart Node.js servers on domain errors: Sensible FUD
The Node.js documentation about the “domain” error handling module has a strongly worded warning imploring you to shut down the process when a domain catches an error.
I was recently asked for a more detailed explanation of why this is, and answered with the following gist.
This principle is not unique to JavaScript, of course. Any language with state, side effects, and exceptions that abandon local context without a way to resume at the throw site, will basically guarantee that unexpected exceptions cannot ever be handled in a way that lets the program proceed normally. (This also applies, for example, to Ruby, Python, PHP, C++, and many other languages, though of course they’re going to have their own set of caveats and workarounds.)
This is not so bad if it’s a program that isn’t long-lived. PHP scripts typically run to completion and then get cleaned up, so a missed exception is pretty meh. Same with command line scripts.
But in a server or some other sort of long-lived daemon, it’s completely unacceptable, and will nearly always lead to undesirable system behavior.
Finish up what you’re doing, close the active connections or whatever, and GTFO. Let the system start a new process.
It’s the only responsible option.


Table of Contents
In this lesson, we’ll continue building off the Friends
app and the movie-cataloguing domain we set up in the previous lesson.
Fetching Records with Ecto.Repo
Recall that a “repository” in Ecto maps to a datastore such as our Postgres database. All communication to the database will be done using this repository.
We can perform simple queries directly against our Friends.Repo
with the help of a handful of functions.
Fetching Records by ID
We can use the Repo.get/3
function to fetch a record from the database given its ID. This function requires two arguments: a “queryable” data structure and the ID of a record to retrieve from the database. It returns a struct describing the record found, if any. It returns nil
if no such record is found.
Let’s take a look at an example. Below, we’ll get the movie with an ID of 1:
iex> alias Friends.{Repo, Movie}
iex> Repo.get(Movie, 1)
%Friends.Movie{
__meta__: %Ecto.Schema.Metadata<:loaded, "movies">,
actors: %Ecto.Association.NotLoaded<association :actors is not loaded>,
characters: %Ecto.Association.NotLoaded<association :characters is not loaded>,
distributor: %Ecto.Association.NotLoaded<association :distributor is not loaded>,
id: 1,
tagline: "Something about video games",
title: "Ready Player One"
}
Notice that the first argument we give to Repo.get/3
is our Movie
module. Movie
is “queryable” because the module uses the Ecto.Schema
module and defines a schema for its data structure. This gives Movie
access to the Ecto.Queryable
protocol. This protocol converts a data structure into an Ecto.Query
. Ecto queries are used to retrieve data from a repository. More on queries later.
Fetching Records by Attribute
We can also fetch records that meet a given criteria with the Repo.get_by/3
function. This function requires two arguments: the “queryable” data structure and the clause with which we want to query. Repo.get_by/3
returns a single result from the repository. Let’s look at an example:
iex> Repo.get_by(Movie, title: "Ready Player One")
%Friends.Movie{
__meta__: %Ecto.Schema.Metadata<:loaded, "movies">,
actors: %Ecto.Association.NotLoaded<association :actors is not loaded>,
characters: %Ecto.Association.NotLoaded<association :characters is not loaded>,
distributor: %Ecto.Association.NotLoaded<association :distributor is not loaded>,
id: 1,
tagline: "Something about video games",
title: "Ready Player One"
}
If we want to write more complex queries, or if we want to return all records that meet a certain condition, we need to use the Ecto.Query
module.
Writing Queries with Ecto.Query
The Ecto.Query
module provides us with the Query DSL which we can use to write queries to retrieve data from the application’s repository.
Keyword-based queries with Ecto.Query.from/2
We can create a query with the Ecto.Query.from/2
macro. This function takes in two arguments: an expression and an optional keyword list. Let’s create the most simple query to select all of the movies from our repository:
iex> import Ecto.Query
iex> query = from(Movie)
#Ecto.Query<from m0 in Friends.Movie>
In order to execute our query, we use the Repo.all/2
function. This function takes in a required argument of an Ecto query and returns all of the records that meet the conditions of the query.
iex> Repo.all(query)
14:58:03.187 [debug] QUERY OK source="movies" db=1.7ms decode=4.2ms
[
%Friends.Movie{
__meta__: %Ecto.Schema.Metadata<:loaded, "movies">,
actors: %Ecto.Association.NotLoaded<association :actors is not loaded>,
characters: %Ecto.Association.NotLoaded<association :characters is not loaded>,
distributor: %Ecto.Association.NotLoaded<association :distributor is not loaded>,
id: 1,
tagline: "Something about video games",
title: "Ready Player One"
}
]
Bindingless queries with from
The example above lacks the most fun parts of SQL statements. We often want to only query for specific fields or filter records by some condition. Let’s fetch title
and tagline
of all movies that have "Ready Player One"
title:
iex> query = from(Movie, where: [title: "Ready Player One"], select: [:title, :tagline])
#Ecto.Query<from m0 in Friends.Movie, where: m0.title == "Ready Player One",
select: [:title, :tagline]>
iex> Repo.all(query)
SELECT m0."title", m0."tagline" FROM "movies" AS m0 WHERE (m0."title" = 'Ready Player One') []
[
%Friends.Movie{
__meta__: %Ecto.Schema.Metadata<:loaded, "movies">,
actors: %Ecto.Association.NotLoaded<association :actors is not loaded>,
characters: %Ecto.Association.NotLoaded<association :characters is not loaded>,
id: nil,
tagline: "Something about video games",
title: "Ready Player One"
}
]
Please note that the returned struct only has tagline
and title
fields set – this is the result of our select:
part.
Queries like this are called bindingless, because they are simple enough to not require bindings.
Bindings in queries
So far we used a module that implements the Ecto.Queryable
protocol (ex: Movie
) as the first argument for from
macro. However, we can also use in
expression, like this:
iex> query = from(m in Movie)
#Ecto.Query<from m0 in Friends.Movie>
In such case, we call m
a binding. Bindings are extremely useful, because they allow us to reference modules in other parts of the query. Let’s select titles of all movies that have id
less than 2
:
iex> query = from(m in Movie, where: m.id < 2, select: m.title)
#Ecto.Query<from m0 in Friends.Movie, where: m0.id < 2, select: m0.title>
iex> Repo.all(query)
SELECT m0."title" FROM "movies" AS m0 WHERE (m0."id" < 2) []
["Ready Player One"]
The very important thing here is how output of the query changed. Using an expression with a binding in select:
part allows you to specify exactly the way selected fields will be returned. We can ask for a tuple, for example:
iex> query = from(m in Movie, where: m.id < 2, select: {m.title})
iex> Repo.all(query)
[{"Ready Player One"}]
It is a good idea to always start with a simple bindingless query and introduce a binding whenever you need to reference your data structure. More on bindings in queries can be found in Ecto documentation
Macro-based queries
In the examples above we used keywords select:
and where:
inside of from
macro to build a query – these are so called keyword-based queries. There is, however, another way to compose queries – macro-based queries. Ecto provides macros for every keyword, like select/3
or where/3
. Each macro accepts a queryable value, an explicit list of bindings and the same expression you’d provide to its keyword analogue:
iex> query = select(Movie, [m], m.title)
#Ecto.Query<from m0 in Friends.Movie, select: m0.title>
iex> Repo.all(query)
SELECT m0."title" FROM "movies" AS m0 []
["Ready Player One"]
The good thing about macros is that they work very well with pipes:
iex> Movie \
...> |> where([m], m.id < 2) \
...> |> select([m], {m.title}) \
...> |> Repo.all
[{"Ready Player One"}]
Note that to continue writing after the line break, use the character \
.
Using where
with Interpolated Values
In order to use interpolated values or Elixir expressions in our where clauses, we need to use the ^
, or pin, operator. This allows us to pin a value to a variable and refer to that pinned value, instead of re-binding that variable.
iex> title = "Ready Player One"
"Ready Player One"
iex> query = from(m in Movie, where: m.title == ^title, select: m.tagline)
%Ecto.Query<from m in Friends.Movie, where: m.title == ^"Ready Player One",
select: m.tagline>
iex> Repo.all(query)
15:21:46.809 [debug] QUERY OK source="movies" db=3.8ms
["Something about video games"]
Getting the First and Last Records
We can fetch the first or last records from a repository using the Ecto.Query.first/2
and Ecto.Query.last/2
functions.
First, we’ll write a query expression using the first/2
function:
iex> first(Movie)
#Ecto.Query<from m0 in Friends.Movie, order_by: [asc: m0.id], limit: 1>
Then we pass our query to the Repo.one/2
function to get our result:
iex> Movie |> first() |> Repo.one()
SELECT m0."id", m0."title", m0."tagline" FROM "movies" AS m0 ORDER BY m0."id" LIMIT 1 []
%Friends.Movie{
__meta__: #Ecto.Schema.Metadata<:loaded, "movies">,
actors: #Ecto.Association.NotLoaded<association :actors is not loaded>,
characters: #Ecto.Association.NotLoaded<association :characters is not loaded>,
distributor: #Ecto.Association.NotLoaded<association :distributor is not loaded>,
id: 1,
tagline: "Something about video games",
title: "Ready Player One"
}
The Ecto.Query.last/2
function is used in the same way:
iex> Movie |> last() |> Repo.one()
Querying For Associated data
Preloading
In order to be able to access the associated records that the belongs_to
, has_many
and has_one
macros expose to us, we need to preload the associated schemas.
Let’s take a look to see what happens when we try to ask a movie for its associated actors:
iex> movie = Repo.get(Movie, 1)
iex> movie.actors
%Ecto.Association.NotLoaded<association :actors is not loaded>
We can’t access those associated characters unless we preload them. There are a few different way to preload records with Ecto.
Preloading With Two Queries
The following query will preload associated records in a separate query.
iex> Repo.all(from m in Movie, preload: [:actors])
13:17:28.354 [debug] QUERY OK source="movies" db=2.3ms queue=0.1ms
13:17:28.357 [debug] QUERY OK source="actors" db=2.4ms
[
%Friends.Movie{
__meta__: %Ecto.Schema.Metadata<:loaded, "movies">,
actors: [
%Friends.Actor{
__meta__: %Ecto.Schema.Metadata<:loaded, "actors">,
id: 1,
movies: %Ecto.Association.NotLoaded<association :movies is not loaded>,
name: "Tyler Sheridan"
},
%Friends.Actor{
__meta__: %Ecto.Schema.Metadata<:loaded, "actors">,
id: 2,
movies: %Ecto.Association.NotLoaded<association :movies is not loaded>,
name: "Gary"
}
],
characters: %Ecto.Association.NotLoaded<association :characters is not loaded>,
distributor: %Ecto.Association.NotLoaded<association :distributor is not loaded>,
id: 1,
tagline: "Something about video games",
title: "Ready Player One"
}
]
We can see that the above line of code ran two database queries. One for all of the movies, and another for all of the actors with the given movie IDs.
Preloading With One Query
We can cut down on our database queries with the following:
iex> query = from(m in Movie, join: a in assoc(m, :actors), preload: [actors: a])
iex> Repo.all(query)
13:18:52.053 [debug] QUERY OK source="movies" db=3.7ms
[
%Friends.Movie{
__meta__: %Ecto.Schema.Metadata<:loaded, "movies">,
actors: [
%Friends.Actor{
__meta__: %Ecto.Schema.Metadata<:loaded, "actors">,
id: 1,
movies: %Ecto.Association.NotLoaded<association :movies is not loaded>,
name: "Tyler Sheridan"
},
%Friends.Actor{
__meta__: %Ecto.Schema.Metadata<:loaded, "actors">,
id: 2,
movies: %Ecto.Association.NotLoaded<association :movies is not loaded>,
name: "Gary"
}
],
characters: %Ecto.Association.NotLoaded<association :characters is not loaded>,
distributor: %Ecto.Association.NotLoaded<association :distributor is not loaded>,
id: 1,
tagline: "Something about video games",
title: "Ready Player One"
}
]
This allows us to execute just one database call. It also has the added benefit of allowing us to select and filter both movies and associated actors in the same query. For example, this approach allows us to query for all movies where the associated actors meet certain conditions using a join
statement. Something like:
Repo.all from m in Movie,
join: a in assoc(m, :actors),
where: a.name == "John Wayne",
preload: [actors: a]
More on join statements in a bit.
Preloading Fetched Records
We can also preload the associated schemas of records that have already been queried from the database.
iex> movie = Repo.get(Movie, 1)
%Friends.Movie{
__meta__: %Ecto.Schema.Metadata<:loaded, "movies">,
actors: %Ecto.Association.NotLoaded<association :actors is not loaded>, # actors are NOT LOADED!!
characters: %Ecto.Association.NotLoaded<association :characters is not loaded>,
distributor: %Ecto.Association.NotLoaded<association :distributor is not loaded>,
id: 1,
tagline: "Something about video games",
title: "Ready Player One"
}
iex> movie = Repo.preload(movie, :actors)
%Friends.Movie{
__meta__: %Ecto.Schema.Metadata<:loaded, "movies">,
actors: [
%Friends.Actor{
__meta__: %Ecto.Schema.Metadata<:loaded, "actors">,
id: 1,
movies: %Ecto.Association.NotLoaded<association :movies is not loaded>,
name: "Tyler Sheridan"
},
%Friends.Actor{
__meta__: %Ecto.Schema.Metadata<:loaded, "actors">,
id: 2,
movies: %Ecto.Association.NotLoaded<association :movies is not loaded>,
name: "Gary"
}
], # actors are LOADED!!
characters: [],
distributor: %Ecto.Association.NotLoaded<association :distributor is not loaded>,
id: 1,
tagline: "Something about video games",
title: "Ready Player One"
}
Now we can ask a movie for its actors:
iex> movie.actors
[
%Friends.Actor{
__meta__: %Ecto.Schema.Metadata<:loaded, "actors">,
id: 1,
movies: %Ecto.Association.NotLoaded<association :movies is not loaded>,
name: "Tyler Sheridan"
},
%Friends.Actor{
__meta__: %Ecto.Schema.Metadata<:loaded, "actors">,
id: 2,
movies: %Ecto.Association.NotLoaded<association :movies is not loaded>,
name: "Gary"
}
]
Using Join Statements
We can execute queries that include join statements with the help of the Ecto.Query.join/5
function.
iex> alias Friends.Character
iex> query = from m in Movie,
join: c in Character,
on: m.id == c.movie_id,
where: c.name == "Wade Watts",
select: {m.title, c.name}
iex> Repo.all(query)
15:28:23.756 [debug] QUERY OK source="movies" db=5.5ms
[{"Ready Player One", "Wade Watts"}]
The on
expression can also use a keyword list:
from m in Movie,
join: c in Character,
on: [id: c.movie_id], # keyword list
where: c.name == "Wade Watts",
select: {m.title, c.name}
In the example above, we are joining on an Ecto schema, m in Movie
. We can also join on an Ecto query. Let’s say our movies table has a column stars
, where we store the “star rating” of the film, a number 1-5.
movies = from m in Movie, where: [stars: 5]
from c in Character,
join: ^movies,
on: [id: c.movie_id], # keyword list
where: c.name == "Wade Watts",
select: {m.title, c.name}
The Ecto Query DSL is a powerful tool that provides us with everything we need to make even complex database queries. With this introduction provides you with the basic building blocks to start querying.
Caught a mistake or want to contribute to the lesson? Edit this page on GitHub!