July 25, 2016

Filtering has_many relationships in Ecto

This is sot-of a follow up to my last post, about self-referencing many_to_many relationships using Ecto.

I find myself in a scenario where I have a User:

# User model
defmodule MyApp.User do  
  use MyApp.Web, :model

  alias MyApp.Contact

  schema "users" do
    has_many :_internal_contacts, MyApp.Contact
    has_many :contacts, through: [:_internal_contacts, :contact]

    timestamps
  end
end  

and an association model Contact through which the User has many contacts (User instances):

defmodule MyApp.Contact do  
  use MyApp.Web, :model

  alias MyApp.User

  schema "contacts" do
    field :status, :integer

    belongs_to :user, User, foreign_key: :user_id
    belongs_to :contact, User, foreign_key: :contact_id

    timestamps
  end

  def status_code(status) do
    case status do
      :accepted ->
        1
      :pending ->
        0
      :rejected ->
        -1
      _ ->
        0
    end
  end

I created an endpoint on my app that's supposed to get me only the user's contacts that have the :accepted status (1). How can this be accomplished?

The Repo module has a set of handy functions that let you preload associations on your models.

user = User |> Repo.get(1) |> Repo.preload(:contacts)  

The above will preload all contacts on the user. Pay attention to the fact that it is of type has_many and it goes through the :_internal_contacts property. This is the important cue.

Turns out that the preloads parameter on Repo.preload/3 can be accompanied by a query.

import Ecto.Query

#1
query = from c in Contact, where: c.status == 1 and c.user_id == 1

u = User  
    |> Repo.get(1) #2
    |> Repo.preload(_internal_contacts: query) #3
    |> Repo.preload(:contacts) #4

The code above will:

  1. Create a query that will ask for Contacts with status 1 and with user_id 1.
  2. Get a User instance.
  3. Preload the _internal_contacts on that instace using our query (these are Contact instances, not User instances)
  4. Preload the :contacts on our user. And since :contacts goes through: _internal_contacts, :contacts has only valid contacts now (User instances).

Notes:

Honestly, I don't know if this is the right approach here (let me know!). I did find this thread on GitHub where the general issue that we're facing here is described. It seems that there's an interest to have the ability to filter the relationships on the declaration itself:

It would look something like this:

has_many :comments, MyApp.Comment, foreign_key: :commentable_id,  fn (query) ->  
  from c in query, where: c.commentable_type == "articles"
end  

However, right now, the saner approach seems to be the use of composable queries.

Have anything to add to this article or did I miss something? Please let me know on Twitter.


July 23, 2016

Self-referencing many-to-many relationships using Ecto

EDIT: I've changed the last section of this article to reflect my findings in actually using this in production.


I've decided to do the backend for a new app I'm creating myself, using Elixir/Phoenix. I've been learning Elixir for the past few months and thought it would be a great opportunity to put what I've learned to good use.

There's a lot of documentation about the new many_to_many macro in Ecto 2 — but somehow, it still was a bit of a pain to get this right the first time. I couldn't find a concrete example of how a self-referencing many-to-many relationship would work with Ecto, and after some digging and the help of the awesome Elixir community, I got it working. I'm writing this post just to put this out there so you can go through this quicker.

In the app, a User can have contacts, which are themselves Users. (Let's omit the authentication stuff for now) The migration looks like this:

# priv/repo/migrations/create_users_table.ex
defmodule MyApp.Repo.Migrations.CreateUsersTable do  
  use Ecto.Migration

  def change do
    create table(:users) do
      add :username, :string
    end

    create unique_index(:users, [:username])
  end
end  

Then, the module that defines the schema looks like this:

defmodule MyApp.User do  
  use MyApp.Web, :model

  schema "users" do
    field :username, :string

    timestamps
  end

  # Omitting changesets
end  

In order to create a many-to-many relationship correctly we must have an associative table, that we'll call "contacts". This is the migration for that:

defmodule MyApp.Repo.Migrations.CreateContactsTable do  
  use Ecto.Migration

  def change do
    create table(:contacts) do
      add :user_id, references(:users, on_delete: :nothing), primary_key: true
      add :contact_id, references(:users, on_delete: :nothing), primary_key: true
      add :status, :int

      timestamps()
    end
  end
end  

To expand a bit, the "Contacts" table has... :

  1. a column user_id, which is the id for the user that initiated the "friend request".
  2. a column contact_id, which is the id for the user that must accept or reject the "friend request"
  3. a column status, which represents the friendship status (0=pending, 1=accepted, -1=rejected).

Now for the interesting part, we have to create a "Contacts" schema that Ecto can work with in order for the association to work correctly. On to it:

defmodule MyApp.Contact do  
  use MyApp.Web, :model

  alias MyApp.User

  schema "contacts" do
    belongs_to :user, User
    belongs_to :contact, User
  end
end  

We define the Contact module, that belongs to both a user and a contact, both being User types. We now have to update the User model to reflect the many-to-many relation:

defmodule MyApp.User do  
  use MyApp.Web, :model

  schema "users" do
    field :username, :string
    # Add the many-to-many association
    many_to_many :contacts, MyApp.User, join_through: "contacts", on_replace: :delete
    timestamps
  end

  # Omitting changesets
end  

Using the many_to_many macro we only have to specify the field on the User model, tell Ecto what table use as association table, and that's it!

You can now do this in your code:

user = Repo.get(User, 1) |> Repo.preload(:contacts)  
contact = hd(user.contacts)  

However...

A member of the Elixir community told me on Slack that this looks more like a one-to-many relationship, and that I should try to represent it that way in my app. It would look like this:

defmodule MyApp.User do  
  use MyApp.Web, :model
  alias MyApp.Contact

  schema "users" do
    field :username, :string
    # Add the many-to-many association
    has_many :_contacts, MyApp.Contact
    has_many :contacts, through: [:_contacts, :contact]
    timestamps
  end

  # Omitting changesets
end  

It works, however I don't like the fact that now we basically have cluttered our model, having _contacts and contacts in it. Then there's the fact that Ecto models are just backed by a simple struct, so there's no way to "hide" details about it (as-in: make _contacts private). I decided to stick with the many_to_many version of this code because it hides the association table quite nicely.

After playing with this for a bit, I realised that this is the correct approach, although it clutters the user instance space.

During actual testing in my Phoenix app, I noticed that the query that Ecto was generating with the former association type was the wrong one. Switching it from many_to_many to has_many as above, fixed the issue.

Have something to add to this article, or did I miss something? Hit me up on Twitter.


June 27, 2016

Developer Insights, from Apple

With all the chatter around making a living on the App Store, and the recent changes to the App Store subscription model for apps, it seems that Apple is now trying to tell us how to create a successful business on top of their platforms.

This one is definitely worth a read.


June 23, 2016

Swift 3 Migration issues

This is a post that I'll be updating continuously with all the bumps that I find on the way while developing with Swift 3 and iOS 10.


CloudKit's database fetchAll methods are missing annotations on Swift 3

Swift 3 API design guidelines favor clarity over brevity. To accomplish that, many of the API's that we interact with on a daily basis changed radically.

For instance, this method that we've known for a long time:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)  

changed to:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)  

