I was working on a simple(I thought it was simple) feature wherein I wanted to list parent models. In the same table, particular
child models for each parent were to be listed too. Say, a list of accounts, each with its latest 3 transactions. This feature
turned out to be lot more tougher than I thought and was an excellent learning curve. Below are the details.
Lets say that we have a simple application consisting for 2 models, Account and Transaction.
Now, as stated earlier, we need to list the accounts and last 3 transactions for each account.
1234
defindex# for sake of simplicity, lets load first 5 accounts.@accounts=Account.limit(5)end
12345
# displaying the accounts
-@accounts.eachdo|account|=displaytheaccount'sdetails-account.transactions.order(created_at:desc).limit(3).eachdo|t|=eachtransaction'sdetails
This is not the right solution because it fires a query to load last 3 transactions for each account causing the N+1 problem.
‘including’ the transactions in the index was of no use too since that would load all the transactions for the 5 accounts and we
just wanted the latest 3.
I had read somewhere that we can define relations as scopes. Since we can order/filter the data in the scope, I created a scope to
load the required transactions.
Now, since we can include an association for eager loading, I can definitely(I though so) use the newest association to load the
accounts(first 5) and their required(latest 3) transactions.
1234
defindex# for sake of simplicity, lets load first 5 accounts.@accounts=Account.limit(5).includes(:latest_transactions)end
Turns out, this doesn’t work as expected. The queries for the above code were ignoring the limit clause and retrieving all the
transactions for the accounts. The only thing that worked was the ordering.
Anyways, the limit clause in the above query wasn’t going to solve the problem because we needed the limit clause on each
account’s transactions and not all the transactions as a whole. My bad.
So, upon futher searching, I was directed to checkout the windows function provided in Postgres. Here is the
documentation and
examples. Please check them out for better clarity.
Using the windows function, we can perform aggregate(sort, order, group, limit etc) on partitioned data. The 3 function that we
can use in the current situation are row_number, rank and dense_rank.
row_number: number of the current row within its partition, counting from 1
rank: rank of the current row with gaps; same as row_number of its first peer. ie, if N records have the same rank X, then the
next record in the partition has rank N+X rather than X+1. eg, if you have 5 items at rank 2, the next rank listed would be
ranked 7 and not 3.
dense_rank: rank of the current row without gaps; this function counts peer groups. ie, if N records have the same rank X, then
the next record in the partition has rank X+1. eg, if you have 5 items at rank 2, the next rank listed would be ranked 3.
We can use any of the 3 functions since the ranking is going to be based on created_at(DateTime) which is always going to be
unique across all records in the table.
So basically, we would be dividing the transactions collection based on the account_id, order each partition in descending order
of created_at and ranking each record of the set based on created_at. So, the newest record would have the lowest rank.
This lists all the transactions partitioned/grouped by transactions.account_id and order in desc order of created_at. Since the
created_at is going to be unique across all the transactions in the table, the combination of (account_id and t_rank) is unique.
Since we need transactions with t_rank <= 3, we use the above query in a nested select statement.
Now, we have a query that retrieves only the last 3 transactions for specific accounts. Since, we tried using the association with
includes but couldnt work it out, the only option is to skip includes and load the data explicitly.
So, we declare a class to perform the task. Our class should accept an collection of accounts, retrieve the associated
transactions using the query defined above and should assign the related transactions to each of the passed accounts.
1234567891011121314151617181920212223
classLatestTransactionsdefinitialize(accounts)@accounts=accountsendattr_accessor:accounts#generate the inner sql qwerydefinner_queryids=@accounts.collect&:idTransaction.select('*, dense_rank() over( partition by transactions.account_id order by transactions.created_at ) as t_rank').where(account_id:ids).to_sqlend#create complete query and execute.deftransactionsTransaction.select('*').from("(#{inner}) as latest_transactions").where('t_rank <= 3')endend
Finally, we have a service which accepts a collection of Account and fetches their latest transactions. Now, the only problem
remaining is grouping the transactions as per their account and assigning the group to the particular account. I fixed this by
adding an attr_accessor to the account model.
# displaying the accounts and their latest 3 transactions
-@accounts.eachdo|account|=displaytheaccount'sdetails-account.latest_transactions.eachdo|transaction|=eachtransaction'sdetails
The above code fires only 2 queries, first to load the 5 accounts and the second to load the required transactions
In the previous post, we looked at the basic concepts in
React. In this post, I would like to explain and demonstrate identifying and creating components with a simple example app.
We would be creating a simple React app as below:
Here, we have 3 main sections: the summary of all the transactions, the form to enter the transaction details and the table
displaying the transactions. So, we can say that we have 3 components. If the component becomes complex, we can futher
break it down to multiple components. For now, this is enough for us to get started. Since all the 3 components are on the same level,
we will hold them collectively held under a component(say TransactionsDetails)
So, the hierarchy of components is going to be as
TransactionsDetails
TransactionsSummary
TransactionForm
TransactionsTable
TransactionRow
For sake of simplicity, we will integrate Bootstrap after the entire app is up and running.
Then, we add the child components: TransactionsSummary, TransactionForm & TransactionsTable. We are displaying
TransactionsSummary & TransactionForm adjacent to each other. Below them, TransactionsTable is displayed.
Now, lets start adding details for each component. I prefer displaying static data. Later, when all the components are in
place and the static data is displayed properly, I start making changes for dynamic data.
Now that the components skeleton is in place, lets start making changes for dynamic data. Lets fix the TransactionForm first.
Since, the TransactionForm is the only place in the application from where data is going to be inserted, lets add states date,
reason and amount to the TransactionForm component.
Now that we have added the basic states to the TransactionForm and have functions to update form states when user enters data,
we need to add logic to accept the form submit functionality. Here, after accepting the form data, we need to save the record in
the TransactionsDetails state since TransactionsSummary and TransactionsTable are the components that are going to refer to
this state to update themselves.
Since the form is working and the entered transactions are being saved in TransactionsDetails’s state.records, we move forward by
displaying the dynamically added records in the table. For this, we introduce a new component named TransactionRow.
Now that we have the form and the table working, the only section remaining is the TransactionsSummary. Here, we are neither going
to pass the state of TransactionsDetails but are passing the return values of functions.
Now that we have a working example that looks good, lets try to add the edit and delete options to alter the transactions. For
this, we would be displaying the ‘Edit’ & ‘Delete’ buttons for each TransactionRow. When clicked on ‘Delete’ it should remove
the record from table. When clicked ‘Edit’ it should load the transaction in TransactionForm for editing.
Adding the functionality to edit the transaction record is going to be tricky because we need to inform another sibling component
(TransactionForm) that it needs to load data for a selected record. We need to identify whether a record being editted in the
form is new or existing. Hence, we add a new state editing to TransactionForm which will be true if a record is being edited.
Also, we add a state recordToEdit to TransactionsDetails. It points to record to be editted when ‘Edit’ is clicked.
The function
componentWillReceiveProps is a
lifecycle function provided by React. It is called when the component is being rerendered. It has the updated props that are
passed as arguments to the function and can be accessed before the component is rerendered.
Now, the last functionality that we need to add is updating a TransactionsTable. We have already implemented to load a transaction
in the form so that it can be updated.
Finally, we have a simple working application created using React. I know its not perfect and am definitely sure that there are
some issues with it. But, our aim was to identitying, creating and linking the components. I hope the post achieved the aim. The
source code can be found here.
Since last couple of days, I have been experimenting with ReactJS. I followed the official
Tutorial, Thinking in React
and some other blogs. This post is a summary of what I learned. I will try to keep is as simple as possible.
What is React? React is a JavaScript library to manage and render Views(V in MVC). The views are rendered by React
Components.
Lets read some simple code. A simple ‘Hello React’ program.
In the above code sample, we have a HTML page with some JS code defined inside the script tag. The JS code has some HTML too. This HTML inside JS is
called JSX. Babel is a library to parse JSX. React uses JSX to render the HTML.
Some basic terminologies:
Component: A component is an entity that is supposed to do only one thing. Ideally, it should follow Single Responsibility
Principle. The main task of a component is to render data(props/state). Component doesn’t inherit other Component but
data(state/props) is passed form one Component(parent) to another Component(child).
state: A Component’s data that it has not inherited from the parent Component is called state.
props: Data that a Component inherits from the parent/Ancestor Component is called props(properties).
React follows unidirectional data flow. What it means is that a Component(parent) passes data(state/props) to another
Component(child). When the data of the parent is altered, the child component automatically update itself.
In the javascript of the above code sample, we have 2 entities named ReactDOM and HelloWorld. In React, these are called
Component. In the above code, the ReactDOM is calling the HelloWorld component passing some data(ie. name=“React”).
Hence, the parent component is ReactDOM and the child component is HelloWorld. message is the state of
HelloWorld whereas name is its props.
Some important points:
Component must implement the render function.
When state is passed down to child components, it becomes props for child components. ie. state of parent Component will
always be props of child Component.
A component can alter(update/delete) it’s state since it belongs to the component. state is mutable.
A component should not alter(update/delete) it’s props since they don’t belong to the component. props is immutable.
If props are to be altered, then they should be altered in the component where they are declared. When mounting the child
Component, the callbacks that need to be called to update/delete the props should be passed too.
React will update all the child components using the props once the parent component’s data(state) has been updated.
When child component receives data(props) from the parent, do not assign it to child component’s state and then use this state
to render html. Use the props directly in the render function. The state is set when the component is mounted for the first
time. you will have to explicitly unmount and remount the child component to rerender the update state data.
child Component can call parent Component’s only those methods that have been passed on to it.
If a state is refered in multiple components of different types, the state should be added to the most recent ancestor of the
components.
We will create a simple example wherein a User can enter message(eg. ‘Hello’) and a name(eg. ‘Prasad’). The page should
display the message and name.
For sake of keeping this post short and simple, I won’t be adding the entire HTML but only the necessary(updated) code.
Also, 1) The code sample would contain only the changes for the components. 2) We have the code sample followed by the explanation.
The above code sample will fail because every Component created using React.createClass must implement the render function.
The detailed error can be viewed in the browser’s console.
We updated the Greet component to set some initial value for message and defined the render function to display the
message. Since, message has been defined in the same component, hence, message is Greet’sstate.
Here, we added a state named name to User. We are rendering the propsmessage and statename in the User’s render function
along with 2 input fields wherein User can enter the custom message and name that he/she wants to be displayed.
Here, The handleNameChange function receives the event and retrieves the updated value from the target. React provides us a
function named setState using which we have updated the statename. If we update the statename without using the setState
function, the updated name is not displayed. This is because the setState informs React that an state has been updated and that
the DOM needs to be updated.
Here, the function updateMessage will receive the updated value and will update the statemessage. Also, we need to pass the
function to User component. When the user types the message in the textbox, the handleGreetChange function is called. This
function retrieves the entered value and passes it handleGreetChangeprops as a parameter. Since, React implements
unidirectional data flow, any components using the propsmessage are updated with the updated value.
We didn’t pass the handleMessageChangeprops to the User component. Now, when we enter the message, the same is not being
displayed on the page. ie. Every parent component props(state/props/function) that you want to access in the child has to be
explicitly passed on to the child Component.
Here, we assigned the messageprops to a messagestate and displayed the messagestate. When we enter the new message in the input
box, the Greet’s messagestate is being updated(can be viewed in browser console) but the same in not reflected on the page. This
is because the getInitialState is called only when the component is mounted. Its like an initializer/constructor.
Summerizing, the post is about the the key concepts and their relations to get started with React. Hope it was interesting.
After implementing sorting on keys of a json column data, I need to add filtering too. We are using
ActiveAdmin(1.0.0-pre1) with postgres 9.4. You can check my
previous post regarding sorting.
For sake of continuity, lets take the same example as in the previous post. We have a model named Box which
stores (length, width & height) as json in a column named dimensions.
In ActiveAdmin, the filters can be displayed as below
12345678910111213141516171819
ActiveAdmin.registerBoxdofilter:length,as::numeric,label:'Length'filter:breadth,as::numeric,label:'Breadth'filter:height,as::numeric,label:'Height'filter:volume,as::numeric,label:'Volume'controllerdodefscoped_collectionBox.select("*, dimensions ->> 'length' as length, dimensions ->> 'breadth' as breadth, dimensions ->> 'height' as height")endendindexdocolumn:length,label:'Length',sortable:"dimensions->>'length'"column:breadth,label:'Breadth',sortable:"dimensions->>'breadth'"column:height,label:'Height',sortable:"dimensions->>'height'"column:volume,label:'Volume',sortable:trueendend
Since some of the fields are not associated with the table, we need need to define custom ransackers for them.
ActiveAdmin uses ransack gem for implementing searching.
1234567891011121314151617181920212223242526
classBox<ActiveRecord::Baseransacker:lengthdo|parent|# '->>' is a part of the syntax for accessing the json keys, 'dimensions' the column name and # 'length' is the actual key on which the search is to be implemented.Arel::Nodes::InfixOperation.new('->>',parent.table[:dimensions],Arel::Nodes.build_quoted('length'))endransacker:breadthdo|parent|Arel::Nodes::InfixOperation.new('->>',parent.table[:dimensions],Arel::Nodes.build_quoted('breadth'))endransacker:heightdo|parent|Arel::Nodes::InfixOperation.new('->>',parent.table[:dimensions],Arel::Nodes.build_quoted('height'))end# The below ransackers do not work. Also, we need to declare three ransackers :length_equals, :length_greater_than & # :length_less_than for each field. Dont know the reason but am looking into it.ransacker:length_greater_than,formatter:->(value){data=where("dimensions ->> 'length' > '#{value}'").pluck:iddata=data.present??data:nil}do|parent|parent.table[:id]endend
This still won’t work. The filters are displayed weirdly(dropdown on top and search input field at the bottom) rather in
the expected layout(dropdown on left and search input field on the right). This is because ActiveAdmin adds a class
select_and_search which decides if a filter field is searchable or not. This class is responsible for the proper
layout and working of filters. The below code is referenced from ActiveAdmin. It decides whether the class is to be
added or not when the filter is initialized.
1234567891011121314151617181920212223242526
moduleActiveAdminmoduleInputsmoduleFiltersmoduleBasemoduleSearchMethodSelectdefwrapper_html_optionsopts=super(opts[:class]||='')<<' select_and_search'unlessseems_searchable?optsendendendendendendmoduleActiveAdminmoduleFiltersmoduleFormtasticAddonsdefseems_searchable?# ransacker? checks if any custom ransackers are defined. has_predicate?||ransacker?||scope?endendendend
So, we remove the check for ransacker? by adding a patch in the initializers.
Now, the filters will be displayed properly and would send the selected option(Greater than, Equals or Less than) with
the specified value to the ransackers.
I am not inclined towards overriding the library methods but haven’t found any other solution for the same. So, for now,
this it the hack that works for me.
Working source code can be found here. Hope it was interesting.
Recently, I had a requirement wherein I need to have sorting on the keys of a json column data. We are using postgres 9.4 and the
schema of the json data is static for all the records. The keys needed to be displayed as columns with sorting enabled against them.
Below is the way I implemented it. Also, on digging into ActiveAdmin, I learned some interesting implementation details of the library.
Lets take an example. We have a model named Box which stores (length, width & height) as json in a column named dimensions.
In ActiveAdmin, columns can be displayed for non-table fields. All you have to do is project them in the query. In the below example,
length, breadth and height are not columns of boxes but have been projected in the select query.
1234567891011121314
ActiveAdmin.registerBoxdocontrollerdodefscoped_collectionBox.select("*, dimensions ->> 'length' as length, dimensions ->> 'breadth' as breadth, dimensions ->> 'height' as height")endendindexdocolumn:length,label:'Length',sortable:"dimensions->>'length'"column:breadth,label:'Breadth',sortable:"dimensions->>'breadth'"column:height,label:'Height',sortable:"dimensions->>'height'"column:volume,label:'Volume',sortable:trueendend
When defining the columns to be displayed, we pass the query by which the json key would be accessed when sorting is applied.
By default ActiveAdmin appends _asc and _desc to the table columns when sorting. Here, we pass the query overriding the default column name.
ActiveAdmin has a method named apply_sorting
which check if the order is valid or not and calls the ordering query.
12345678910111213141516171819202122232425
# When sorting the breadth in descending order, the params sent to the controller are as below.# {"order"=>"dimensions->>'length'_desc", "controller"=>"admin/boxes", "action"=>"index"}defapply_sorting(chain)params[:order]||=active_admin_config.sort_order#default order for sorting which is 'id_desc'orders=[]params[:order].split('_and_').eachdo|fragment|order_clause=ActiveAdmin::OrderClause.new(fragment)#dimensions->>'breadth'_desc# <ActiveAdmin::OrderClause:0x007fad6dfe @column="dimensions->>'length'", @field="dimensions->>'length'", # @op=nil, @order="desc">iforder_clause.valid?#check if field.present? and order.present?orders<<order_clause.to_sql(active_admin_config)endendiforders.empty?chainelsechain.reorder(orders.shift).order(orders)# executes SELECT *, dimensions ->> 'length' as length, dimensions ->> 'breadth' as breadth, # dimensions ->> 'height' as height # FROM "boxes" ORDER BY "boxes".dimensions->>'length' desc;endend
The last change that we need is to rewrite the
ActiveAdmin::OrderClause. The OrderClause parses the field and order
string(say, volume_desc or dimensions->>‘length’_desc) and splits it into the field name, operation and the order thats to be applied.
# Original codemoduleActiveAdminclassOrderClauseattr_reader:field,:orderdefinitialize(clause)clause=~/^([\w\_\.]+)(->'\w+')?_(desc|asc)$/@column=$1@op=$2@order=$3@field=[@column,@op].compact.joinenddefvalid?@field.present?&&@order.present?enddefto_sql(active_admin_config)table=active_admin_config.resource_column_names.include?(@column)?active_admin_config.resource_table_name:niltable_column=(@column=~/\./)?@column:[table,active_admin_config.resource_quoted_column_name(@column)].compact.join(".")[table_column,@op,' ',@order].compact.joinendendend# Updated code in config/initializers/hacked_order_clause.rbmoduleActiveAdminclassOrderClauseattr_reader:field,:orderdefinitialize(clause)clause=~/^([\w\_\.]+)(->'\w+')?_(desc|asc)$|^([\w\_\.]+->>'[\w\_]+')(->'\w+')?_(desc|asc)$/@column=$1||$4@op=$2||$5@order=$3||$6@field=[@column,@op].compact.joinenddefvalid?@field.present?&&@order.present?enddefto_sql(active_admin_config)table=column_in_table?(active_admin_config.resource_column_names,@column)?active_admin_config.resource_table_name:niltable_column=(@column=~/\./)?@column:[table,json_column?(@column)?@column:active_admin_config.resource_quoted_column_name(@column)].compact.join(".")[table_column,@op,' ',@order].compact.joinendprivatedefjson_column?(column)column.include?('->>')enddefcolumn_in_table?(names,column)column=json_column?(column)?column.split('->>')[0].strip:columnnames.include?(column)endendend
Working source code can be found here. Hope it was interesting.