parse-stack 1.3.8 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 18eab2bb5c64686e9b7713d4083db00fd5b7d026
4
- data.tar.gz: 21cc6c542518c284edcd90c8ff8c09df4883dd30
3
+ metadata.gz: ba96b9a68cd8bee15dfcd3f63b00fe7b3ce66a79
4
+ data.tar.gz: 627c40c21e1866fd1473171cc5794f48becd2d0e
5
5
  SHA512:
6
- metadata.gz: 38a5decb6fbbb89caa454ac583ea9cfbad0929a59bbfa78b86befc00bb53a861f040951ce9bb6013a6b03bb0fb086bd98438c00927da2030a66cb57798159858
7
- data.tar.gz: 8c16ed21c5c450270382d8223d010978de8950cbd9cfaf3172d2915f1e81d73a151b55176b61e41f1fbd662fa6a8c1554e620650c9a92aec84cdb26446fd26b3
6
+ metadata.gz: ad583e23a9af975fef0dd3e3a5badfe4a494a1754778d7f14c48b0aecd26863cd915994a1609a07824bfadb39ddb298dd69d83cb6ec23a1089d81e3c2be782c7
7
+ data.tar.gz: 59cc21ab26200badfd575729a9ee47b239374b948b6715fdc36418a8077edf4f86d035904e84c675996a1d0cb21b9ea0832364f2b64ef819d32471129b44a944
data/Changes.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Parse-Stack Changes
2
2
 
3
+ 1.4.2
4
+ -----------
5
+ - NEW: Support for rails generators: `parse_stack:install` and `parse_stack:model`.
6
+ - Support Parse::Date with ActiveSupport::TimeWithZone.
7
+ - :date properties will now raise an error if value was not converted to a Parse::Date.
8
+ - Support for calling `before_save` and `before_destroy` callbacks in your model when a Parse::Object is returned by your `before_save` or `before_delete` webhook respectively.
9
+ - Parse::Query `:cache` expression now allows integer values to define the specific cache duration for this specific query request. If `false` is passed, will ignore the cache and make the request regardless if a cache response is available. If `true` is passed (default), it will use the value configured when setting up when calling `Parse.setup`.
10
+ - Fixes the use of `:use_master_key` in Parse::Query.
11
+ - Fixes to the cache key used in middleware.
12
+ - Parse::User before_save callback clears the record ACLs.
13
+ - Added `anonymous?` instance method to `Parse::User` class.
14
+
3
15
  1.3.8
4
16
  -----------
5
17
  - Support for reloading the Parse config data with `Parse.config!`.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- parse-stack (1.3.8)
4
+ parse-stack (1.4.3)
5
5
  active_model_serializers (>= 0.9, < 1)
6
6
  activemodel (>= 4.2.1, < 6)
7
7
  activesupport (>= 4.2.1, < 6)