These changes are important as help us write more legible code.

On Apple's CloudKit framework, with Swift 2, you could call

func fetchAllRecordZonesWithCompletionHandler(_ completionHandler: ([CKRecordZone]?,  
                                                       NSError?) -> Void)

to get all Record Zones for a given database, or call

func fetchAllSubscriptionsWithCompletionHandler(_ completionHandler: ([CKSubscription]?,  
                                                         NSError?) -> Void)

to get all Subscriptions.

However, the API changes on Swift 3, now both methods are named almost the same, with the completion handler's parameters types being the only ones that change:

func fetchAll(completionHandler: ([CKRecordZone]?, NSError?) -> Void) // to get Record Zones  
func fetchAll(completionHandler: ([CKSubscription]?, NSError?) -> Void) // to get Subscriptions  

Trying to use any one of those functions make the compiler choke because it doesn't have enough information to know what you're trying to fetch, unless you type the parameters yourself:

db.fetchAll { (zones: [CKRecordZone]?, error) in  
    // ...
}

To me, this is a clear example where the API design guidelines are not being respected, favoring brevity over clarity.

Submitted as rdar://26977470.


June 15, 2016

Updating my codebases to Swift 3

Apple officially released the first beta of Xcode 8 this past Monday, which includes Swift 2.3 (an updated version of the language to work with the new APIs on iOS 10 and macOS 10.12), and Swift 3, the latest big release for the language.

Swift 3 basically breaks your project. If you're writing Swift and planning to continue doing so, my suggestion is: take the plunge right now and start migrating your project to Swift 3 right away.

That is, of course, providing you've got the time and the resources to do so.

In my case, I downloaded Xcode 8 and ran the migrator on a small project (about 1000 lines of Swift 2) with little success. The API changes that Swift 3 brings, such as removing namespaces, are big.

What I'm doing instead of trying to incrementally change my codebase (because the project wouldn't even try to compile to show errors), is to update each part one by one on a new project, which eventually will replace my codebase.

It may not be the best route for everyone, specially if you're dealing with large codebases.

This fall is shaping to be a busy one.


April 22, 2016

› Rewrite from Scratch - Autopsy of a Failed Software Project

This post brought a ton of memories back to mind. I've wanted to start a project from scratch far too many times more than I'd care to admit.

What I've found in my experience as a software developer is that trying to call the right shots from the beginning is a core part of the development process. Often this is the hard path, but I've found myself in situations where I need to change something, and thanked my past self for doing what he did.

Also remember that software is never finished.


April 12, 2016

Let's Encrypt leaves beta

Finally.

Encryption has never been easier. Such important sponsors put my mind at ease now, I'm going to be using Let's Encrypt for my projects from now on.


March 30, 2016

Another favourite from Xcode 7.3

In previous versions of Xcode, if you were working with Swift and tried to override a var from a super-class, you had no auto-completion suggestions. Most if the time, I had to go into the super declaration to copy the signature and then just add override in front of it.

Xcode 7.3 changes that, for good.

Yay! 🎉


March 23, 2016

#selector

One of my favourite changes in Swift 2.2 is that Objective-C selectors now are first class citizens in the Swift ecosystem. So long to error-prone selector names as Strings.

Swift 2.2 lets you use the #selector expression to reference a selector from your Swift code.

class MyView: UIView {  
    private lazy var button: UIButton = {
        let b = UIButton()

        // Swift 2.1 and earlier
        b.addTarget(
            self,
            action: "buttonTapped:",
            forControlEvents: .TouchUpInside
        )

        // Swift 2.2
        b.addTarget(
            self,
            action: #selector(MyView.buttonTapped(_:)),
            forControlEvents: .TouchUpInside
        )
        return b
    }()

    func buttonTapped(sender: UIButton) {
        // ...
    }
}

My only complain about this is that the actions still need to be declared either internal or public, declaring the action as private returns the error Argument of '#selector' refers to a method that is not exposed to Objective-C, which makes sense if yout think about it.

EDIT: I should have read the error message more carefully.

Turns out that if you want your selector to be private, you only need to add the @objc attribute to the function declaration, and you're set:

extension Selector {  
    static let cancelButtonTapped = #selector(MyView.cancelButtonTapped)
}

class MyView: UIView {  
    @objc private func cancelButtonTapped() {
        // ...    
    }
}