Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
PgJbuilder provides a wrapper around PostgreSQL's JSON functions (array_to_json and row_to_json) allowing you to write queries that serialize their results directly to a JSON string. This completely bypasses creating ActiveRecord objects and using Arel giving a large speed boost. It is especially useful for creating JSON APIs with low response times.
Using PostgreSQL to serialize your query results to JSON is much faster than serializing the records inside of Ruby.
Add to your Gemfile:
gem 'pg_jbuilder'
And then execute:
$ bundle
PgJbuilder requires:
Compatible with Rails 3.0+
If you're using Rails you don't need to do any additional setup. To use the gem outside of Rails there are a few things you need to do:
Set the database connection. This needs to be an ActiveRecord connection.
PgJbuilder.connection = ActiveRecord::Base.connection
This can also be a lambda{} that when called returns a connection.
Set the path where your queries will be. For example if your queries are in the app/queries directory:
PgJbuilder.paths.unshift File.join(File.dirname(__FILE__),'app','queries')
The examples below are for Rails. For non-Rails applications where
the examples below use select_object
and select_array
you can use
PgJbuilder.render_object
, PbJbuilder.render_array
,
PgJbuilder.render
to render your
queries. Once rendered they can be sent to your database and will return
a single string of JSON. For example:
def user_json id
sql = PgJbuilder.render_object 'users/show', id: id
ActiveRecord::Base.connection.select_value(sql)
end
For Rails applications queries are expected to be in app/queries
. You can change this
by creating an initializer and adding a different path to PgJbuilder.paths
(see the example in Initializing the gem in a non-Rails environment).
app/queries/users/show.sql
:SELECT
users.id,
users.email,
users.first_name,
users.last_name
FROM users
WHERE id = {{id}}
ORDER BY id ASC
class User < ActiveRecord::Base
def show_json
select_object 'users/show', id: id
end
end
Note that queries use Handlebars for templating. We pass in the id to
select_object
then the {{id}}
in the template will be replaced
with this value. Read more on Handlebars syntax on their
website.
This query would return a JSON object like:
{
"id": 1,
"email": "mbolton@initech.com",
"first_name": "Michael",
"last_name": "Bolton"
}
Since this is a JSON object and not an array the query must return only a single row. If more than one row is returned by the query PostgreSQL will raise an error and the query will fail.
show_json
method added to User
to return the user as
JSON. For example if you were using this in a JSON API then in your controller you might use:class UsersController < ApplicationController
before_filter :load_user
def show
render json: @user.show_json
end
private
def load_user
@user = User.find(params[:id])
end
end
app/queries/users/index.sql
like this:SELECT
users.id,
users.email,
users.first_name,
users.last_name
FROM users
ORDER BY id
User
model that renders the array:class User < ActiveRecord::Base
def self.index_json
select_array 'users/index'
end
end
This would return a JSON array like this:
[
{
"id": 1,
"email": "mbolton@initech.com",
"first_name": "Michael",
"last_name": "Bolton"
},
{
"id": 2,
"email": "pgibbons@initech.com",
"first_name": "Peter",
"last_name": "Gibbons"
},
{
"id": 3,
"email": "snagheenanajar@initech.com",
"first_name": "Samir",
"last_name": "Nagheenanajar"
}
]
User
model to return the JSON. For
example in your controller you might add:class UsersController < ApplicationController
def index
render json: User.index_json
end
end
You can use the {{quote}}
helper to escape user inputted values to
make them safe to include in the query. For example if your query is
app/queries/users/search.sql
:
SELECT users.id
FROM users
WHERE
users.first_name = {{quote first_name}}
and you call the query:
select_array 'users/search', first_name: 'John'
it will render the query as:
SELECT users.id
FROM users
WHERE
users.first_name = 'John'
Without the quote helper it would render as:
SELECT users.id
FROM users
WHERE
users.first_name = John
without the quotes which would allow SQL injection attacks. {{quote}}
will also escape quotes for example:
select_array 'users/search', first_name: "Jo'hn"
will render as:
SELECT users.id
FROM users
WHERE
users.first_name = 'Jo''hn'
You can include partials in your template using the {{include}}
helper. For example you might refactor the SELECT portion of your query
into its own partial app/queries/users/select.sql
SELECT
users.id,
users.first_name,
users.last_name,
users.email
Then in app/queries/users/show.sql
you would have:
{{include 'users/select'}}
FROM users
WHERE id = {{id}}
Variables passed into a query will automatically be passed into the
partial. In the above example there is a {{id}}
variable. You would
also be able to use this variable in the partial.
You can pass additional variables into the partial using this syntax:
{{include 'template_name' variable1='value' variable2='value' ...}}
You can embed objects using the {{object}}
helper. For example if you
want to have a user object inside a your comment index in
app/queries/comments/index.sql
:
SELECT
comments.id,
comments.body,
{{#object}}
SELECT
users.id,
users.first_name,
users.last_name,
users.email
FROM users
WHERE
users.id = comments.user_id
{{/object}} AS user
FROM comments
ORDER BY id
This would create a JSON object like:
{
"id": 1,
"body": "This is my comment",
"user": {
"id": 100,
"username": "witty_commenter"
}
}
You can also refactor the object into a partial. So you could create a
query in app/queries/users/object.sql
:
SELECT
users.id,
users.first_name,
users.last_name,
users.email
FROM users
WHERE
users.id = {{id}}
Then include it using this syntax in app/queries/comments/index.sql
:
SELECT
comments.id,
comments.body,
{{object 'users/object' id='comments.user_id'}} AS user
FROM comments
ORDER BY id
This would produce the same JSON as above.
Embedding arrays works just like embedding objects but uses the
{{array}}
helper. For example if you have a user object in
app/queries/users/show.sql
and want to return a list of the user's
comments inside the user object:
SELECT
users.id,
users.first_name,
users.last_name,
users.email,
{{#array}}
SELECT
comments.id,
comments.body
FROM comments
WHERE comments.user_id = users.id
{{/array}} AS comments
FROM users
WHERE id = {{id}}
This would return a JSON object like:
{
"id": 1,
"username": "witty_commenter",
"comments": [
{
"id": 100,
"body": "Witty Comment #1"
},
{
"id": 200,
"body": "Witty Comment #2"
}
]
}
Just like with {{object}}
you can refactor your arrays into a partial.
So if you have app/queries/users/comments.sql
SELECT
comments.id,
comments.body
FROM comments
WHERE comments.user_id = {{user_id}}
then in app/queries/users/show.sql
you can have:
SELECT
users.id,
users.username,
{{array 'users/comments' user_id='users.id'}} AS comments
FROM users
WHERE id = {{id}}
To do pagination you need to execute two queries. One to count the rows, then another to return the results with a LIMIT and OFFSET. To accomplish this with pg_jbuilder your query would have to look like this:
SELECT
{{#if count}}
COUNT(*) AS total_rows
{{else}}
comments.id,
comments.body
{{/if}}
FROM comments
{{#unless count}}
ORDER BY id
LIMIT {{per_page}}
OFFSET ({{quote page}} - 1) * {{per_page}}
{{/unless}}
Then in your model:
class Comment < ActiveRecord::Base
PER_PAGE = 20
def self.count_index_json attrs={}
attrs[:count] = true
attrs[:per_page] = PER_PAGE
select_value('comments/index').to_i
end
def self.index_json attrs={}
attrs[:per_page] = PER_PAGE
select_array 'comments/index', attrs
end
end
select_value
will return render your query and return a single value
from it.
And in your controller:
class CommentsController < ApplicationController
def index
count = Comment.count_index_json(index_params)
headers['X-Pagination-Total-Entries'] = count.to_s
render json: Comment.index_json(index_params)
end
private
def index_params
params.permit :page
end
end
The API consumer can then read the X-Pagination-Total-Entries
to see the
total number of entries and can pass a page
parameter to specify which
page to fetch.
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)FAQs
Unknown package
We found that pg_jbuilder demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.