data/README.md CHANGED
@@ -5,6 +5,35 @@ Parse-Stack is a [Parse Server](https://github.com/ParsePlatform/parse-server) R
5
5
  [![Gem Version](https://badge.fury.io/rb/parse-stack.svg)](https://badge.fury.io/rb/parse-stack)
6
6
  [![Build Status](https://travis-ci.org/modernistik/parse-stack.svg?branch=master)](https://travis-ci.org/modernistik/parse-stack)
7
7
 
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'parse-stack'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install parse-stack
21
+
22
+ ### Rails
23
+ Parse-Stack comes with support for Rails by adding additional rake tasks and generators. After adding `parse-stack` as a gem dependency in your Gemfile and running `bundle`, you should run the install script:
24
+
25
+ $ rails g parse_stack:install
26
+
27
+ This will create a configuration file (`config/initializers/parse.rb`) and a set of sample models and hooks under `app/models` directory. Modify `config/initializers/parse.rb` file with your Parse-Server API keys. You can then generate models with the `parse_stack:model` generator.
28
+
29
+ $ rails g parse_stack:model Song name:string released:date genres:array
30
+
31
+ This would create a `song.rb` file in `app/models` with the provided properties. Once you are ready to update your schema, you can run the `parse:upgrade` task to upgrade the remote Parse-Server schema to match your new models.
32
+
33
+ $ rails parse:upgrade
34
+
35
+ That should create the new collection `Song` in your Parse-Server backend. For a more full featured example, see [Parse-Server-Rails-Example](https://github.com/modernistik/parse-server-rails-example).
36
+
8
37
  ## Table Of Contents
9
38
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
10
39
  <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@@ -85,18 +114,17 @@ Parse-Stack is a [Parse Server](https://github.com/ParsePlatform/parse-server) R
85
114
  - [Bounding Box Constraint](#bounding-box-constraint)
86
115
  - [Relational Queries](#relational-queries)
87
116
  - [Compound Queries](#compound-queries)
88
- - [Cloud Code Functions](#cloud-code-functions)
89
- - [Cloud Code Background Jobs](#cloud-code-background-jobs)
90
- - [Hooks and Callbacks](#hooks-and-callbacks)
117
+ - [Calling Cloud Code Functions](#calling-cloud-code-functions)
118
+ - [Calling Background Jobs](#calling-background-jobs)
119
+ - [Model Callbacks](#model-callbacks)
91
120
  - [Schema Upgrades and Migrations](#schema-upgrades-and-migrations)
92
121
  - [Push Notifications](#push-notifications)
93
122
  - [Cloud Code Webhooks](#cloud-code-webhooks)
94
- - [Cloud Code functions](#cloud-code-functions)
123
+ - [Cloud Code Functions](#cloud-code-functions)
95
124
  - [Cloud Code Triggers](#cloud-code-triggers)
96
125
  - [Mounting Webhooks Application](#mounting-webhooks-application)
97
126
  - [Register Webhooks](#register-webhooks)
98
127
  - [Parse REST API Client](#parse-rest-api-client)
99
- - [Options](#options-2)
100
128
  - [Request Caching](#request-caching)
101
129
  - [Installation](#installation)
102
130
  - [Development](#development)
@@ -104,7 +132,7 @@ Parse-Stack is a [Parse Server](https://github.com/ParsePlatform/parse-server) R
104
132
  <!-- END doctoc generated TOC please keep comment here to allow auto update -->
105
133
 
106
134
  ## Overview
107
- Parse::Stack is a full stack framework that utilizes several ideas behind [DataMapper](http://datamapper.org/docs/find.html) and [ActiveModel](https://github.com/rails/rails/tree/master/activemodel) to manage and maintain larger scale ruby applications and tools that utilize the Parse Platform. If you are familiar with these technologies, the framework should feel familiar to you.
135
+ Parse-Stack is a full stack framework that utilizes several ideas behind [DataMapper](http://datamapper.org/docs/find.html) and [ActiveModel](https://github.com/rails/rails/tree/master/activemodel) to manage and maintain larger scale ruby applications and tools that utilize the Parse Platform. If you are familiar with these technologies, the framework should feel familiar to you.
108
136
 
109
137
  ```ruby
110
138
 
@@ -430,13 +458,18 @@ This class represents the data and columns contained in the standard Parse `_Ins
430
458
 
431
459
  ```ruby
432
460
  class Parse::Installation < Parse::Object
433
- property :channels, :array
434
461
  property :gcm_sender_id, :string, field: :GCMSenderId
462
+ property :app_identifier
463
+ property :app_name
464
+ property :app_version
435
465
  property :badge, :integer
436
- property :installation_id
466
+ property :channels, :array
437
467
  property :device_token
468
+ property :device_token_last_modified, :integer
438
469
  property :device_type
470
+ property :installation_id
439
471
  property :locale_identifier
472
+ property :parse_version
440
473
  property :push_type
441
474
  property :time_zone
442
475
  end
@@ -508,39 +541,39 @@ Using the example above, we can add the base properties to our classes.
508
541
 
509
542
  ```ruby
510
543
  class Post < Parse::Object
511
- property :title
512
- property :content, :string # explicit
544
+ property :title
545
+ property :content, :string # explicit
513
546
 
514
- # treat the values of this field as symbols instead of strings.
515
- property :category, :string, symbolize: true
547
+ # treat the values of this field as symbols instead of strings.
548
+ property :category, :string, symbolize: true
516
549
 
517
- # maybe a count of comments.
518
- property :comment_count, :integer, default: 0
550
+ # maybe a count of comments.
551
+ property :comment_count, :integer, default: 0
519
552
 
520
553
  # use lambda to access the instance object.
521
554
  # Set draft_date to the created_at date if empty.
522
555
  property :draft_date, :date, default: lambda { |x| x.created_at }
523
556
  # the published date. Maps to "publishDate"
524
- property :publish_date, :date, default: lambda { |x| DateTime.now }
557
+ property :publish_date, :date, default: lambda { |x| DateTime.now }
525
558
 
526
- # maybe whether it is currently visible
527
- property :visible, :boolean
559
+ # maybe whether it is currently visible
560
+ property :visible, :boolean
528
561
 
529
- # a list using
530
- property :tags, :array
562
+ # a list using
563
+ property :tags, :array
531
564
 
532
- # Maps to "featuredImage" column representing a File.
533
- property :featured_image, :file
565
+ # Maps to "featuredImage" column representing a File.
566
+ property :featured_image, :file
534
567
 
535
- property :location, :geopoint
568
+ property :location, :geopoint
536
569
 
537
570
  # Support bytes
538
571
  property :data, :bytes
539
572
 
540
- # store SEO information. Make sure we map it to the column
541
- # "SEO", otherwise it would have implicitly used "seo"
542
- # as the remote column name
543
- property :seo, :object, field: "SEO"
573
+ # store SEO information. Make sure we map it to the column
574
+ # "SEO", otherwise it would have implicitly used "seo"
575
+ # as the remote column name
576
+ property :seo, :object, field: "SEO"
544
577
  end
545
578
  ```
546
579
 
@@ -953,7 +986,7 @@ songs.save
953
986
  ```
954
987
 
955
988
  ### Magic `save_all`
956
- By default, all Parse queries have a maximum fetch limit of 1000. While using the `:max` option, `Parse::Stack` can increase this up to 11,000. In the cases where you need to update a large number of objects, you can utilize the `Parse::Object#save_all` method
989
+ By default, all Parse queries have a maximum fetch limit of 1000. While using the `:max` option, Parse-Stack can increase this up to 11,000. In the cases where you need to update a large number of objects, you can utilize the `Parse::Object#save_all` method
957
990
  to fetch, modify and save objects.
958
991
 
959
992
  This methodology works by continually fetching and saving older records related to the time you begin a `save_all` request (called an "anchor date"), until there are no records left to update. To enable this to work, you must have confidence that any modifications you make to the records will successfully save through you validations that may be present in your `before_save`. This is important, as saving a record will set its `updated_at` date to one newer than the "anchor date" of when the `save_all` started. This `save_all` process will stop whenever no more records match the provided constraints that are older than the "anchor date", or when an object that was previously updated, is seen again in a future fetch (_which means the object failed to save_). Note that `save_all` will automatically manage the correct `updated_at` constraints in the query, so it is recommended that you do not use it as part of the initial constraints.
@@ -1004,7 +1037,7 @@ You can destroy a Parse record, just call the `#destroy` method. It will return
1004
1037
  ```
1005
1038
 
1006
1039
  ### Auto-Fetching Associations
1007
- All associations in `Parse::Stack` are fetched lazily by default. If you wish to include objects as part of your query results you can use the `:includes` expression.
1040
+ All associations in are fetched lazily by default. If you wish to include objects as part of your query results you can use the `:includes` expression.
1008
1041
 
1009
1042
  ```ruby
1010
1043
  song = Song.first
@@ -1016,7 +1049,7 @@ All associations in `Parse::Stack` are fetched lazily by default. If you wish to
1016
1049
 
1017
1050
  ```
1018
1051
 
1019
- However, `Parse::Stack` performs automatic fetching of associations when the associated classes and their properties are locally defined. Using our Artist and Song examples. In this example, the Song object fetched only has a pointer object in its `#artist` field. However, because the framework knows there is a `Artist#name` property, calling `#name` on the artist pointer will automatically go to Parse to fetch the associated object and provide you with the value.
1052
+ However, Parse-Stack performs automatic fetching of associations when the associated classes and their properties are locally defined. Using our Artist and Song examples. In this example, the Song object fetched only has a pointer object in its `#artist` field. However, because the framework knows there is a `Artist#name` property, calling `#name` on the artist pointer will automatically go to Parse to fetch the associated object and provide you with the value.
1020
1053
 
1021
1054
  ```ruby
1022
1055
  song = Song.first
@@ -1181,11 +1214,14 @@ Use with limit to paginate through results. Default is 0 with maximum value bein
1181
1214
  ```
1182
1215
 
1183
1216
  #### :cache
1184
- A true/false value. If you are using the built-in caching middleware, `Parse::Middleware::Caching`, it will prevent it from using a previously cached result if available. The default value is `true`.
1217
+ A `true`, `false` or integer value. If you are using the built-in caching middleware, `Parse::Middleware::Caching`, setting this to `false` will prevent it from using a previously cached result if available. You may pass an integer value, which will allow this request to be cached for the specified number of seconds. The default value is `true`, which uses the [`:expires`](#expires) value that was passed when [configuring the client](#connection-setup).
1185
1218
 
1186
1219
  ```ruby
1187
1220
  # don't use a cached result if available
1188
- Song.all limit: 3, cache: false
1221
+ Song.all limit: 500, cache: false
1222
+
1223
+ # cache this particular request for 60 seconds
1224
+ Song.all limit: 500, cache: 1.minute
1189
1225
  ```
1190
1226
 
1191
1227
  #### :use_master_key
@@ -1201,6 +1237,7 @@ A Parse session token string. If you would like to perform a query as a particul
1201
1237
 
1202
1238
  ```ruby
1203
1239
  # disable sending the master key in the request if configured
1240
+ # and perform this request as a Parse user represented by this token
1204
1241
  Song.all limit: 3, session_token: "<session_token>"
1205
1242
  ```
1206
1243
 
@@ -1499,7 +1536,7 @@ query.or_where(:wins.lt => 5)
1499
1536
  results = query.results
1500
1537
  ```
1501
1538
 
1502
- ## Cloud Code Functions
1539
+ ## Calling Cloud Code Functions
1503
1540
  You can call on your defined Cloud Code functions using the `call_function()` method. The result will be `nil` in case of errors or the value of the `result` field in the Parse response.
1504
1541
 
1505
1542
  ```ruby
@@ -1512,7 +1549,7 @@ You can call on your defined Cloud Code functions using the `call_function()` me
1512
1549
  response.result unless response.error?
1513
1550
  ```
1514
1551
 
1515
- ## Cloud Code Background Jobs
1552
+ ## Calling Background Jobs
1516
1553
  You can trigger background jobs that you have configured in your Parse application as follows.
1517
1554
 
1518
1555
  ```ruby
@@ -1525,7 +1562,7 @@ You can trigger background jobs that you have configured in your Parse applicati
1525
1562
  response.result unless response.error?
1526
1563
  ```
1527
1564
 
1528
- ## Hooks and Callbacks
1565
+ ## Model Callbacks
1529
1566
  All `Parse::Object` subclasses extend [`ActiveModel::Callbacks`](http://api.rubyonrails.org/classes/ActiveModel/Callbacks.html) for `#save` and `#destroy` operations. You can setup internal hooks for `before`, `during` and `after`. See
1530
1567
 
1531
1568
  ```ruby
@@ -1542,7 +1579,7 @@ end
1542
1579
 
1543
1580
  song = Song.new name: "my title"
1544
1581
  puts song.name # 'my title'
1545
- song.save
1582
+ song.save # runs :save callbacks
1546
1583
  puts song.name # 'My Title'
1547
1584
 
1548
1585
  ```
@@ -1590,24 +1627,34 @@ Push notifications are implemented through the `Parse::Push` class. To send push
1590
1627
  ## Cloud Code Webhooks
1591
1628
  Parse Parse allows you to receive Cloud Code webhooks on your own hosted server. The `Parse::Webhooks` class is a lightweight Rack application that routes incoming Cloud Code webhook requests and payloads to locally registered handlers. The payloads are `Parse::Payload` type of objects that represent that data that Parse sends webhook handlers. You can register any of the Cloud Code webhook trigger hooks (`beforeSave`, `afterSave`, `beforeDelete`, `afterDelete`) and function hooks.
1592
1629
 
1593
- ### Cloud Code functions
1630
+ ### Cloud Code Functions
1594
1631
  You can use the `route()` method to register handler blocks. The last value returned by the block will be returned back to the client in a success response. If `error!(value)` is called inside the block, we will return the correct Parse error response with the value you provided.
1595
1632
 
1596
1633
  ```ruby
1597
1634
  # Register handling the 'helloWorld' function.
1598
1635
  Parse::Webhooks.route(:function, :helloWorld) do
1599
1636
  # use the Parse::Payload instance methods in this block
1600
- incoming_params = params #function params
1601
- name = params['name'].to_s
1602
-
1637
+ name = params['name'].to_s #function params
1638
+ puts "CloudCode Webhook helloWorld called in Ruby!"
1603
1639
  # will return proper error response
1604
- error!("Missing argument 'name'.") unless name.present?
1605
- # return early
1606
- "Hello #{name}!"
1640
+ # error!("Missing argument 'name'.") unless name.present?
1641
+
1642
+ name.present? ? "Hello #{name}!" : "Hello World!"
1607
1643
  end
1608
1644
 
1609
1645
  # Advanced: you can register handlers through classes if you prefer
1610
- Parse::Webhooks.route :function, :myFunc, MyClass.method(:my_func)
1646
+ # Parse::Webhooks.route :function, :myFunc, MyClass.method(:my_func)
1647
+ ```
1648
+
1649
+ If you have registered this webhook (see instructions below), you should be able to test it out by running curl using the command below. For a more in-depth example, see [Parse-Server-Rails-Example](https://github.com/modernistik/parse-server-rails-example).
1650
+
1651
+ ```bash
1652
+ curl -X POST \
1653
+ -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
1654
+ -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
1655
+ -H "Content-Type: application/json" \
1656
+ -d '{}' \
1657
+ https://api.parse.com/1/functions/helloWorld
1611
1658
  ```
1612
1659
 
1613
1660
  If you are creating `Parse::Object` subclasses, you may also register them there to keep common code and functionality centralized.
@@ -1619,7 +1666,7 @@ class Song < Parse::Object
1619
1666
  the_user = user # available if a Parse user made the call
1620
1667
  params = params
1621
1668
  # ... do stuff ...
1622
- true
1669
+ some_result
1623
1670
  end
1624
1671
 
1625
1672
  end
@@ -1653,7 +1700,7 @@ You can register webhooks to handle the different object triggers: `:before_save
1653
1700
 
1654
1701
  For any `after_*` hook, return values are not needed since Parse does not utilize them. You may also register as many `after_save` or `after_delete` handlers as you prefer, all of them will be called.
1655
1702
 
1656
- `before_save` and `before_delete` hooks have special functionality. When the `error!` method is called by the provided block, the framework will return the correct error response to Parse with value provided. Returning an error will prevent Parse from saving the object in the case of `before_save` and will prevent Parse from deleting the object when in a `before_delete`. In addition, for a `before_save`, the last value returned by the block will be the value returned in the success response. If the block returns nil or an `empty?` value, it will return `true` as the default response. You can also return a JSON object in a hash format to override the values that will be saved for the object. For this, we recommend using the `payload_update` method. For more details, see [Cloud Code BeforeSave Webhooks](https://parse.com/docs/cloudcode/guide#cloud-code-advanced-beforesave-webhooks)
1703
+ `before_save` and `before_delete` hooks have special functionality. When the `error!` method is called by the provided block, the framework will return the correct error response to Parse with value provided. Returning an error will prevent Parse from saving the object in the case of `before_save` and will prevent Parse from deleting the object when in a `before_delete`. In addition, for a `before_save`, the last value returned by the block will be the value returned in the success response. If the block returns nil or an `empty?` value, it will return `true` as the default response. You can also return a JSON object in a hash format to override the values that will be saved. However, we recommend modifying the `parse_object` provided since it has dirty tracking, and then returning that same object. This will automatically call your model specific `before_save` callbacks and send the proper payload back to Parse. For more details, see [Cloud Code BeforeSave Webhooks](https://parse.com/docs/cloudcode/guide#cloud-code-advanced-beforesave-webhooks)
1657
1704
 
1658
1705
  ```ruby
1659
1706
  # recommended way
@@ -1680,7 +1727,7 @@ class Artist < Parse::Object
1680
1727
  end
1681
1728
 
1682
1729
  # *important* returns a special hash of changed values
1683
- artist.payload_update
1730
+ artist
1684
1731
  end
1685
1732
 
1686
1733
  webhook :before_delete do
@@ -1696,16 +1743,16 @@ end
1696
1743
  The app can be mounted like any regular Rack-based application.
1697
1744
 
1698
1745
  ```ruby
1699
- # Rack in config.ru
1746
+ # Rack (add this to config.ru)
1700
1747
  map "/webhooks" do
1701
1748
  run Parse::Webhooks
1702
1749
  end
1703
1750
 
1704
- # Padrino (in apps.rb)
1751
+ # or in Padrino (add this to apps.rb)
1705
1752
  Padrino.mount('Parse::Webhooks', :cascade => true).to('/webhooks')
1706
1753
 
1707
- # Rails
1708
- RailsApp::Application.routes.draw do
1754
+ # or in Rails (add this in routes.rb)
1755
+ Rails.application.routes.draw do
1709
1756
  mount Parse::Webhooks, :at => '/webhooks'
1710
1757
  end
1711
1758
  ```
@@ -1733,16 +1780,12 @@ end
1733
1780
 
1734
1781
  ```
1735
1782
 
1736
- However, we have predefined a few rake tasks you can use in your application. Just require `parse/stack/tasks` in your `Rakefile` and call `Parse::Stack.load_tasks`. This is useful for web frameworks like `Padrino` and `Rails`.
1783
+ However, we have predefined a few rake tasks you can use in your application. Just require `parse/stack/tasks` in your `Rakefile` and call `Parse::Stack.load_tasks`. This is useful for web frameworks like `Padrino`. Note that if you are using Parse-Stack with Rails, this is automatically done for you through the Railtie.
1737
1784
 
1738
1785
  ```ruby
1739
- # Rails Rakefile example
1740
- require_relative 'config/application'
1786
+ # Add to your Rakefile (if not using Rails)
1741
1787
  require 'parse/stack/tasks' # add this line
1742
-
1743
- Rails.application.load_tasks
1744
1788
  Parse::Stack.load_tasks # add this line
1745
-
1746
1789
  ```
1747
1790
 
1748
1791
  Then you can see the tasks available by typing `rake -T`.
@@ -11,6 +11,11 @@ Parse.setup
11
11
 
12
12
  class Song < Parse::Object
13
13
  property :name
14
+
15
+ before_save do
16
+ self.name = self.name.truncate(60)
17
+ end
18
+
14
19
  end
15
20
  # You can add fixtures and/or initialization code here to make experimenting
16
21
  # with your gem easier. You can also use a different console, if you like.
@@ -48,7 +48,6 @@ module Parse
48
48
  end
49
49
 
50
50
  class BatchOperation
51
- MAX_REQ_SEC = 30
52
51
 
53
52
  attr_accessor :requests, :responses
54
53
  include Enumerable
@@ -120,7 +119,7 @@ module Parse
120
119
  @requests.each_slice(segment) do |slice|
121
120
  @responses << client.batch_request( BatchOperation.new(slice) )
122
121
  #throttle
123
- sleep (slice.count.to_f / MAX_REQ_SEC.to_f )
122
+ # sleep (slice.count.to_f / MAX_REQ_SEC.to_f )
124
123
  end
125
124
  @responses.flatten!
126
125
  #puts "Requests: #{@requests.count} == Response: #{@responses.count}"
@@ -54,7 +54,7 @@ module Parse
54
54
  include Parse::API::Push
55
55
  include Parse::API::Schema
56
56
  RETRY_COUNT = 2
57
- RETRY_DELAY = 3 #seconds
57
+ RETRY_DELAY = 2 #seconds
58
58
 
59
59
  attr_accessor :session, :cache
60
60
  attr_reader :application_id, :api_key, :master_key, :server_url
@@ -189,13 +189,13 @@ module Parse
189
189
  # http method
190
190
  method = method.downcase.to_sym
191
191
  # set the User-Agent
192
- headers["User-Agent".freeze] = "Parse-Stack Ruby Client v#{Parse::Stack::VERSION}".freeze
192
+ headers["User-Agent".freeze] = "Parse-Server Ruby Client v#{Parse::Stack::VERSION}".freeze
193
193
 
194
194
  if opts[:cache] == false
195
- headers[Parse::Middleware::Caching::CACHE_CONTROL] = "no-cache"
195
+ headers[Parse::Middleware::Caching::CACHE_CONTROL] = "no-cache".freeze
196
196
  elsif opts[:cache].is_a?(Numeric)
197
- # future feature
198
- # headers[Parse::Middleware::Caching::CACHE_CONTROL] = "max-age: #{opts[:cache].to_i}"
197
+ # specify the cache duration of this request
198
+ headers[Parse::Middleware::Caching::CACHE_EXPIRES_DURATION] = opts[:cache].to_i
199
199
  end
200
200
 
201
201
  if opts[:use_master_key] == false
@@ -259,15 +259,20 @@ module Parse
259
259
  response
260
260
  rescue Parse::ServiceUnavailableError => e
261
261
  if retry_count > 0
262
- puts "[Parse:ServiceUnavailableError] Retries remaining #{retry_count} : #{response.request}"
262
+ puts "[Parse:Retry] Retries remaining #{retry_count} : #{response.request}"
263
263
  sleep RETRY_DELAY
264
264
  retry_count -= 1
265
265
  retry
266
266
  end
267
267
  raise e
268
268
  rescue Faraday::Error::ClientError, Net::OpenTimeout => e
269
- puts "[Parse:ConnectionError] Faraday/Net:OpenTimeout : #{e.message}"
270
- raise Parse::ConnectionError, e.message
269
+ if retry_count > 0
270
+ puts "[Parse:Retry] Retries remaining #{retry_count} : #{_request}"
271
+ sleep RETRY_DELAY
272
+ retry_count -= 1
273
+ retry
274
+ end
275
+ raise Parse::ConnectionError, "#{_request} : #{e.class} - #{e.message}"
271
276
  end
272
277
 
273
278
  # shorthand for request(:get, uri, query: {})
@@ -5,6 +5,7 @@ require_relative 'protocol'
5
5
  # This is a caching middleware for Parse queries using Moneta.
6
6
  module Parse
7
7
  module Middleware
8
+ class CachingError < Exception; end;
8
9
  class Caching < Faraday::Middleware
9
10
  include Parse::Protocol
10
11
  # Cache-Control: no-cache
@@ -18,6 +19,7 @@ module Parse
18
19
  # * 410 - 'Gone' - removed
19
20
  CACHEABLE_HTTP_CODES = [200, 203, 300, 301, 302].freeze
20
21
  CACHE_CONTROL = 'Cache-Control'.freeze
22
+ CACHE_EXPIRES_DURATION = 'X-Parse-Stack-Cache-Expires'.freeze
21
23
 
22
24
  class << self
23
25
  attr_accessor :enabled, :logging
@@ -43,7 +45,7 @@ module Parse
43
45
  @expires = @opts[:expires]
44
46
 
45
47
  unless @store.is_a?(Moneta::Transformer)
46
- raise "Parse::Middleware::Caching store object must a Moneta key/value store."
48
+ raise Parse::Middleware::CachingError, "Caching store object must a Moneta key/value store (Moneta::Transformer)."
47
49
  end
48
50
 
49
51
  end
@@ -53,54 +55,64 @@ module Parse
53
55
  end
54
56
 
55
57
  def call!(env)
58
+ @request_headers = env[:request_headers]
56
59
 
57
- #unless caching is enabled and we have a valid cache duration
58
- # then just work as a passthrough
59
- return @app.call(env) unless @store.present? && @expires > 0 && self.class.enabled
60
-
61
- cache_enabled = true
60
+ # get default caching state
61
+ @enabled = self.class.enabled
62
+ # disable cache for this request if "no-cache" was passed
63
+ if @request_headers[CACHE_CONTROL] == "no-cache".freeze
64
+ @enabled = false
65
+ end
62
66
 
63
- if env[:request_headers][CACHE_CONTROL] == "no-cache".freeze
64
- cache_enabled = false
67
+ # get the expires information from header (per-request) or instance default
68
+ if @request_headers[CACHE_EXPIRES_DURATION].to_i > 0
69
+ @expires = @request_headers[CACHE_EXPIRES_DURATION].to_i
65
70
  end
66
71
 
72
+ # cleanup
73
+ @request_headers.delete(CACHE_CONTROL)
74
+ @request_headers.delete(CACHE_EXPIRES_DURATION)
75
+
76
+ # if caching is enabled and we have a valid cache duration, use cache
77
+ # otherwise work as a passthrough.
78
+ return @app.call(env) unless @store.present? && @enabled && @expires > 0
79
+
67
80
  url = env.url
68
81
  method = env.method
82
+ @cache_key = url.to_s
69
83
  begin
70
- if cache_enabled && method == :get && url.present? && @store.key?(url)
71
- puts("[Parse::Cache] >>> #{url}") if self.class.logging.present?
84
+ if method == :get && @cache_key.present? && @store.key?(@cache_key)
85
+ puts("[Parse::Cache::Hit] >> #{url}") if self.class.logging.present?
72
86
  response = Faraday::Response.new
73
- res_env = @store[url] # previous cached response
87
+ res_env = @store[@cache_key] # previous cached response
74
88
  body = res_env.respond_to?(:body) ? res_env.body : nil
75
89
  if body.present?
76
90
  response.finish({status: 200, response_headers: { "X-Cache-Response" => true }, body: body })
77
91
  return response
78
92
  else
79
- @store.delete url
93
+ @store.delete @cache_key
80
94
  end
81
- elsif cache_enabled && url.present?
95
+ elsif @cache_key.present?
82
96
  #non GET requets should clear the cache for that same resource path.
83
97
  #ex. a POST to /1/classes/Artist/<objectId> should delete the cache for a GET
84
98
  # request for the same '/1/classes/Artist/<objectId>' where objectId are equivalent
85
- @store.delete url
99
+ @store.delete @cache_key
86
100
  end
87
101
  rescue Errno::EINVAL, Redis::CannotConnectError => e
88
102
  # if the cache store fails to connect, catch the exception but proceed
89
103
  # with the regular request, but turn off caching for this request. It is possible
90
104
  # that the cache connection resumes at a later point, so this is temporary.
91
- cache_enabled = false
92
- warn "[Parse::Cache Error] #{e}"
105
+ @enabled = false
106
+ puts "[Parse::Cache] Error: #{e}"
93
107
  end
94
108
 
95
-
109
+ puts("[Parse::Cache::Miss] !! #{url}") if self.class.logging.present?
96
110
  @app.call(env).on_complete do |response_env|
97
111
  # Only cache GET requests with valid HTTP status codes whose content-length
98
112
  # is greater than 20. Otherwise they could be errors, successes and empty result sets.
99
- if cache_enabled && method == :get && CACHEABLE_HTTP_CODES.include?(response_env.status) &&
113
+ if @enabled && method == :get && CACHEABLE_HTTP_CODES.include?(response_env.status) &&
100
114
  response_env.present? && response_env.response_headers["content-length".freeze].to_i > 20
101
-
102
- @store.store(url, response_env, expires: @expires) # ||= response_env.body
103
-
115
+ @store.store(@cache_key, response_env, expires: @expires) # ||= response_env.body
104
116
  end # if
105
117
  # do something with the response
106
118
  # response_env[:response_headers].merge!(...)
@@ -96,6 +96,7 @@ module Parse
96
96
 
97
97
  anchor_date = Parse::Date.now
98
98
  constraints.merge! :updated_at.on_or_before => anchor_date
99
+ constraints.merge! cache: false
99
100
  # oldest first, so we create a reduction-cycle
100
101
  constraints.merge! order: :updated_at.asc, limit: 100
101
102
  update_query = query(constraints)
@@ -337,6 +338,22 @@ module Parse
337
338
  success
338
339
  end
339
340
 
341
+ def prepare_save!
342
+ run_callbacks(:save) { false }
343
+ end
344
+
345
+ def changes_payload
346
+ h = attribute_updates
347
+ if relation_changes?
348
+ r = relation_change_operations.select { |s| s.present? }.first
349
+ h.merge!(r) if r.present?
350
+ end
351
+ h.merge!(className: parse_class) unless h.empty?
352
+ h.as_json
353
+ end
354
+
355
+ alias_method :update_payload, :changes_payload
356
+
340
357
  # this method is useful to generate an array of additions and removals to a relational
341
358
  # column.
342
359
  def relation_change_operations
@@ -26,6 +26,7 @@ module Parse
26
26
  module Properties
27
27
  # This is an exception that is thrown if there is an issue when creating a specific property for a class.
28
28
  class DefinitionError < Exception; end;
29
+ class ValueError < Exception; end;
29
30
 
30
31
  # These are the base types supported by Parse.
31
32
  TYPES = [:id, :string, :relation, :integer, :float, :boolean, :date, :array, :file, :geopoint, :bytes, :object, :acl].freeze
@@ -411,13 +412,16 @@ module Parse
411
412
  elsif val.is_a?(String)
412
413
  # if it's a string, try parsing the date
413
414
  val = Parse::Date.parse val
415
+ #elsif val.present?
416
+ # pus "[Parse::Stack] Invalid date value '#{val}' assigned to #{self.class}##{key}, it should be a Parse::Date or DateTime."
417
+ # raise ValueError, "Invalid date value '#{val}' assigned to #{self.class}##{key}, it should be a Parse::Date or DateTime."
414
418
  end
415
419
  else
416
420
  # You can provide a specific class instead of a symbol format
417
421
  if data_type.respond_to?(:typecast)
418
422
  val = data_type.typecast(val)
419
423
  else
420
- warn "Property :#{key}: :#{data_type} has not valid data type"
424
+ warn "Property :#{key}: :#{data_type} has no valid data type"
421
425
  val = val #default
422
426
  end
423
427
  end
@@ -52,3 +52,11 @@ class DateTime
52
52
  Parse::Date.parse iso8601(3)
53
53
  end
54
54
  end
55
+
56
+ module ActiveSupport
57
+ class TimeWithZone
58
+ def parse_date
59
+ Parse::Date.parse iso8601(3)
60
+ end
61
+ end
62
+ end
@@ -66,9 +66,10 @@ module Parse
66
66
  return Parse::File if str == TYPE_FILE.freeze
67
67
  return Parse::GeoPoint if str == TYPE_GEOPOINT.freeze
68
68
  return Parse::Date if str == TYPE_DATE.freeze
69
+ return Parse::Bytes if str == TYPE_BYTES.freeze
69
70
  # return Parse::User if str == "User".freeze
70
71
  # return Parse::Installation if str == "Installation".freeze
71
-
72
+
72
73
  str = str.to_s
73
74
  # Basically go through all Parse::Object subclasses and see who is has a parse_class
74
75
  # set to this string. We will cache the results for future use.
@@ -86,7 +87,7 @@ end
86
87
  class String
87
88
  # short helper method to provide lower-first-camelcase
88
89
  def columnize
89
- return "objectId" if self == "id"
90
+ return "objectId".freeze if self == "id".freeze
90
91
  camelize(:lower)
91
92
  end;
92
93
 
@@ -230,6 +230,10 @@ module Parse
230
230
  self.class.new h
231
231
  end
232
232
 
233
+ def pretty
234
+ JSON.pretty_generate( as_json )
235
+ end
236
+
233
237
  def clear_attribute_change!(atts)
234
238
  clear_attribute_changes(atts)
235
239
  end
@@ -295,19 +299,35 @@ module Parse
295
299
  property :email
296
300
  property :password
297
301
  property :username
302
+
303
+ before_save do
304
+ # You cannot specify user ACLs.
305
+ self.clear_attribute_change!(:acl)
306
+ end
307
+
308
+ def anonymous?
309
+ auth_data.present? && auth_data["anonymous"].present?
310
+ end
298
311
  end
299
312
 
300
313
  class Installation < Parse::Object
301
314
  parse_class "_Installation".freeze
302
- property :channels, :array
315
+
303
316
  property :gcm_sender_id, :string, field: :GCMSenderId
317
+ property :app_identifier
318
+ property :app_name
319
+ property :app_version
304
320
  property :badge, :integer
305
- property :installation_id
321
+ property :channels, :array
306
322
  property :device_token
323
+ property :device_token_last_modified, :integer
307
324
  property :device_type
325
+ property :installation_id
308
326
  property :locale_identifier
327
+ property :parse_version
309
328
  property :push_type
310
329
  property :time_zone
330
+
311
331
  end
312
332
 
313
333
  class Role < Parse::Object
@@ -346,10 +366,11 @@ class Array
346
366
  # if it constains the proper fields. Non convertible objects will be removed
347
367
  # If the className is not contained or known, you can pass a table name as an argument
348
368
  def parse_objects(table = nil)
369
+ f = "className".freeze
349
370
  map do |m|
350
371
  next m if m.is_a?(Parse::Pointer)
351
- if m.is_a?(Hash) && (m["className"] || m[:className] || table)
352
- next Parse::Object.build m, (m["className"] || m[:className] || table)
372
+ if m.is_a?(Hash) && (m[f] || m[:className] || table)
373
+ next Parse::Object.build m, (m[f] || m[:className] || table)
353
374
  end
354
375
  nil
355
376
  end.compact
@@ -119,7 +119,7 @@ module Parse
119
119
  elsif expression == :cache
120
120
  self.cache = value
121
121
  elsif expression == :use_master_key
122
- self.cache = value
122
+ self.use_master_key = value
123
123
  elsif expression == :session
124
124
  # you can pass a session token or a Parse::Session
125
125
  value = value.is_a?(Parse::Session) ? value.session_token : value
@@ -315,11 +315,11 @@ module Parse
315
315
 
316
316
  def fetch!(compiled_query)
317
317
  opts = {}
318
- opts[:cache] = false unless self.cache
319
- opts[:use_mster_key] = self.use_master_key
318
+ opts[:cache] = self.cache || false
319
+ opts[:use_master_key] = self.use_master_key
320
320
  opts[:session_token] = self.session_token
321
321
  # for now, don't cache requests where we disable master_key or provide session token
322
- if opts[:use_mster_key] == false || opts[:session_token].present?
322
+ if opts[:use_master_key] == false || opts[:session_token].present?
323
323
  opts[:cache] = false
324
324
  end
325
325
 
@@ -10,3 +10,5 @@ module Parse
10
10
  # Your code goes here...
11
11
  end
12
12
  end
13
+
14
+ require_relative 'stack/railtie' if defined?(::Rails)
@@ -0,0 +1,35 @@
1
+ require 'parse/stack'
2
+ require 'parse/stack/tasks'
3
+ require 'rails/generators'
4
+ require 'rails/generators/named_base'
5
+
6
+ module ParseStack
7
+
8
+ class InstallGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("../templates", __FILE__)
10
+
11
+ desc "This generator creates an initializer file at config/initializers"
12
+ def generate_initializer
13
+ copy_file "parse.rb", "config/initializers/parse.rb"
14
+ copy_file "model_user.rb", File.join("app/models", "user.rb")
15
+ copy_file "model_role.rb", File.join("app/models", "role.rb")
16
+ copy_file "model_session.rb", File.join("app/models", "session.rb")
17
+ copy_file "model_installation.rb", File.join("app/models", "installation.rb")
18
+ copy_file "webhooks.rb", File.join("app/models", "webhooks.rb")
19
+ end
20
+ end
21
+
22
+ class ModelGenerator < Rails::Generators::NamedBase
23
+ source_root File.expand_path(__dir__ + "/templates")
24
+ desc "Creates a Parse::Object model subclass."
25
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
26
+ check_class_collision
27
+
28
+ def create_model_file
29
+ @allowed_types = Parse::Properties::TYPES - [:acl, :id, :relation]
30
+ template "model.erb", File.join("app/models", class_path, "#{file_name}.rb")
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,52 @@
1
+
2
+ class <%= class_name %> < Parse::Object
3
+ # See: https://github.com/modernistik/parse-stack#defining-properties
4
+
5
+ # You can change the inferred Parse table/collection name below
6
+ # parse_class "<%= class_name.to_s.to_parse_class %>"
7
+ <% attributes.each do |attr|
8
+ parse_type = attr.type.to_s.downcase.to_sym
9
+ unless @allowed_types.include?(parse_type)
10
+ puts "\n[Warning] Skipping property `#{attr.name}` with type `#{parse_type}`. Type should be one of #{@allowed_types}."
11
+ next
12
+ end
13
+ %>
14
+ property :<%= attr.name %>, :<%= parse_type -%>
15
+ <% end %>
16
+
17
+ # See: https://github.com/modernistik/parse-stack#cloud-code-webhooks
18
+ # define a before save webhook for <%= class_name %>
19
+ webhook :before_save do
20
+ <%= class_name.to_s.underscore %> = parse_object
21
+ # perform any validations with <%= class_name.to_s.underscore %>
22
+ # use `error!(msg)` to fail the save
23
+ # ...
24
+ <%= class_name.to_s.underscore %>
25
+ end
26
+
27
+ ## define an after save webhook for <%= class_name %>
28
+ #
29
+ # webhook :after_save do
30
+ # <%= class_name.to_s.underscore %> = parse_object
31
+ #
32
+ # end
33
+
34
+ ## define a before delete webhook for <%= class_name %>
35
+ # webhook :before_delete do
36
+ # <%= class_name.to_s.underscore %> = parse_object
37
+ # # use `error!(msg)` to fail the delete
38
+ # true # allow the deletion
39
+ # end
40
+
41
+ ## define an after delete webhook for <%= class_name %>
42
+ # webhook :after_delete do
43
+ # <%= class_name.to_s.underscore %> = parse_object
44
+ # end
45
+
46
+ ## Example of a CloudCode Webhook function
47
+ ## define a `helloWorld` Parse CloudCode function
48
+ # webhook :function, :helloWorld do
49
+ # "Hello!"
50
+ # end
51
+
52
+ end
@@ -0,0 +1,6 @@
1
+
2
+
3
+ class Parse::Installation < Parse::Object
4
+ # See: https://github.com/modernistik/parse-stack#parseinstallation
5
+ # add additional properties here
6
+ end
@@ -0,0 +1,6 @@
1
+
2
+
3
+ class Parse::Role < Parse::Object
4
+ # See: https://github.com/modernistik/parse-stack#parserole
5
+ # add additional properties here
6
+ end
@@ -0,0 +1,6 @@
1
+
2
+
3
+ class Parse::Role < Parse::Object
4
+ # See: https://github.com/modernistik/parse-stack#parsesession
5
+ # add additional properties here
6
+ end
@@ -0,0 +1,13 @@
1
+
2
+ # See: https://github.com/modernistik/parse-stack#parseuser
3
+ class Parse::User < Parse::Object
4
+ # add additional properties
5
+
6
+ # define a before save webhook for Parse::User
7
+ # webhook :before_save do
8
+ # obj = parse_object # Parse::User
9
+ # # make changes to record....
10
+ # obj # will send the proper changelist back to Parse-Server
11
+ # end
12
+
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'parse/stack'
2
+
3
+ # Set your specific Parse keys in your ENV. For all connection options, see
4
+ # https://github.com/modernistik/parse-stack#connection-setup
5
+
6
+ Parse.setup app_id: ENV['PARSE_APP_ID'],
7
+ api_key: ENV['PARSE_API_KEY'],
8
+ master_key: ENV['PARSE_MASTER_KEY'],
9
+ server_url: 'https://api.parse.com/1/'
10
+ # optional
11
+ # logging: false,
12
+ # cache: Moneta.new(:File, dir: 'tmp/cache'),
13
+ # expires: 1 # cache ttl 1 second
@@ -0,0 +1,11 @@
1
+
2
+ # See: https://github.com/modernistik/parse-stack#cloud-code-webhooks
3
+ Parse::Webhooks.route(:function, :helloWorld) do
4
+ # use the Parse::Payload instance methods in this block
5
+ name = params['name'].to_s #function params
6
+
7
+ # will return proper error response
8
+ # error!("Missing argument 'name'.") unless name.present?
9
+
10
+ name.present? ? "Hello #{name}!" : "Hello World!"
11
+ end
@@ -0,0 +1,17 @@
1
+
2
+ module Parse
3
+ module Stack
4
+ class Railtie < ::Rails::Railtie
5
+
6
+ rake_tasks do
7
+ require_relative 'tasks'
8
+ Parse::Stack.load_tasks
9
+ end
10
+
11
+ generators do
12
+ require_relative 'generators/rails'
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -18,11 +18,27 @@ module Parse
18
18
 
19
19
  def install_tasks
20
20
 
21
+ if defined?(::Rails)
22
+ unless Rake::Task.task_defined?('db:seed') || Rails.root.blank?
23
+ namespace :db do
24
+ desc "Seeds your database with by loading db/seeds.rb"
25
+ task :seed => 'parse:env' do
26
+ load Rails.root.join("db","seeds.rb")
27
+ end
28
+ end
29
+ end
30
+ end
31
+
21
32
  namespace :parse do
22
33
 
23
34
  task :env do
35
+
24
36
  if Rake::Task.task_defined?('environment')
25
37
  Rake::Task['environment'].invoke
38
+ if defined?(::Rails)
39
+ Rails.application.eager_load! if Rails.application.present?
40
+ end
41
+
26
42
  end
27
43
  end
28
44
 
@@ -32,7 +48,7 @@ module Parse
32
48
  raise "Please make sure you have setup the Parse.setup configuration before invoking task. Usually done in the :environment task."
33
49
  end
34
50
 
35
- endpoint = ENV['HOOKS_URL']
51
+ endpoint = ENV['HOOKS_URL'] || ''
36
52
  unless endpoint.empty? || endpoint.starts_with?('https://')
37
53
  raise "The ENV variable HOOKS_URL must be a <https> url : '#{endpoint}'. Ex. https://12345678.ngrok.io/webhooks"
38
54
  end
@@ -1,5 +1,5 @@
1
1
  module Parse
2
2
  module Stack
3
- VERSION = "1.3.8"
3
+ VERSION = "1.4.3"
4
4
  end
5
5
  end
@@ -68,16 +68,6 @@ module Parse
68
68
 
69
69
  end
70
70
 
71
- def update_payload
72
- h = attribute_updates
73
- if relation_changes?
74
- r = relation_change_operations.select { |s| s.present? }.first
75
- h.merge!(r) if r.present?
76
- end
77
- h.merge!(className: parse_class) unless h.empty?
78
- h.as_json
79
- end
80
-
81
71
  end
82
72
 
83
73
  class Payload
@@ -147,10 +137,26 @@ module Parse
147
137
  return unless routes[type].present? && routes[type][className].present?
148
138
  registry = routes[type][className]
149
139
 
150
- return payload.instance_exec(payload, &registry) unless registry.is_a?(Array)
151
- results = registry.map { |hook| payload.instance_exec(payload, &hook) }
152
- return results.last
140
+ if registry.is_a?(Array)
141
+ result = registry.map { |hook| payload.instance_exec(payload, &hook) }.last
142
+ else
143
+ result = payload.instance_exec(payload, &registry)
144
+ end
145
+
146
+ if result.is_a?(Parse::Object)
147
+ # if it is a Parse::Object, we will call the registered ActiveModel callbacks
148
+ # and then send the proper changes payload
149
+ if type == :before_save
150
+ # returning false from the callback block only runs the before_* callback
151
+ result.prepare_save!
152
+ result = result.changes_payload
153
+ elsif type == :before_delete
154
+ result.run_callbacks(:destroy) { false }
155
+ result = true
156
+ end
157
+ end
153
158
 
159
+ result
154
160
  end
155
161
 
156
162
  def success(data = true)
@@ -36,7 +36,7 @@ module Parse
36
36
 
37
37
  def register_functions!(endpoint)
38
38
 
39
- unless endpoint.starts_with?('https://')
39
+ unless endpoint.present? && endpoint.starts_with?('https://')
40
40
  raise "The HOOKS_URL must be https: '#{endpoint}''"
41
41
  end
42
42
  endpoint += '/' unless endpoint.ends_with?('/')
@@ -61,7 +61,7 @@ module Parse
61
61
 
62
62
  def register_triggers!(endpoint, include_wildcard: false)
63
63
 
64
- unless endpoint.starts_with?('https://')
64
+ unless endpoint.present? && endpoint.starts_with?('https://')
65
65
  raise "The HOOKS_URL must be https: '#{endpoint}''"
66
66
  end
67
67
  endpoint += '/' unless endpoint.ends_with?('/')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parse-stack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.8
4
+ version: 1.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anthony Persaud
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-23 00:00:00.000000000 Z
11
+ date: 2016-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -228,6 +228,15 @@ files:
228
228
  - lib/parse/query/operation.rb
229
229
  - lib/parse/query/ordering.rb
230
230
  - lib/parse/stack.rb
231
+ - lib/parse/stack/generators/rails.rb
232
+ - lib/parse/stack/generators/templates/model.erb
233
+ - lib/parse/stack/generators/templates/model_installation.rb
234
+ - lib/parse/stack/generators/templates/model_role.rb
235
+ - lib/parse/stack/generators/templates/model_session.rb
236
+ - lib/parse/stack/generators/templates/model_user.rb
237
+ - lib/parse/stack/generators/templates/parse.rb
238
+ - lib/parse/stack/generators/templates/webhooks.rb
239
+ - lib/parse/stack/railtie.rb
231
240
  - lib/parse/stack/tasks.rb
232
241
  - lib/parse/stack/version.rb
233
242
  - lib/parse/webhooks.rb
@@ -254,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
263
  version: '0'
255
264
  requirements: []
256
265
  rubyforge_project:
257
- rubygems_version: 2.5.1
266
+ rubygems_version: 2.6.6
258
267
  signing_key:
259
268
  specification_version: 4
260
269
  summary: Parse-Server Ruby Client and Active Model Object Relational Mapping