Skip to content

Add new jsonb container backend#157

Merged
shioyama merged 8 commits intomasterfrom
jsonb_container
Jan 24, 2018
Merged

Add new jsonb container backend#157
shioyama merged 8 commits intomasterfrom
jsonb_container

Conversation

@shioyama
Copy link
Owner

This is the first new backend to be added to Mobility since all the original ones were introduced in v0.1.x. I was inspired to add it looking at the Elixir hex package named Trans, which stores all translations for a model in a single depth-2 jsonb column (translations). I've seen this also discussed elsewhere, and thought it would be interesting to implement.

This implementation piggy-backs on parts of the existing Jsonb backend, but in other parts is entirely independent. Specs seem to be passing.

Regarding naming, I'm using the term "container" since this is what Trans uses. In the code I've called the backend "JsonbContainer", but this is quite a mouthful so I'm thinking of just renaming it "container", so you'd use it like this:

class Post
  extend Mobility
  translates :title, backend: :container
end

... or of course just set config.default_backend = :container.

Curious if anyone has any thoughts on this (backend itself, implementation, naming, etc.)

@shioyama
Copy link
Owner Author

Note to self: still need to implement for Sequel.

@shioyama
Copy link
Owner Author

shioyama commented Jan 19, 2018

A query like Post.i18n.where(title: "foo", content: "bar") becomes:

SELECT "posts".* FROM "posts"
WHERE ("posts"."translations" -> 'en' @> '{"title":"foo"}'
AND "posts"."translations" -> 'en' @> '{"content":"bar"}')

This could actually be done with one clause:

SELECT "posts".* FROM "posts"
WHERE ("posts"."translations" -> 'en' @> '{"title":"foo","content":"bar"}')

I'm not sure if there is any performance impact of querying on each key/value separately...

@shioyama
Copy link
Owner Author

And here's a query with a nil value, Post.i18n.where(title: "foo", content: nil), where we use the existence operator:

SELECT "posts".* FROM "posts"
  WHERE "posts"."translations" -> 'en' @> '{"title":"foo"}'
  AND NOT ("posts"."translations" ? 'en' AND "posts"."translations" -> 'en' ? 'content')

And a not query, negating the previous one: Post.i18n.where.not(title: "foo", content: nil) becomes:

SELECT "posts".* FROM "posts"
  WHERE NOT (1=0)
  AND "posts"."translations" ? 'en'
  AND "posts"."translations" -> 'en' ? 'title' AND NOT ("posts"."translations" -> 'en' @> '{"title":"foo"}')
  AND "posts"."translations" ? 'en' AND "posts"."translations" -> 'en' ? 'content'

We are thus looking for posts that have a title other than "foo", and which have a content which is non-nil.

Like the jsonb backend, we depend here on the fact that when storing data, we strip any values which are not present. This allows us to use the existence operator (?) without having to worry about keys with blank/null values -- we just assume there are none.

We can also store/query on non-string values (again, like the jsonb backend). e.g. Post.i18n.where(title: {"a" => "b"}) would produce:

SELECT "posts".* FROM "posts"
WHERE "posts"."translations" -> 'en' @> '{"title":{"a":"b"}}'

And this will correctly match posts which have a hash value of {"a": "b"}. The value stored in the translations column in this case would look like this:

{"en"=>{"title"=>{"a"=>"b"}}}

@shioyama
Copy link
Owner Author

@pwim I'm curious is this is a backend you would consider using. It has a nice balance of ease-of-use (just one column per translated model), performance (jsonb so fast to query) and relatively low complexity (relative to, say, storing translations on a separate table). Implementation is actually quite simple, even querying.

end
include const_set(module_name, dupable)
end
end
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of code just to fix an issue which is arguably a bug in AR 4.2... debating maybe just skipping the dup specs for this backend in that version of Rails.

@shioyama shioyama force-pushed the jsonb_container branch 5 times, most recently from ae33885 to 30e1732 Compare January 23, 2018 13:29
@shioyama shioyama merged commit 625cffc into master Jan 24, 2018
@shioyama shioyama deleted the jsonb_container branch January 24, 2018 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant