netsuite 0.8.11 → 0.8.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql-analysis.yml +1 -1
- data/HISTORY.md +9 -0
- data/README.md +81 -20
- data/lib/netsuite/actions/delete.rb +1 -1
- data/lib/netsuite/actions/delete_list.rb +1 -1
- data/lib/netsuite/actions/get.rb +1 -1
- data/lib/netsuite/actions/get_deleted.rb +1 -1
- data/lib/netsuite/actions/get_list.rb +1 -1
- data/lib/netsuite/actions/initialize.rb +2 -2
- data/lib/netsuite/actions/search.rb +1 -1
- data/lib/netsuite/records/assembly_item.rb +1 -1
- data/lib/netsuite/records/cash_refund_item.rb +1 -1
- data/lib/netsuite/records/credit_memo.rb +1 -0
- data/lib/netsuite/records/description_item.rb +2 -2
- data/lib/netsuite/records/discount_item.rb +1 -1
- data/lib/netsuite/records/employee.rb +1 -1
- data/lib/netsuite/records/inventory_item.rb +1 -1
- data/lib/netsuite/records/invoice.rb +1 -0
- data/lib/netsuite/records/item_group.rb +2 -2
- data/lib/netsuite/records/kit_item.rb +1 -1
- data/lib/netsuite/records/lot_numbered_assembly_item.rb +1 -1
- data/lib/netsuite/records/lot_numbered_inventory_item.rb +1 -1
- data/lib/netsuite/records/non_inventory_purchase_item.rb +1 -1
- data/lib/netsuite/records/non_inventory_resale_item.rb +1 -1
- data/lib/netsuite/records/non_inventory_sale_item.rb +1 -1
- data/lib/netsuite/records/null_field_list.rb +15 -0
- data/lib/netsuite/records/other_charge_sale_item.rb +1 -1
- data/lib/netsuite/records/payment_item.rb +2 -2
- data/lib/netsuite/records/record_ref.rb +1 -1
- data/lib/netsuite/records/serialized_assembly_item.rb +1 -1
- data/lib/netsuite/records/serialized_inventory_item.rb +1 -1
- data/lib/netsuite/records/service_resale_item.rb +1 -1
- data/lib/netsuite/records/service_sale_item.rb +1 -1
- data/lib/netsuite/records/subtotal_item.rb +2 -3
- data/lib/netsuite/support/records.rb +15 -6
- data/lib/netsuite/support/sublist.rb +1 -1
- data/lib/netsuite/utilities/strings.rb +15 -0
- data/lib/netsuite/version.rb +1 -1
- data/lib/netsuite.rb +2 -1
- data/netsuite.gemspec +1 -1
- data/spec/netsuite/records/cash_refund_item_spec.rb +1 -1
- data/spec/netsuite/records/credit_memo_spec.rb +14 -0
- data/spec/netsuite/records/invoice_spec.rb +14 -0
- data/spec/netsuite/records/null_field_list_spec.rb +30 -0
- data/spec/netsuite/support/records_spec.rb +33 -7
- metadata +8 -5
- data/lib/netsuite/core_ext/string/lower_camelcase.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f61f6e044bec13cb11de64e320044febb52108b63214d28cb6446ae98e172f9
|
4
|
+
data.tar.gz: 93a84083918815d7407a1464a4462a448f35751a37d1972cc6ff35b518a17ff7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 914d9e8b9effdfc2815de7907404d89b9ddf71b03ab08a78f95c762de470a63f5c51c4af138bc326cad2757ce0e5e076cbd0695330794c324d1e224d1ab04b17
|
7
|
+
data.tar.gz: ba72989cd165db27b6420b4b09e93c2aec018bf27e10b516872068cebf20c2405dd17d4015f45beeb274f8d27bc1db7e8d0074795c03c8ae070a9e2ec08dd5b0
|
data/HISTORY.md
CHANGED
@@ -4,6 +4,15 @@
|
|
4
4
|
|
5
5
|
### Fixed
|
6
6
|
|
7
|
+
## 0.8.12
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
* Add NullFieldList record (to credit memos and invoices) (#529)
|
12
|
+
* Add `get_deleted` action to item records (#530)
|
13
|
+
* Add `get_deleted` action to Employee records (#531)
|
14
|
+
* Remove monkey patched `lower_camelcase` method on String (#533)
|
15
|
+
|
7
16
|
## 0.8.11
|
8
17
|
|
9
18
|
### Added
|
data/README.md
CHANGED
@@ -4,13 +4,13 @@
|
|
4
4
|
|
5
5
|
# NetSuite SuiteTalk API Ruby Gem
|
6
6
|
|
7
|
-
* This gem will act as a wrapper around the NetSuite SuiteTalk
|
7
|
+
* This gem will act as a wrapper around the NetSuite SuiteTalk Web Services API.
|
8
8
|
* The gem does not cover the entire API, only the subset contributors have used so far. Please submit a PR for any functionality that's missing!
|
9
9
|
* NetSuite is a complex system. There's a lot to learn and sparse resources available to learn from. Here's a list of [NetSuite Development Resources](https://github.com/NetSweet/netsuite/wiki/NetSuite-Development-Resources).
|
10
10
|
|
11
11
|
# Help & Support
|
12
12
|
|
13
|
-
Join the [
|
13
|
+
Join the [Slack channel](http://opensuite-slackin.herokuapp.com) for help with any NetSuite issues. Please do not post usage questions as issues in GitHub.
|
14
14
|
|
15
15
|
There is some additional helpful resources for NetSuite development [listed here](https://dashboard.suitesync.io/docs/resources#netsuite).
|
16
16
|
|
@@ -36,20 +36,20 @@ gem 'netsuite'
|
|
36
36
|
|
37
37
|
If you'd like more accurate time conversion support, include the `tzinfo` gem.
|
38
38
|
|
39
|
-
This gem is built for
|
39
|
+
This gem is built for Ruby 2.6.x+, but should work on older versions down to 1.9. There's a [1-8-stable](https://github.com/NetSweet/netsuite/tree/1-8-stable) branch for Ruby 1.8.x support.
|
40
40
|
|
41
41
|
## Configuration
|
42
42
|
|
43
|
-
The most important thing you'll need is your NetSuite account ID. Not sure how to find your account
|
43
|
+
The most important thing you'll need is your NetSuite account ID. Not sure how to find your account ID? [Here's a guide.](http://mikebian.co/find-netsuite-web-services-account-number/)
|
44
44
|
|
45
45
|
How you connect to NetSuite has changed a lot over the years and differs between API versions. For instance:
|
46
46
|
|
47
47
|
* Older API versions (~2015) allowed authentication via username and password
|
48
|
-
*
|
48
|
+
* Newer API versions (> 2016) still allowed for username and password authentication, but required an application ID
|
49
49
|
* "OAuth", which requires four separate keys to be manually generated, was supported sometime after 2015
|
50
50
|
* API versions greater than 2018_2 require `endpoint` to be set directly ([more info](https://github.com/NetSweet/netsuite/pull/473))
|
51
51
|
|
52
|
-
Here's an example connection configuration. You don't want to actually use username + password config;
|
52
|
+
Here's an example connection configuration. You don't want to actually use username + password config; Token Based Authentication is detailed [in a separate section](#token-based-authentication):
|
53
53
|
|
54
54
|
```ruby
|
55
55
|
NetSuite.configure do
|
@@ -60,13 +60,13 @@ NetSuite.configure do
|
|
60
60
|
api_version '2018_2'
|
61
61
|
|
62
62
|
# password-based login information
|
63
|
-
# in most cases you should use
|
63
|
+
# in most cases you should use Token Based Authentication instead
|
64
64
|
email 'email@example.com'
|
65
65
|
password 'password'
|
66
66
|
role 10
|
67
67
|
|
68
|
-
# recent API versions require
|
69
|
-
# use `NetSuite::Utilities.data_center_url('TSTDRV1576318')` to retrieve
|
68
|
+
# recent API versions require an account-specific endpoint to be set
|
69
|
+
# use `NetSuite::Utilities.data_center_url('TSTDRV1576318')` to retrieve WSDL URL
|
70
70
|
# you'll want to do this in a background process and strip the protocol out of the return string
|
71
71
|
wsdl_domain 'tstdrv1576318.suitetalk.api.netsuite.com'
|
72
72
|
|
@@ -87,17 +87,17 @@ NetSuite.configure do
|
|
87
87
|
|
88
88
|
api_version '2018_2'
|
89
89
|
|
90
|
-
# optionally specify full
|
90
|
+
# optionally specify full WSDL URL (to switch to sandbox, for example)
|
91
91
|
wsdl "https://webservices.sandbox.netsuite.com/wsdl/v#{api_version}_0/netsuite.wsdl"
|
92
92
|
|
93
|
-
# if your datacenter is being switched, you'll have to manually set your
|
93
|
+
# if your datacenter is being switched, you'll have to manually set your WSDL location
|
94
94
|
wsdl "https://webservices.na2.netsuite.com/wsdl/v#{api_version}_0/netsuite.wsdl"
|
95
95
|
|
96
96
|
# or specify the wsdl_domain if you want to specify the datacenter and let the configuration
|
97
97
|
# construct the full wsdl location - e.g. "https://#{wsdl_domain}/wsdl/v#{api_version}_0/netsuite.wsdl"
|
98
98
|
wsdl_domain "webservices.na2.netsuite.com"
|
99
99
|
|
100
|
-
# often the
|
100
|
+
# often the NetSuite servers will hang which would cause a timeout exception to be raised
|
101
101
|
# if you don't mind waiting (e.g. processing NS via a background worker), increasing the timeout should fix the issue
|
102
102
|
read_timeout 100_000
|
103
103
|
|
@@ -111,7 +111,7 @@ NetSuite.configure do
|
|
111
111
|
# log_level :debug
|
112
112
|
|
113
113
|
# password-based login information
|
114
|
-
# in most cases you should use
|
114
|
+
# in most cases you should use Token Based Authentication instead
|
115
115
|
email 'email@domain.com'
|
116
116
|
password 'password'
|
117
117
|
account '12345'
|
@@ -124,7 +124,7 @@ NetSuite.configure do
|
|
124
124
|
end
|
125
125
|
```
|
126
126
|
|
127
|
-
If are using username + password authentication (which you shouldn't be!) *and* you'd like to use
|
127
|
+
If you are using username + password authentication (which you shouldn't be!) *and* you'd like to use an API endpoint greater than 2015_1, you'll need to specify an application ID:
|
128
128
|
|
129
129
|
```ruby
|
130
130
|
NetSuite::Configuration.soap_header = {
|
@@ -134,9 +134,9 @@ NetSuite::Configuration.soap_header = {
|
|
134
134
|
}
|
135
135
|
```
|
136
136
|
|
137
|
-
### Token
|
137
|
+
### Token Based Authentication
|
138
138
|
|
139
|
-
OAuth credentials are supported and the recommended authentication approach. [Learn more about how to set up
|
139
|
+
OAuth credentials are supported and the recommended authentication approach. [Learn more about how to set up Token Based Authentication here](http://mikebian.co/using-netsuites-token-based-authentication-with-suitetalk/).
|
140
140
|
|
141
141
|
```ruby
|
142
142
|
NetSuite.configure do
|
@@ -152,7 +152,7 @@ NetSuite.configure do
|
|
152
152
|
# oauth does not work with API versions less than 2015_2
|
153
153
|
api_version '2016_2'
|
154
154
|
|
155
|
-
# the endpoint indicated in the > 2018_2
|
155
|
+
# the endpoint indicated in the > 2018_2 WSDL is invalid
|
156
156
|
# you must set the endpoint directly
|
157
157
|
# https://github.com/NetSweet/netsuite/pull/473
|
158
158
|
endpoint "https://#{wsdl_domain}/services/NetSuitePort_#{api_version}"
|
@@ -284,6 +284,20 @@ NetSuite::Records::BaseRefList.get_select_value(
|
|
284
284
|
)
|
285
285
|
```
|
286
286
|
|
287
|
+
## Null Fields
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
# updating a field on a record to be null
|
291
|
+
invoice = NetSuite::Records::Invoice.get(12345)
|
292
|
+
invoice.update(null_field_list: 'shipMethod')
|
293
|
+
|
294
|
+
# updating multiple fields on a record to be null
|
295
|
+
invoice.update(null_field_list: ['shipAddressList', 'shipMethod'])
|
296
|
+
|
297
|
+
# updating a custom fields on a record to be null, using custom field ID
|
298
|
+
invoice.update(null_field_list: 'custBody9')
|
299
|
+
```
|
300
|
+
|
287
301
|
## Searching
|
288
302
|
|
289
303
|
```ruby
|
@@ -300,7 +314,7 @@ search = NetSuite::Records::Customer.search({
|
|
300
314
|
|
301
315
|
`open https://system.netsuite.com/app/common/entity/custjob.nl?id=#{search.results.first.internal_id}`
|
302
316
|
|
303
|
-
# find the
|
317
|
+
# find the Avalara tax item. Some records don't support search.
|
304
318
|
all_sales_taxes = NetSuite::Utilities.backoff { NetSuite::Records::SalesTaxItem.get_all }
|
305
319
|
ns_tax_code = all_sales_taxes.detect { |st| st.item_id == 'AVATAX' }
|
306
320
|
|
@@ -411,13 +425,13 @@ NetSuite::Records::SalesOrder.search({
|
|
411
425
|
]
|
412
426
|
},
|
413
427
|
|
414
|
-
# the column syntax is a WIP. This will change in the future
|
428
|
+
# the column syntax is a WIP. This will change in the future.
|
415
429
|
columns: {
|
416
430
|
'tranSales:basic' => [
|
417
431
|
'platformCommon:internalId/' => {},
|
418
432
|
'platformCommon:email/' => {},
|
419
433
|
'platformCommon:tranDate/' => {},
|
420
|
-
# If you include columns that are only part of the *SearchRowBasic (ie. TransactionSearchRowBasic),
|
434
|
+
# If you include columns that are only part of the *SearchRowBasic (ie. TransactionSearchRowBasic),
|
421
435
|
# they'll be readable on the resulting record just like regular fields (my_record.close_date).
|
422
436
|
'platformCommon:closeDate/' => {}
|
423
437
|
],
|
@@ -593,6 +607,53 @@ deposit.payment = 20
|
|
593
607
|
deposit.add
|
594
608
|
```
|
595
609
|
|
610
|
+
## Getting Deleted Records
|
611
|
+
|
612
|
+
```ruby
|
613
|
+
response = NetSuite::Records::LotNumberedInventoryItem.get_deleted({
|
614
|
+
criteria: [
|
615
|
+
{
|
616
|
+
# If you don't specify a type criteria, you'll get all deleted records,
|
617
|
+
# regardless of the type of record you called this on.
|
618
|
+
field: 'type',
|
619
|
+
operator: 'anyOf',
|
620
|
+
value: 'lotNumberedInventoryItem',
|
621
|
+
}
|
622
|
+
],
|
623
|
+
})
|
624
|
+
|
625
|
+
Array(response.body.fetch(:deleted_record_list)).first
|
626
|
+
# => {
|
627
|
+
# :deleted_date => Wed, 16 Feb 2022 17:43:45 -0800,
|
628
|
+
# :record => {
|
629
|
+
# :name => "My Item",
|
630
|
+
# :@internal_id => "12485",
|
631
|
+
# :@type => "lotNumberedInventoryItem",
|
632
|
+
# :"@xsi:type" => "platformCore:RecordRef"
|
633
|
+
# }
|
634
|
+
# }
|
635
|
+
|
636
|
+
# deleted_record_list could be:
|
637
|
+
# nil - No records matching criteria were deleted
|
638
|
+
# Hash - A single record matching criteria was deleted
|
639
|
+
# Array - Multiple records matching criteria were deleted
|
640
|
+
|
641
|
+
# Simple pagination
|
642
|
+
page = 1
|
643
|
+
begin
|
644
|
+
response = NetSuite::Records::LotNumberedInventoryItem.get_deleted({
|
645
|
+
criteria: [
|
646
|
+
# your criteria
|
647
|
+
],
|
648
|
+
page: page,
|
649
|
+
})
|
650
|
+
|
651
|
+
# Do your thing with response.body.fetch(:deleted_record_list)
|
652
|
+
|
653
|
+
page += 1
|
654
|
+
end until page > Integer(response.fetch(:total_pages))
|
655
|
+
```
|
656
|
+
|
596
657
|
## Non-standard Operations
|
597
658
|
|
598
659
|
```ruby
|
data/lib/netsuite/actions/get.rb
CHANGED
@@ -28,12 +28,12 @@ module NetSuite
|
|
28
28
|
def request_body
|
29
29
|
{
|
30
30
|
'platformMsgs:initializeRecord' => {
|
31
|
-
'platformCore:type' => @klass
|
31
|
+
'platformCore:type' => NetSuite::Support::Records.netsuite_type(@klass),
|
32
32
|
'platformCore:reference' => {},
|
33
33
|
:attributes! => {
|
34
34
|
'platformCore:reference' => {
|
35
35
|
'internalId' => @object.internal_id,
|
36
|
-
:type => @object
|
36
|
+
:type => NetSuite::Support::Records.netsuite_type(@object)
|
37
37
|
}
|
38
38
|
}
|
39
39
|
}
|
@@ -26,7 +26,7 @@ module NetSuite
|
|
26
26
|
.update(NetSuite::Configuration.soap_header)
|
27
27
|
.merge(
|
28
28
|
(@options.delete(:preferences) || {}).inject({'platformMsgs:SearchPreferences' => {}}) do |h, (k, v)|
|
29
|
-
h['platformMsgs:SearchPreferences'][k.to_s
|
29
|
+
h['platformMsgs:SearchPreferences'][NetSuite::Utilities::Strings.lower_camelcase(k.to_s)] = v
|
30
30
|
h
|
31
31
|
end
|
32
32
|
)
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :get_select_value, :add, :delete, :update, :upsert, :upsert_list, :search
|
10
|
+
actions :get, :get_deleted, :get_list, :get_select_value, :add, :delete, :update, :upsert, :upsert_list, :search
|
11
11
|
|
12
12
|
fields :auto_lead_time, :auto_preferred_stock_level, :auto_reorder_point, :available_to_partners, :average_cost, :build_entire_assembly,
|
13
13
|
:copy_description, :cost, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :cost_units, :costing_method,
|
@@ -6,7 +6,7 @@ module NetSuite
|
|
6
6
|
include Support::Records
|
7
7
|
include Namespaces::TranCust
|
8
8
|
|
9
|
-
fields :amount, :rate, :quantity, :is_taxable, :order_line, :line, :description
|
9
|
+
fields :amount, :gross_amt, :rate, :quantity, :is_taxable, :order_line, :line, :description
|
10
10
|
field :custom_field_list, CustomFieldList
|
11
11
|
|
12
12
|
record_refs :item, :klass, :price
|
@@ -23,6 +23,7 @@ module NetSuite
|
|
23
23
|
field :item_list, CreditMemoItemList
|
24
24
|
field :apply_list, CreditMemoApplyList
|
25
25
|
field :ship_group_list, SalesOrderShipGroupList
|
26
|
+
field :null_field_list, NullFieldList
|
26
27
|
|
27
28
|
# field :bill_address_list,
|
28
29
|
field :transaction_bill_address, BillAddress
|
@@ -7,12 +7,12 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert
|
11
11
|
|
12
12
|
fields :available_to_partners, :created_date, :description, :display_name, :include_children, :is_inactive, :item_id, :last_modified_date
|
13
13
|
|
14
14
|
record_refs :custom_form, :department, :issue_product, :klass, :location
|
15
|
-
|
15
|
+
|
16
16
|
field :custom_field_list, CustomFieldList
|
17
17
|
field :subsidiary_list, RecordRefList
|
18
18
|
field :translations_list, TranslationList
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :update, :delete, :search, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :update, :delete, :search, :upsert
|
11
11
|
|
12
12
|
fields :available_to_partners, :created_date, :description, :display_name, :include_children, :is_inactive, :is_pre_tax,
|
13
13
|
:item_id, :last_modified_date, :non_posting, :rate, :upc_code, :vendor_name
|
@@ -9,7 +9,7 @@ module NetSuite
|
|
9
9
|
|
10
10
|
# https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2014_1/script/record/employee.html
|
11
11
|
|
12
|
-
actions :get, :get_list, :add, :update, :upsert, :upsert_list, :delete, :search
|
12
|
+
actions :get, :get_deleted, :get_list, :add, :update, :upsert, :upsert_list, :delete, :search
|
13
13
|
|
14
14
|
fields :alt_name, :phone, :first_name, :last_name, :is_inactive, :email, :give_access, :send_email, :is_support_rep,
|
15
15
|
:birth_date, :hire_date, :last_review_date, :next_review_date, :title, :home_phone, :office_phone,
|
@@ -20,7 +20,7 @@ module NetSuite
|
|
20
20
|
# }
|
21
21
|
# ]
|
22
22
|
#
|
23
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert, :update_list
|
23
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert, :update_list
|
24
24
|
|
25
25
|
fields :auto_lead_time,
|
26
26
|
:auto_preferred_stock_level,
|
@@ -38,6 +38,7 @@ module NetSuite
|
|
38
38
|
field :custom_field_list, CustomFieldList
|
39
39
|
field :shipping_address, Address
|
40
40
|
field :billing_address, Address
|
41
|
+
field :null_field_list, NullFieldList
|
41
42
|
|
42
43
|
read_only_fields :sub_total, :discount_total, :total, :recognized_revenue, :amount_remaining, :amount_paid,
|
43
44
|
:alt_shipping_cost, :gift_cert_applied, :handling_cost, :alt_handling_cost
|
@@ -7,13 +7,13 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert
|
11
11
|
|
12
12
|
fields :available_to_partners, :created_date, :description, :display_name, :include_children, :include_start_end_lines,
|
13
13
|
:is_inactive, :is_vsoe_bundle, :item_id, :last_modified_date, :print_items, :upc_code, :vendor_name
|
14
14
|
|
15
15
|
record_refs :custom_form, :default_item_ship_method, :department, :issue_product, :item_ship_method_list, :klass, :location, :parent
|
16
|
-
|
16
|
+
|
17
17
|
field :custom_field_list, CustomFieldList
|
18
18
|
# TODO field :item_carrier, ShippingCarrier
|
19
19
|
field :member_list, ItemMemberList
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert
|
11
11
|
|
12
12
|
fields :available_to_partners, :cost_estimate, :created_date, :defer_rev_rec, :description, :display_name,
|
13
13
|
:dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, :featured_description, :handling_cost,
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :get_select_value, :add, :delete, :update, :upsert, :upsert_list, :search
|
10
|
+
actions :get, :get_deleted, :get_list, :get_select_value, :add, :delete, :update, :upsert, :upsert_list, :search
|
11
11
|
|
12
12
|
fields :auto_lead_time, :auto_preferred_stock_level, :auto_reorder_point, :available_to_partners, :average_cost, :build_entire_assembly,
|
13
13
|
:copy_description, :cost, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :cost_units, :costing_method,
|
@@ -27,7 +27,7 @@ module NetSuite
|
|
27
27
|
# TODO: field :site_category_list, SiteCategoryList
|
28
28
|
field :translations_list, TranslationList
|
29
29
|
|
30
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert, :update_list
|
30
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert, :update_list
|
31
31
|
|
32
32
|
record_refs :alternate_demand_source_item,
|
33
33
|
:asset_account,
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :upsert
|
11
11
|
|
12
12
|
fields :available_to_partners, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :country_of_manufacture,
|
13
13
|
:created_date, :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap,
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :upsert, :update
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :upsert, :update
|
11
11
|
|
12
12
|
fields :amortization_period,
|
13
13
|
:available_to_partners,
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert
|
11
11
|
|
12
12
|
fields :available_to_partners,
|
13
13
|
:contingent_revenue_handling,
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module NetSuite
|
2
|
+
module Records
|
3
|
+
class NullFieldList
|
4
|
+
include Support::Fields
|
5
|
+
include Support::Records
|
6
|
+
include Namespaces::PlatformCore
|
7
|
+
|
8
|
+
fields :name
|
9
|
+
|
10
|
+
def initialize(attributes = {})
|
11
|
+
initialize_from_attributes_hash(attributes)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :update, :delete, :upsert, :search
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :update, :delete, :upsert, :search
|
11
11
|
|
12
12
|
attr_reader :internal_id
|
13
13
|
attr_accessor :external_id
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert
|
11
11
|
|
12
12
|
fields :available_to_partners, :created_date, :description, :display_name, :include_children, :is_inactive, :item_id, :last_modified_date, :undep_funds
|
13
13
|
|
@@ -15,7 +15,7 @@ module NetSuite
|
|
15
15
|
|
16
16
|
field :custom_field_list, CustomFieldList
|
17
17
|
field :subsidiary_list, RecordRefList
|
18
|
-
|
18
|
+
|
19
19
|
# TODO custom records need to be implemented
|
20
20
|
field :translations_list, TranslationList
|
21
21
|
|
@@ -21,7 +21,7 @@ module NetSuite
|
|
21
21
|
record = attributes_or_record
|
22
22
|
@internal_id = record.internal_id if record.respond_to?(:internal_id)
|
23
23
|
@external_id = record.external_id if record.respond_to?(:external_id)
|
24
|
-
@type =
|
24
|
+
@type = NetSuite::Support::Records.netsuite_type(record)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert
|
11
11
|
|
12
12
|
fields :alternate_demand_source_item,
|
13
13
|
:asset_account,
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert
|
11
11
|
|
12
12
|
record_refs :soft_descriptor,
|
13
13
|
:stock_unit,
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :update, :delete, :upsert, :search
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :update, :delete, :upsert, :search
|
11
11
|
|
12
12
|
fields :amortization_period,
|
13
13
|
:available_to_partners,
|
@@ -7,7 +7,7 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :update, :delete, :upsert, :search
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :update, :delete, :upsert, :search
|
11
11
|
|
12
12
|
fields :available_to_partners, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :create_job, :created_date,
|
13
13
|
:display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, :featured_description,
|
@@ -7,12 +7,12 @@ module NetSuite
|
|
7
7
|
include Support::Actions
|
8
8
|
include Namespaces::ListAcct
|
9
9
|
|
10
|
-
actions :get, :get_list, :add, :delete, :search, :update, :upsert
|
10
|
+
actions :get, :get_deleted, :get_list, :add, :delete, :search, :update, :upsert
|
11
11
|
|
12
12
|
fields :available_to_partners, :created_date, :description, :include_children, :is_inactive, :item_id, :last_modified_date
|
13
13
|
|
14
14
|
record_refs :custom_form, :department, :issue_product, :klass, :location
|
15
|
-
|
15
|
+
|
16
16
|
field :custom_field_list, CustomFieldList
|
17
17
|
field :subsidiary_list, RecordRefList
|
18
18
|
field :translations_list, TranslationList
|
@@ -32,4 +32,3 @@ module NetSuite
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
|
-
|
@@ -6,8 +6,8 @@ module NetSuite
|
|
6
6
|
|
7
7
|
def to_record
|
8
8
|
attributes.reject { |k,v| self.class.read_only_fields.include?(k) || self.class.search_only_fields.include?(k) }.inject({}) do |hash, (k,v)|
|
9
|
-
kname = "#{record_namespace}:"
|
10
|
-
kname += k == :klass ? 'class' : k.to_s
|
9
|
+
kname = "#{v.is_a?(NetSuite::Records::NullFieldList) ? v.record_namespace : record_namespace}:"
|
10
|
+
kname += k == :klass ? 'class' : NetSuite::Utilities::Strings.lower_camelcase(k.to_s)
|
11
11
|
|
12
12
|
to_attributes!(hash, kname, v)
|
13
13
|
|
@@ -38,13 +38,13 @@ module NetSuite
|
|
38
38
|
if v.kind_of?(NetSuite::Records::RecordRef) && v.type
|
39
39
|
hash[:attributes!] ||= {}
|
40
40
|
hash[:attributes!][kname] ||= {}
|
41
|
-
hash[:attributes!][kname]['type'] = v.type
|
41
|
+
hash[:attributes!][kname]['type'] = NetSuite::Utilities::Strings.lower_camelcase(v.type)
|
42
42
|
end
|
43
43
|
|
44
44
|
if v.kind_of?(NetSuite::Records::CustomRecordRef) && v.type_id
|
45
45
|
hash[:attributes!] ||= {}
|
46
46
|
hash[:attributes!][kname] ||= {}
|
47
|
-
hash[:attributes!][kname]['typeId'] = v.type_id
|
47
|
+
hash[:attributes!][kname]['typeId'] = NetSuite::Utilities::Strings.lower_camelcase(v.type_id)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -53,11 +53,11 @@ module NetSuite
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def netsuite_type
|
56
|
-
|
56
|
+
Records.netsuite_type(self)
|
57
57
|
end
|
58
58
|
|
59
59
|
def record_type_without_namespace
|
60
|
-
|
60
|
+
Records.record_type_without_namespace(self)
|
61
61
|
end
|
62
62
|
|
63
63
|
def refresh(credentials = {})
|
@@ -75,6 +75,15 @@ module NetSuite
|
|
75
75
|
self
|
76
76
|
end
|
77
77
|
|
78
|
+
def self.netsuite_type(obj)
|
79
|
+
NetSuite::Utilities::Strings.lower_camelcase(record_type_without_namespace(obj))
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.record_type_without_namespace(obj)
|
83
|
+
klass = obj.is_a?(Class) ? obj : obj.class
|
84
|
+
"#{klass.to_s.split('::').last}"
|
85
|
+
end
|
86
|
+
|
78
87
|
end
|
79
88
|
end
|
80
89
|
end
|
@@ -35,7 +35,7 @@ module NetSuite
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def to_record
|
38
|
-
rec = { "#{record_namespace}:#{sublist_key.to_s
|
38
|
+
rec = { "#{record_namespace}:#{NetSuite::Utilities::Strings.lower_camelcase(sublist_key.to_s)}" => send(self.sublist_key).map(&:to_record) }
|
39
39
|
|
40
40
|
if !replace_all.nil?
|
41
41
|
rec["#{record_namespace}:replaceAll"] = !!replace_all
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module NetSuite
|
2
|
+
module Utilities
|
3
|
+
module Strings
|
4
|
+
class << self
|
5
|
+
def lower_camelcase(obj)
|
6
|
+
str = obj.is_a?(String) ? obj.dup : obj.to_s
|
7
|
+
str.gsub!(/\/(.?)/) { "::#{$1.upcase}" }
|
8
|
+
str.gsub!(/(?:_+|-+)([a-z]|[0-9])/) { $1.upcase }
|
9
|
+
str.gsub!(/(\A|\s)([A-Z])/) { $1 + $2.downcase }
|
10
|
+
str
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/netsuite/version.rb
CHANGED
data/lib/netsuite.rb
CHANGED
@@ -5,9 +5,9 @@ require 'netsuite/version'
|
|
5
5
|
require 'netsuite/errors'
|
6
6
|
require 'netsuite/utilities'
|
7
7
|
require 'netsuite/utilities/data_center'
|
8
|
+
require 'netsuite/utilities/strings'
|
8
9
|
require 'netsuite/rest/utilities/roles'
|
9
10
|
require 'netsuite/rest/utilities/request'
|
10
|
-
require 'netsuite/core_ext/string/lower_camelcase'
|
11
11
|
|
12
12
|
module NetSuite
|
13
13
|
autoload :Configuration, 'netsuite/configuration'
|
@@ -225,6 +225,7 @@ module NetSuite
|
|
225
225
|
autoload :NonInventoryResaleItem, 'netsuite/records/non_inventory_resale_item'
|
226
226
|
autoload :Note, 'netsuite/records/note'
|
227
227
|
autoload :NoteType, 'netsuite/records/note_type'
|
228
|
+
autoload :NullFieldList, 'netsuite/records/null_field_list'
|
228
229
|
autoload :Opportunity, 'netsuite/records/opportunity'
|
229
230
|
autoload :OpportunityItem, 'netsuite/records/opportunity_item'
|
230
231
|
autoload :OpportunityItemList, 'netsuite/records/opportunity_item_list'
|
data/netsuite.gemspec
CHANGED
@@ -20,6 +20,20 @@ describe NetSuite::Records::CreditMemo do
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
it 'has all the right fields with specific classes' do
|
24
|
+
{
|
25
|
+
custom_field_list: NetSuite::Records::CustomFieldList,
|
26
|
+
item_list: NetSuite::Records::CreditMemoItemList,
|
27
|
+
apply_list: NetSuite::Records::CreditMemoApplyList,
|
28
|
+
ship_group_list: NetSuite::Records::SalesOrderShipGroupList,
|
29
|
+
null_field_list: NetSuite::Records::NullFieldList,
|
30
|
+
transaction_bill_address: NetSuite::Records::BillAddress,
|
31
|
+
billing_address: NetSuite::Records::Address,
|
32
|
+
}.each do |field, klass|
|
33
|
+
expect(memo).to have_field(field, klass)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
23
37
|
it 'has all the right record refs' do
|
24
38
|
[
|
25
39
|
:account, :bill_address_list, :created_from, :custom_form, :department, :discount_item, :entity, :gift_cert,
|
@@ -32,6 +32,20 @@ describe NetSuite::Records::Invoice do
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
it 'has all the right fields with specific classes' do
|
36
|
+
{
|
37
|
+
transaction_bill_address: NetSuite::Records::BillAddress,
|
38
|
+
transaction_ship_address: NetSuite::Records::ShipAddress,
|
39
|
+
item_list: NetSuite::Records::InvoiceItemList,
|
40
|
+
custom_field_list: NetSuite::Records::CustomFieldList,
|
41
|
+
shipping_address: NetSuite::Records::Address,
|
42
|
+
billing_address: NetSuite::Records::Address,
|
43
|
+
null_field_list: NetSuite::Records::NullFieldList,
|
44
|
+
}.each do |field, klass|
|
45
|
+
expect(invoice).to have_field(field, klass)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
35
49
|
it 'has all the right read_only_fields' do
|
36
50
|
[
|
37
51
|
:sub_total, :discount_total, :total, :alt_handling_cost, :alt_shipping_cost, :gift_cert_applied,
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NetSuite::Records::RecordRef do
|
4
|
+
let(:null_field_list) { NetSuite::Records::NullFieldList.new }
|
5
|
+
|
6
|
+
describe '#to_record' do
|
7
|
+
it 'can represent itself as a SOAP record for single value' do
|
8
|
+
null_field_list.name = 'blah'
|
9
|
+
record = {
|
10
|
+
'platformCore:name' => 'blah'
|
11
|
+
}
|
12
|
+
expect(null_field_list.to_record).to eql(record)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can represent itself as a SOAP record for multiple values' do
|
16
|
+
null_field_list.name = ['blah', 'foo']
|
17
|
+
record = {
|
18
|
+
'platformCore:name' => ['blah', 'foo']
|
19
|
+
}
|
20
|
+
expect(null_field_list.to_record).to eql(record)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#record_type' do
|
25
|
+
it 'returns a string of the SOAP type' do
|
26
|
+
expect(null_field_list.record_type).to eql('platformCore:NullFieldList')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -4,11 +4,14 @@ module Foo
|
|
4
4
|
module Bar
|
5
5
|
class Baz
|
6
6
|
include NetSuite::Support::Fields
|
7
|
+
include NetSuite::Support::RecordRefs
|
7
8
|
include NetSuite::Support::Records
|
9
|
+
include NetSuite::Namespaces::TranSales
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
fields :source, :total
|
12
|
+
field :null_field_list, NetSuite::Records::NullFieldList
|
13
|
+
|
14
|
+
record_ref :related_record
|
12
15
|
end
|
13
16
|
end
|
14
17
|
end
|
@@ -16,18 +19,41 @@ end
|
|
16
19
|
describe NetSuite::Support::Records do
|
17
20
|
let(:instance) { Foo::Bar::Baz.new }
|
18
21
|
|
19
|
-
describe '#
|
22
|
+
describe '#to_record' do
|
20
23
|
it 'returns a hash of attributes to be used in a SOAP request' do
|
24
|
+
instance.source = 'Google'
|
25
|
+
instance.total = 100.0
|
26
|
+
|
21
27
|
expect(instance.to_record).to eql({
|
22
|
-
'
|
23
|
-
'
|
28
|
+
'tranSales:source' => 'Google',
|
29
|
+
'tranSales:total' => 100.0
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'uses the records namespace for the outer elements namespace and the field values namespace for the inner elements namespace' do
|
34
|
+
instance.related_record = NetSuite::Records::RecordRef.new(name: 'blah')
|
35
|
+
|
36
|
+
expect(instance.to_record).to eq({
|
37
|
+
'tranSales:relatedRecord' => {
|
38
|
+
'platformCore:name' => 'blah',
|
39
|
+
},
|
40
|
+
})
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'uses the fields namespace for the outer elements namespace for NullFieldList value' do
|
44
|
+
instance.null_field_list.name = 'source'
|
45
|
+
|
46
|
+
expect(instance.to_record).to eq({
|
47
|
+
'platformCore:nullFieldList' => {
|
48
|
+
'platformCore:name' => 'source',
|
49
|
+
},
|
24
50
|
})
|
25
51
|
end
|
26
52
|
end
|
27
53
|
|
28
54
|
describe '#record_type' do
|
29
55
|
it 'returns a string of the record type to be used in a SOAP request' do
|
30
|
-
expect(instance.record_type).to eql('
|
56
|
+
expect(instance.record_type).to eql('tranSales:Baz')
|
31
57
|
end
|
32
58
|
end
|
33
59
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: netsuite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Moran
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-04-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: savon
|
@@ -45,14 +45,14 @@ dependencies:
|
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version:
|
48
|
+
version: 12.3.3
|
49
49
|
type: :development
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
55
|
+
version: 12.3.3
|
56
56
|
description: NetSuite SuiteTalk API Wrapper
|
57
57
|
email:
|
58
58
|
- ryan.moran@gmail.com
|
@@ -92,7 +92,6 @@ files:
|
|
92
92
|
- lib/netsuite/actions/upsert.rb
|
93
93
|
- lib/netsuite/actions/upsert_list.rb
|
94
94
|
- lib/netsuite/configuration.rb
|
95
|
-
- lib/netsuite/core_ext/string/lower_camelcase.rb
|
96
95
|
- lib/netsuite/errors.rb
|
97
96
|
- lib/netsuite/namespaces/act_sched.rb
|
98
97
|
- lib/netsuite/namespaces/comm_general.rb
|
@@ -272,6 +271,7 @@ files:
|
|
272
271
|
- lib/netsuite/records/non_inventory_sale_item.rb
|
273
272
|
- lib/netsuite/records/note.rb
|
274
273
|
- lib/netsuite/records/note_type.rb
|
274
|
+
- lib/netsuite/records/null_field_list.rb
|
275
275
|
- lib/netsuite/records/opportunity.rb
|
276
276
|
- lib/netsuite/records/opportunity_item.rb
|
277
277
|
- lib/netsuite/records/opportunity_item_list.rb
|
@@ -376,6 +376,7 @@ files:
|
|
376
376
|
- lib/netsuite/support/sublist.rb
|
377
377
|
- lib/netsuite/utilities.rb
|
378
378
|
- lib/netsuite/utilities/data_center.rb
|
379
|
+
- lib/netsuite/utilities/strings.rb
|
379
380
|
- lib/netsuite/version.rb
|
380
381
|
- netsuite.gemspec
|
381
382
|
- spec/netsuite/actions/add_spec.rb
|
@@ -486,6 +487,7 @@ files:
|
|
486
487
|
- spec/netsuite/records/non_inventory_sale_item_spec.rb
|
487
488
|
- spec/netsuite/records/note_spec.rb
|
488
489
|
- spec/netsuite/records/note_type_spec.rb
|
490
|
+
- spec/netsuite/records/null_field_list_spec.rb
|
489
491
|
- spec/netsuite/records/partner_spec.rb
|
490
492
|
- spec/netsuite/records/payment_item_spec.rb
|
491
493
|
- spec/netsuite/records/payment_method_spec.rb
|
@@ -723,6 +725,7 @@ test_files:
|
|
723
725
|
- spec/netsuite/records/non_inventory_sale_item_spec.rb
|
724
726
|
- spec/netsuite/records/note_spec.rb
|
725
727
|
- spec/netsuite/records/note_type_spec.rb
|
728
|
+
- spec/netsuite/records/null_field_list_spec.rb
|
726
729
|
- spec/netsuite/records/partner_spec.rb
|
727
730
|
- spec/netsuite/records/payment_item_spec.rb
|
728
731
|
- spec/netsuite/records/payment_method_spec.rb
|