-
Notifications
You must be signed in to change notification settings - Fork 299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce ActiveRecord like methods to Quickbooks Models #330
base: master
Are you sure you want to change the base?
Changes from all commits
e60e6d9
d50629e
afaa8bc
2553353
8bcbc76
c98c095
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,8 +73,14 @@ OAUTH_CONSUMER_SECRET = "OAUTH_CONSUMER_SECRET" | |
:authorize_url => "https://appcenter.intuit.com/Connect/Begin", | ||
:access_token_path => "/oauth/v1/get_access_token" | ||
}) | ||
|
||
Quickbooks::Configuration.set_oauth_consumer(QB_OAUTH_CONSUMER) | ||
|
||
Quickbooks::Configuration.set_tokens_and_realm_id(token, secret, realm_id, :scope => :application) | ||
``` | ||
|
||
You can pass an optional key value pair as last argument to `set_tokens_and_realm_id` method. The key value pair can be `:scope => :application` or `:scope => :thread`. If you want to set the tokens, secret and realm_id on application then you need to pass `:scope => :application`. The default value for `:scope` is `:thread`. | ||
|
||
To start the authentication flow with Intuit you include the Intuit Javascript and on a page of your choosing you present the "Connect to Quickbooks" button by including this XHTML: | ||
|
||
|
||
|
@@ -175,7 +181,41 @@ end | |
|
||
## Getting Started - Retrieving a list of Customers | ||
|
||
The general approach is you first instantiate a `Service` object based on the entity you would like to retrieve. Lets retrieve a list of Customers: | ||
There are two approaches to retrieve results from Quickbooks. However, you're encouraged to use the first approach as we'll be removing the second approach in the long run. | ||
|
||
###### Approach 1 | ||
|
||
Make sure that you've followed the steps mentioned in [Getting Started & Initiating Authentication Flow with Intuit](#getting-started--initiating-authentication-flow-with-intuit) before you proceed to use Approach 1. | ||
|
||
Then you can simply call `Quickbooks::Model::Customer.all` to retrieve a list of all customers. However, there is a default pagination applied and only 20 records are fetched per request. If you want to skip pagination and just want to fetch all the records then you can pass `skip_pagination: true` argument to `Quickbooks::Model::Customer.all`: | ||
```ruby | ||
Quickbooks::Model::Customer.all #=> Returns array of first 20 customers | ||
Quickbooks::Model::Customer.all(nil, skip_pagination: true) #=> Returns array of all customers | ||
``` | ||
The first argument in the method is query and if you don't pass any query then the default query will be executed which is, in this case, `SELECT * FROM Customer` | ||
|
||
Quickbooks models behave exactly the same like ActiveRecord for querying purposes. That means that these models have other methods e.g: `where` `count` `find_by` `find` etc. Following are some examples: | ||
```ruby | ||
|
||
# where | ||
Quickbooks::Model::Customer.where('') #=> runs default query and returns first 20 customers | ||
Quickbooks::Model::Customer.where(id: 170) #=> runs a conditional query and returns first 20 customers having id equal to 170 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem like a great example because you shouldn't have multiple customers with the same id. |
||
Quickbooks::Model::Customer.where(id: 170, skip_pagination: true) #=> runs the conditional query and returns all customers having id equal to 170 without pagination | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, would be better to use a more realistic value to demonstrate the filtering. I'm not clear what "without pagination" means? The pagination will happen behind inside that method call right? |
||
|
||
# find_by | ||
Quickbooks::Model::Customer.find_by('') #=> runs default query and returns first customer | ||
Quickbooks::Model::Customer.find_by(id: 170) #=> runs a conditional query and returns first customer having id equal to 170 | ||
|
||
# find | ||
Quickbooks::Model::Customer.find('') #=> runs default query and returns first customer | ||
Quickbooks::Model::Customer.find(170) #=> runs a conditional query and returns first customer having id equal to 170 | ||
|
||
# count | ||
Quickbooks::Model::Customer.count #=> returns total number of customers in your quickbooks account | ||
``` | ||
|
||
###### Approach 2 | ||
The second approach is you first instantiate a `Service` object based on the entity you would like to retrieve. Lets retrieve a list of Customers: | ||
|
||
```ruby | ||
|
||
|
@@ -218,8 +258,21 @@ customers.query(query, :page => 2, :per_page => 25) | |
``` | ||
### Querying in Batches | ||
|
||
Often one needs to retrieve multiple pages of records of an Entity type | ||
and loop over them all. Fortunately there is the `query_in_batches` collection method: | ||
Often one needs to retrieve multiple pages of records of an Entity type and loop over them all. Again there are two approaches currently supported and first approach is encouraged to be used. | ||
|
||
###### Approach 1 | ||
|
||
Make sure that you've followed the steps mentioned in [Getting Started & Initiating Authentication Flow with Intuit](#getting-started--initiating-authentication-flow-with-intuit) before you proceed to use Approach 1. | ||
|
||
```ruby | ||
Quickbooks::Model::Customer.query_in_batches(query, per_page: 1000) do |batch| | ||
batch.each do |customer| | ||
# ... | ||
end | ||
end | ||
``` | ||
###### Approach 2 | ||
As of second approach, there is the `query_in_batches` collection method in each service: | ||
|
||
```ruby | ||
query = nil | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module Quickbooks | ||
class Configuration | ||
class << self | ||
attr_accessor :qb_oauth_consumer, :qb_token_scope, :qb_token, :qb_secret, :qb_realm_id | ||
end | ||
|
||
def self.set_tokens_and_realm_id(token, secret, realm_id, options = {}) | ||
options[:scope] ||= :thread | ||
if options[:scope] == :thread | ||
Thread.current[:token] = token | ||
Thread.current[:secret] = secret | ||
Thread.current[:realm_id] = realm_id | ||
self.qb_token_scope = :thread | ||
else | ||
self.qb_token = token | ||
self.qb_secret = secret | ||
self.qb_realm_id = realm_id | ||
self.qb_token_scope = :application | ||
end | ||
end | ||
|
||
def self.set_oauth_consumer(oauth_consumer) | ||
self.qb_oauth_consumer = oauth_consumer | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
module Quickbooks | ||
module Model | ||
module ActiveRecordScaffold | ||
|
||
def self.included(base) | ||
base.extend(ClassMethods) | ||
end | ||
|
||
def save(options = {}) | ||
if id.blank? | ||
self.class.send(:initialize_service).create(self, options) | ||
else | ||
self.class.send(:initialize_service).update(self, options) | ||
end | ||
end | ||
|
||
module ClassMethods | ||
def all(query = nil, options = {}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm not sure this quite matches up with active record: http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html#method-i-all |
||
initialize_service(options).query(query, options).entries | ||
end | ||
|
||
def first | ||
all.first | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this really be calling all? seems wasteful if you just want one record |
||
end | ||
|
||
def where(options = {}) | ||
if options.is_a?(String) | ||
all(build_string_query(options), {}) | ||
elsif options.present? | ||
all(build_hash_query(options.except(:skip_pagination)), options) | ||
else | ||
all(full_query, {}) | ||
end | ||
end | ||
|
||
def find_by(options = {}) | ||
where(options).first | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like this should do a limit 1 to avoid fetching and parsing records that aren't used. |
||
end | ||
|
||
def find(id) | ||
find_by(id: id) | ||
end | ||
|
||
def count | ||
initialize_service.query("SELECT COUNT(*) FROM #{model}").total_count | ||
end | ||
|
||
def query_in_batches(options = {}) | ||
initialize_service(options).query_in_batches(query, options).entries | ||
end | ||
|
||
private | ||
|
||
def initialize_service(options = {}) | ||
if Quickbooks::Configuration.qb_oauth_consumer.blank? | ||
raise "QB_OAUTH_CONSUMER not set. Please follow instructions at " + | ||
"https://github.com/ruckus/quickbooks-ruby#getting-started--initiating-authentication-flow-with-intuit" | ||
end | ||
service = eval("Quickbooks::Service::#{model}").new | ||
service.access_token = OAuth::AccessToken.new( | ||
Quickbooks::Configuration.qb_oauth_consumer, options[:token] || get_token, | ||
options[:secret] || get_secret | ||
) | ||
service.company_id = options[:realm_id] || get_realm_id | ||
service | ||
end | ||
|
||
def build_string_query(query = nil) | ||
unless query.downcase.starts_with?('select ') | ||
query = query.present? ? "#{conditional_query} #{query}" : full_query | ||
end | ||
query | ||
end | ||
|
||
def build_hash_query(options = {}) | ||
query_builder = Quickbooks::Util::QueryBuilder.new | ||
conditions = options.collect do |key, value| | ||
if value.is_a?(Array) | ||
query_builder.clause(key, 'IN', value) | ||
else | ||
query_builder.clause(key, '=', value) | ||
end | ||
end.join(' AND ') | ||
conditional_query + conditions | ||
end | ||
|
||
def conditional_query | ||
"#{full_query} WHERE " | ||
end | ||
|
||
def full_query | ||
"SELECT * FROM #{model}" | ||
end | ||
|
||
def model | ||
self.name.split('::').last | ||
end | ||
|
||
def get_token | ||
Quickbooks::Configuration.qb_token_scope == :thread ? Thread.current[:token] : Quickbooks::Configuration.qb_token | ||
end | ||
|
||
def get_secret | ||
Quickbooks::Configuration.qb_token_scope == :thread ? Thread.current[:secret] : Quickbooks::Configuration.qb_secret | ||
end | ||
|
||
def get_realm_id | ||
Quickbooks::Configuration.qb_token_scope == :thread ? Thread.current[:realm_id] : Quickbooks::Configuration.qb_realm_id | ||
end | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it should be a heading level 3, e.g.
###