Hi,
I would like to announce that I joined total.
I am available for hire at:
http://www.toptal.com/ruby-on-rails#efficient-professional-web-development
Friday, 5 June 2015
Wednesday, 6 May 2015
The benefit of the squeel gem over standard active record
The benefit of the squeel gem over the standard active record
Let's try this query:Field.where(id: 10).unscope(where: :id)
It results in the below SQL:
"SELECT \"fields\".* FROM \"fields\"
Let try to unscope something a bit more difficult:
Field.where('id > 10').unscope(where: :id)
In this case the unscoping didn't work as we intended:
SELECT \"fields\".* FROM \"fields\" WHERE (id > 10)
Let's try now by using squeel gem:
Field.where{id > 10}.unscope(where: :id).to_sql
Now the unstopping works just as we intended:
"SELECT \"fields\".* FROM \"fields\""
Enjoy :)
Friday, 16 January 2015
Testing the ActiveRecord models using Rspec
Usually the skeleton of my models spec looks like this:require 'spec_helper'
describe Car do
context 'class hierarchy' do
#Here comes the class hierarchy specification
end
context 'assotiations' do
#Here comes the enumeration of associations
end
context 'validations' do
#Here comes the validation of models
end
context 'callbacks' do
#Specs for callbacks
end
context 'methods' do
end
endTesting the class hierarchy:
Since the model could include modules which affect the functionality I consider it necessary to assert on them.
describe Car do
context 'class hierarchy' do
specify {expect(subject.class).to be < ActiveRecord::Base}
specify{expect(subject).to be_kind_of(Elasticsearch::Model)}
specify{expect(subject).to be_kind_of(Elasticsearch::Model::Callbacks)}
end
end
I often encountered errors when the model was expected to have a field and that field was missing. So I always assert on the used fields:
describe Car do
context 'fields' do
specify {expect(subject).to respond_to(:name)}
end
end
context 'fields' do
specify {expect(subject).to respond_to(:name)}
specify {expect(subject).to respond_to(:filter)}
end
Testing the associations:
I test the presence of the correct associations using the 'shoulda-matchers' gem.
context 'assotiations' do
specify { expect(subject).to belong_to(:user) }
specify { expect(subject).to belong_to(:manufacturer) }
end
end
specify { expect(subject).to belong_to(:manufacturer) }
end
end
Testing the validations:
I usually test the validations of the fields using the 'shoulda-matchers' gem.
describe Car do
context 'validations' do
specify { expect(subject).to validate_uniqueness_of(:filter).scoped_to(:manufacturer_id)}
end
end
Testing the callbacks:
My callback test usually are like this:
end
Testing the callbacks:
My callback test usually are like this:
describe Car do
context 'validations' do
context 'before_destroy' do
Testing the methods:
Usually I test the methods by calling it the asserting that all the necessary changes are made. I do this for all the execution paths.
context 'validations' do
context 'before_destroy' do
specify 'call destroy like callback'do
expect(equipment).to receive(:destroy_likes)
equipment.destroy
end
end
expect(equipment).to receive(:destroy_likes)
equipment.destroy
end
end
end
end
Usually I test the methods by calling it the asserting that all the necessary changes are made. I do this for all the execution paths.
Thursday, 15 January 2015
Api Development in Ruby On Rails
Recently I wrote a series of blog posts about the best practices of API development in RoR and how to develop API in general.Let me summarize those posts:
- General introduction
- Presenting the Entities
- Performance Optimisation
- Test your API from A-Z
- Organising your routes in API
- Error Handling
Happy API development :)
API development in Rails error path
Let me share my experience with you about what I have learned about proper error handling.
I call error path the case when the user can't achieve what he wants.
This case can happen by the following reasons:
- The user calls an invalid url
- The user addresses a missing resource
- The user misses a mandatory parameter
- The uses passes a wrong parameter
- Some other internal error occurs
- ...
Since thousands of calls can be made daily or hourly against the API there is no way to stop the server and debug it. So all the information related to the erroneous call must be recorded. Basically all the information to reproduce the error must be recorded. I call this ApiCallAudit.
Such an ApiCallAudit must contain:
- All the incoming parameters
- The type of call (GET, POST, DELETE, ...)
- cookies
- backtrace
- created_at
I added some additional fields to it for filtering purposes:
- level. It serves to quickly determine the possible source of errors. It could be parameter_error, unexpected_error, parameter_missing_error, entity_missing_error
- status. It is a default error message.
- code. An error code which uniquely identifies the error.
In the beginning the error code was missing from my design and the mobile clients were using the default error message. This has some disadvantages:
- The mobile UI is usually developed in a different codebase. And the server side error message modification is not possible. Specially if the same chunk of server side code is serving many applications.
- -The language used on the mobile UI can vary. For example the UX developer can decide to use:
- "You haven't provided the group" - First person complaining style.
- "The group is missing" - Passive objective style
- "Please provide a group" - Proactive gentle style
- "Select a group!" - Imperative style
- ...
Happy coding :)
API Development Restfull vs Facebook style
In this blog post I will express my opinion how to organise the API which manages the entities.
Generally speaking I like the REST concept and I organize my API to conform that way. For cases when there are no association among the entities this philosophy is good enough and clear.
However I like the API to reflect the associations when we are dealing with has_many or has_and_belongs_to_many associations. Just like in case of Facebook Graph API. In these cases I find that style more intuitive and I am following that style whenever I deal with associations.
Examples:
Lets suppose that cars and manufacturers can be reviewed.
class Car < ActiveRecord::Base
has_many :reviews, as: :reviewable
class Manufacturer < ActiveRecord::Base
has_many :reviews, as: :reviewable
has_many :reviews, as: :reviewable
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :reviewable, polymorphic: true
In this case the review API would be:
- creation of reviews:
POST /api/cars/{id}/reviews
POST /api/manufacturers/{id}/reviews
- retrieval of reviews:
GET /api/cars/{id}/reviews
GET /api/manufacturers/{id}/reviews
- deletion of reviews:
DELETE /api/reviews/{id}
In the classic restful style the review creation would be:
params do
requires :comment, type: String, desc: "The comment"
requires :reviewable_id, type: Integer, desc: "The id of a reviewable entity"
requires :reviewable_type, type: String, desc: "The type of a reviewable entity"
end
POST /api/reviews
Both solutions have pros and cons. The restful style is more DRY. The Facebook Graph Api style relieves more information about the associations ergo it is more intuitive and easier to use.
Happy coding :)
Friday, 2 January 2015
Specing the API
When you cover your API with specs the first rule is to cover everything.
Since the API can be called by third parties you need to be sure what is happening in every case. You must be able to reproduce every scenario any time and you must have the same results. :) Does it seems scientific? Well... Indeed it is.
You also need to keep your specs DRY
This will reduce maintenance effort of the test code and it will keep your specs more readable and you will have easy time to add new features and you will feel more happy and in control.
As you can see the above steps like checking the response code, checking if the response format is JSON, checking that the audit log is created are repetitive tasks and can be DRY-ed with rspecs shared examples. The only specific thing which changes from one api endpoint to another is "the entities are massaged as needed by that specific case".
RSpec.shared_examples "returning 401" do
specify "returns 401" do
api_call params, developer_header
expect(response.status).to eq(401)
end
end
RSpec.shared_examples "being JSON" do
specify 'returns JSON' do
api_call params, developer_header
expect { JSON.parse(response.body) }.not_to raise_error
end
end
...
Then in your specs you can use these shared examples in particular cases:
context '/API/learn_by_playing/' do
def api_call *params
get "/api/learn_by_playing", *params
end
let(:api_key) { create :apikey }
let(:developer_header) { {'Authorization' => api_key.token} }
context 'GET' do
let(:required_params) do
{
:first_name => 'Botond',
:last_name => "Orban"
}
end
let(:params) { required_params }
it_behaves_like 'restricted for developers'
context 'wrong parameters' do
required_params.keys.each do |s|
context 'when the #{s} parameter is blank' do
let(:params) { required_params.merge({s => ''}) }
it_behaves_like 'returning 400'
it_behaves_like 'being JSON'
it_behaves_like 'creating an audit log'
end
context 'when the #{s} parameter is missing' do
let(:params) { required_params.except(s) }
it_behaves_like 'returning 400'
it_behaves_like 'being JSON'
it_behaves_like 'creating an audit log'
end
Professional DRY hacking ;)
Since the API can be called by third parties you need to be sure what is happening in every case. You must be able to reproduce every scenario any time and you must have the same results. :) Does it seems scientific? Well... Indeed it is.
Let me show you some examples:
-You might need to check that only the calls with developer key can access the API. In the opposite case the response is 401, it is still a JSON and maybe an audit log is created.
-You might need to check that the user is authenticated. If not then the response is 401, it is still a JSON and again an audit log is created.
-If some of the parameters are missing or wrong then the response is 400, it is still a JSON and an audit log is created by logging all the incoming parameters.
-If everything is alright then the response is 200/201, the response is a JSON and the entities are massaged as needed by that specific case.
You also need to keep your specs DRY
This will reduce maintenance effort of the test code and it will keep your specs more readable and you will have easy time to add new features and you will feel more happy and in control.
As you can see the above steps like checking the response code, checking if the response format is JSON, checking that the audit log is created are repetitive tasks and can be DRY-ed with rspecs shared examples. The only specific thing which changes from one api endpoint to another is "the entities are massaged as needed by that specific case".
RSpec.shared_examples "returning 401" do
specify "returns 401" do
api_call params, developer_header
expect(response.status).to eq(401)
end
end
RSpec.shared_examples "being JSON" do
specify 'returns JSON' do
api_call params, developer_header
expect { JSON.parse(response.body) }.not_to raise_error
end
end
...
Then in your specs you can use these shared examples in particular cases:
context '/API/learn_by_playing/' do
def api_call *params
get "/api/learn_by_playing", *params
end
let(:api_key) { create :apikey }
let(:developer_header) { {'Authorization' => api_key.token} }
context 'GET' do
let(:required_params) do
{
:first_name => 'Botond',
:last_name => "Orban"
}
end
let(:params) { required_params }
it_behaves_like 'restricted for developers'
context 'wrong parameters' do
required_params.keys.each do |s|
context 'when the #{s} parameter is blank' do
let(:params) { required_params.merge({s => ''}) }
it_behaves_like 'returning 400'
it_behaves_like 'being JSON'
it_behaves_like 'creating an audit log'
end
context 'when the #{s} parameter is missing' do
let(:params) { required_params.except(s) }
it_behaves_like 'returning 400'
it_behaves_like 'being JSON'
it_behaves_like 'creating an audit log'
end
end
end
context 'valid params' do
specify '...whatever you need to really assert for in this particular case...' do
api_call params, developer_header
expect ...
end
end
end
context 'valid params' do
specify '...whatever you need to really assert for in this particular case...' do
api_call params, developer_header
expect ...
end
end
end
end
As you can see from the above example using Rspec.shared examples a lot of repetitive lines from your Rspec can be thrown out.
On one of my projects I managed to reduce the specs size by 20% and raise the visibility a lot :) I can't measure and therefore I can't express in numbers what "a lot" means but everybody was satisfied and pleased with the result.
On one of my projects I managed to reduce the specs size by 20% and raise the visibility a lot :) I can't measure and therefore I can't express in numbers what "a lot" means but everybody was satisfied and pleased with the result.
Professional DRY hacking ;)
Subscribe to:
Posts (Atom)