├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── coffee └── watch.coffee ├── doc ├── logo.png ├── logo_small.png └── top_sample.png ├── gon.gemspec ├── js └── watch.js ├── lib ├── gon.rb └── gon │ ├── base.rb │ ├── compatibility │ └── old_rails.rb │ ├── env_finder.rb │ ├── escaper.rb │ ├── global.rb │ ├── helpers.rb │ ├── jbuilder.rb │ ├── jbuilder │ └── parser.rb │ ├── json_dumper.rb │ ├── rabl.rb │ ├── request.rb │ ├── spec_helpers.rb │ ├── version.rb │ └── watch.rb └── spec ├── gon ├── basic_spec.rb ├── global_spec.rb ├── jbuilder_spec.rb ├── rabl_spec.rb ├── templates_spec.rb ├── thread_spec.rb └── watch_spec.rb ├── spec_helper.rb └── test_data ├── _sample_partial.json.jbuilder ├── sample.json.jbuilder ├── sample.rabl ├── sample_rabl_rails.rabl ├── sample_url_helpers.json.jbuilder ├── sample_with_controller_method.json.jbuilder ├── sample_with_helpers.json.jbuilder ├── sample_with_helpers.rabl ├── sample_with_helpers_rabl_rails.rabl ├── sample_with_locals.json.jbuilder └── sample_with_partial.json.jbuilder /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | tidelift: "rubygems/gon" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | tmp/* 6 | .rvmrc 7 | *.idea 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | env: 4 | - "RABL_GEM=rabl" 5 | - "RABL_GEM=rabl-rails" 6 | 7 | rvm: 8 | - 2.3.8 9 | - 2.4.10 10 | - 2.5.8 11 | - 2.6.6 12 | - 2.7.1 13 | - ruby-head 14 | - truffleruby-head 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [Unreleased] 4 | 5 | ## [6.4.0] - 2020-09-18 6 | ### Security 7 | - CVE-2020-25739: Enforce HTML entities escaping in gon output 8 | 9 | ## [6.3.2] - 2019-11-18 10 | ### Security 11 | - Restrict possibility of vulnerable i18n legacy verision (0.3.6.pre) 12 | installation 13 | 14 | ## [6.3.1] - 2019-11-18 15 | ### Changed 16 | - ActionView::Base and ActionController::Base should be loaded inside 17 | ActiveSupport.on_load hook. Thanks to @amatsuda 18 | - Require Ruby >= 2.2.2 (activesupport). Thanks to @nicolasleger 19 | - Update old_rails.rb to reflect GonHelpers -> ControllerHelpers name change. 20 | Thanks to @etipton 21 | 22 | ## [6.2.1] - 2018-07-11 23 | ### Changed 24 | - Update README: correct spelling mistake. Thanks to @EdwardBetts 25 | - Autoload test classes only in test env. Thanks to @wilddima 26 | 27 | ### Fixed 28 | - Fix keys cache. Thanks to @ertrzyiks 29 | - fixing tests by running with rabl and rabl-rails separately. Thanks to 30 | @dsalahutdinov 31 | 32 | ## [6.2.0] - 2017-10-04 33 | ### Added 34 | - Introduce keys cache. Thanks to @vlazar 35 | - Add possibleErrorCallback to watch params. Thanks to @etagwerker 36 | 37 | ### Changed 38 | - Update readme with PhoenixGon hex link. Thanks to @khusnetdinov 39 | - Fix code highlighting in README. Thanks to @ojab 40 | - Refactoring: use attr_reader 41 | 42 | ### Removed 43 | - Remove unnecessary json dependency. 44 | - Remove rubysl and rubinius-developer_tools gem. 45 | 46 | ## [6.1.0] - 2016-07-11 47 | ### Deprecated 48 | - env is deprecated and will be removed from Rails 5.0. Thanks to @dlupu 49 | 50 | ### Fixed 51 | - fix merging routes bug. Thanks to @strikyflo 52 | - Show what method was used in public methods error. 53 | 54 | ### Changed 55 | - Use 'need_tag' as option name to prevent calling 'tag' method. Thanks to 56 | @june29 57 | - Update README; comment out gon.clear from sample code. Thanks to 58 | @speee-nakajima 59 | - Update README; Replace the include_gon method with render_data method. 60 | - Refactoring: use attr_accessor method. 61 | - Refactoring: use attr_reader method. 62 | 63 | ## [6.0.1] - 2015-07-22 64 | ### Changed 65 | - Free dependencies 66 | 67 | ## [6.0.0] - 2015-07-22 68 | ### Added 69 | - nonce option. Thanks to @joeljackson 70 | 71 | ### Changed 72 | - Refactoring 73 | - Included rails url_helpers into jbuilder. Thanks to @razum2um 74 | 75 | ## [5.2.3] - 2014-11-03 76 | ### Added 77 | - Coffescript implementation of watch.js. Thanks to @willcosgrove 78 | - unwatchAll function in watch.js. Thanks to @willcosgrove 79 | 80 | ## [5.2.2] - 2014-10-31 81 | ### Added 82 | - support for controller helper methods in jbuilder 83 | 84 | ## [5.2.1] - 2014-10-28 85 | ### Added 86 | - merge variable feature (for merge hash-like variables instead of overriding 87 | them). Thanks to @jalkoby 88 | 89 | ### Fixed 90 | - fix for jbuilder module. Thanks to @jankovy 91 | 92 | ## [5.2.0] - 2014-08-26 93 | ### Added 94 | - namespace_check option. Thanks to @tommyh 95 | - AMD compatible version of including gon. Thanks to @vijoc 96 | 97 | ### Changed 98 | - Only inject gon into ActionController::Base-like object in spec_helper. Thanks 99 | to @kevinoconnor7 100 | 101 | ### Fixed 102 | - fix issue where include_gon would raise exception if the controller did not 103 | assign any gon variables. Thanks to @asalme 104 | 105 | ## [5.1.2] - 2014-07-22 106 | ### Changed 107 | - Clarifying helpers, dump gon#watch content to safe json before render. Thanks 108 | to @Strech 109 | 110 | ## [5.1.1] - 2014-07-17 111 | ### Added 112 | - global_root option. Thanks to @rafaelliu 113 | - MultiJson support. Thanks to @Strech 114 | 115 | ## [5.1.0] - 2014-06-29 116 | ### Fixed 117 | - Many fixes. Thanks to @Silex, @kilefritz, @irobayna, @kyrylo, @randoum, 118 | @jackquack, @tuvistavie, @Strech for awesome commits and help! 119 | 120 | ## [5.0.4] - 2014-02-13 121 | ### Fixed 122 | - Fix check for get and assign variables for Gon.global 123 | 124 | ## [5.0.3] - 2014-02-12 125 | ### Removed 126 | - Revert changes in gemspec 127 | 128 | ## [5.0.2] - 2014-02-12 129 | ### Fixed 130 | - Fix issue when there is no gon object for current thread and rendering 131 | include_gon (#108 part) (wasn't fixed) (@gregmolnar) 132 | 133 | ## [5.0.1] - 2013-12-30 134 | ### Fixed 135 | - Fix issue when there is no gon object for current thread and rendering 136 | include_gon (#108 part) 137 | 138 | ## [5.0.0] - 2013-12-26 139 | ### Changed 140 | - Gon is threadsafe now! (@razum2um) 141 | - Camelcasing with depth (@MaxSchmeling) 142 | - Optional CDATA and style refactoring (@torbjon) 143 | - jBuilder supports not only String and Hash types of locals (@steakchaser) 144 | - Using ActionDispatch::Request#uuid instead of ActionDispatch::Request#id 145 | (@sharshenov) 146 | 147 | ## [4.1.1] - 2013-06-04 148 | ### Fixed 149 | - Fixed critical XSS vulnerability https://github.com/gazay/gon/issues/84 150 | (@vadimr & @Hebo) 151 | 152 | ## [4.1.0] - 2013-04-14 153 | ### Added 154 | - rabl-rails support (@jtherrell) 155 | 156 | ### Changed 157 | - Refactored script tag generation (@toothrot) 158 | - Stop support for MRI 1.8.7 159 | - Accepting locals in jbuilder templates 160 | 161 | ## [4.0.3] - 2013-04-14 162 | !!!IMPORTANT!!! Last version with compatibility for MRI 1.8.7 163 | 164 | ### Added 165 | - new method `Gon#push` for assign variables through Hash-like objects (@topdev) 166 | ### Changed 167 | - Fixes for 1.8.7 compatibility. 168 | 169 | ## [4.0.2] - 2012-12-17 170 | ### Fixed 171 | - Fixed gon.watch in JS without callback and options 172 | 173 | ## [4.0.1] - 2012-10-25 174 | ### Added 175 | - option :locals to gon.rabl functionality 176 | 177 | ### Changed 178 | - Gon#set_variable and Gon#get_variable moved to public scope 179 | 180 | ### Removed 181 | - BlankSlate requirement (@phoet) 182 | 183 | ## [4.0.0] - 2012-07-23 184 | ### Added 185 | - gon.watch functionality (thanks to @brainopia and @kossnocorp) 186 | - Compatibility with jbuilder paths for partial! method 187 | 188 | ### Changed 189 | - Little bit refactoring - Gon now is a class 190 | 191 | ### Fixed 192 | - Fixed some bugs 193 | 194 | ## [3.0.5] - 2012-06-22 195 | ### Added 196 | - type text/javascript option (@torbjon) 197 | 198 | ### Changed 199 | - A litlle bit refactoring 200 | - Made compatible with active support json encoding for escaping script tags 201 | 202 | ### Fixed 203 | - bug for init option 204 | - clear if init true (@torbjon) 205 | 206 | ## [3.0.4] - 2012-06-02 207 | ### Fixed 208 | - Fix bug with gon clear with global variables, bump version 209 | 210 | ## [3.0.3] - 2012-05-22 211 | ### Added 212 | - init option (@torbjon) 213 | 214 | ### Changed 215 | - Include ActionView::Helpers into Gon::JBuilder 216 | 217 | ## [3.0.2] - 2012-04-28 218 | ### Added 219 | - need_tag option (@afa) 220 | 221 | ## [3.0.0] - 2012-04-17 222 | ### Added 223 | - Added Gon.global for using gon everywhere 224 | 225 | ### Changed 226 | - Almost all code refactored 227 | - Included ActionView::Helpers into Rabl::Engine 228 | 229 | ## [2.3.0] - 2012-04-09 230 | ### Changed 231 | - Don't really remember what was before this version 232 | 233 | [Unreleased]: https://github.com/gazay/gon/compare/v6.3.2...master 234 | [6.3.2]: https://github.com/gazay/gon/compare/v6.3.1...v6.3.2 235 | [6.3.1]: https://github.com/gazay/gon/compare/v6.2.1...v6.3.1 236 | [6.2.1]: https://github.com/gazay/gon/compare/v6.2.0...v6.2.1 237 | [6.2.0]: https://github.com/gazay/gon/compare/v6.1.0...v6.2.0 238 | [6.1.0]: https://github.com/gazay/gon/compare/v6.0.1...v6.1.0 239 | [6.0.1]: https://github.com/gazay/gon/compare/v6.0.0...v6.0.1 240 | [6.0.0]: https://github.com/gazay/gon/compare/v5.2.3...v6.0.0 241 | [5.2.3]: https://github.com/gazay/gon/compare/v5.2.2...v5.2.3 242 | [5.2.2]: https://github.com/gazay/gon/compare/v5.2.1...v5.2.2 243 | [5.2.1]: https://github.com/gazay/gon/compare/v5.2.0...v5.2.1 244 | [5.2.0]: https://github.com/gazay/gon/compare/v5.1.2...v5.2.0 245 | [5.1.2]: https://github.com/gazay/gon/compare/v5.1.1...v5.1.2 246 | [5.1.1]: https://github.com/gazay/gon/compare/v5.1.0...v5.1.1 247 | [5.1.0]: https://github.com/gazay/gon/compare/v5.0.4...v5.1.0 248 | [5.0.4]: https://github.com/gazay/gon/compare/v5.0.3...v5.0.4 249 | [5.0.3]: https://github.com/gazay/gon/compare/v5.0.2...v5.0.3 250 | [5.0.2]: https://github.com/gazay/gon/compare/v5.0.1...v5.0.2 251 | [5.0.1]: https://github.com/gazay/gon/compare/v5.0.0...v5.0.1 252 | [5.0.0]: https://github.com/gazay/gon/compare/v4.1.1...v5.0.0 253 | [4.1.1]: https://github.com/gazay/gon/compare/v4.1.0...v4.1.1 254 | [4.1.0]: https://github.com/gazay/gon/compare/v4.0.3...v4.1.0 255 | [4.0.3]: https://github.com/gazay/gon/compare/v4.0.2...v4.0.3 256 | [4.0.2]: https://github.com/gazay/gon/compare/v4.0.1...v4.0.2 257 | [4.0.1]: https://github.com/gazay/gon/compare/v4.0.0...v4.0.1 258 | [4.0.0]: https://github.com/gazay/gon/compare/v3.0.5...v4.0.0 259 | [3.0.5]: https://github.com/gazay/gon/compare/v3.0.4...v3.0.5 260 | [3.0.4]: https://github.com/gazay/gon/compare/v3.0.3...v3.0.4 261 | [3.0.3]: https://github.com/gazay/gon/compare/v3.0.2...v3.0.3 262 | [3.0.2]: https://github.com/gazay/gon/compare/v3.0.0...v3.0.2 263 | [3.0.0]: https://github.com/gazay/gon/compare/v2.3.0...v3.0.0 264 | [2.3.0]: https://github.com/gazay/gon/releases/tag/v2.3.0 265 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in gon.gemspec 4 | gem ENV['RABL_GEM'] || 'rabl' 5 | 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011-2019 Alexey Gaziev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gon gem — get your Rails variables in your js 2 | 3 | [![Join the chat at https://gitter.im/gazay/gon](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gazay/gon?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | ![Gon. You should try this. If you look closer - you will see an elephant.](https://github.com/gazay/gon/raw/master/doc/logo_small.png) 6 | 7 | [![Build Status](https://travis-ci.org/gazay/gon.svg?branch=master)](https://travis-ci.org/gazay/gon) [![CodeClimate](https://codeclimate.com/github/gazay/gon/badges/gpa.svg)](https://codeclimate.com/github/gazay/gon) 8 | 9 | If you need to send some data to your js files and you don't want to do this with long way through views and parsing - use this force! 10 | 11 | Now you can easily renew data in your variables through ajax with [gon.watch](https://github.com/gazay/gon/wiki/Usage-gon-watch)! 12 | 13 | With [Jbuilder](https://github.com/rails/jbuilder), [Rabl](https://github.com/nesquena/rabl), and [Rabl-Rails](https://github.com/ccocchi/rabl-rails) support! 14 | 15 | For Sinatra available [gon-sinatra](https://github.com/gazay/gon-sinatra). 16 | 17 | For .Net MVC available port [NGon](https://github.com/brooklynDev/NGon). 18 | 19 | For elixir Phoenix available [PhoenixGon](https://github.com/khusnetdinov/phoenix_gon). 20 | 21 | 22 | Sponsored by Evil Martians 23 | 24 | 25 | ## An example of typical use 26 | 27 | ### Very good and detailed example and reasons to use is considered in [railscast](http://railscasts.com/episodes/324-passing-data-to-javascript) by Ryan Bates 28 | 29 | When you need to send some start data from your controller to your js 30 | you might be doing something like this: 31 | 32 | 1. Write this data in controller(presenter/model) to some variable 33 | 2. In view for this action you put this variable to some objects by data 34 | attributes, or write js right in view 35 | 3. Then there can be two ways in js: 36 | + if you previously wrote data in data 37 | attributes - you should parse this attributes and write data to some 38 | js variable. 39 | + if you wrote js right in view (many frontenders would shame you for 40 | that) - you just use data from this js - OK. 41 | 4. You can use your data in your js 42 | 43 | And every time when you need to send some data from action to js you do this. 44 | 45 | With gon you configure it firstly - just put in layout one tag, and add 46 | gem line to your Gemfile and do the following: 47 | 48 | 1. Write variables by 49 | 50 | ``` ruby 51 | gon.variable_name = variable_value 52 | 53 | # or new syntax 54 | gon.push({ 55 | :user_id => 1, 56 | :user_role => "admin" 57 | }) 58 | 59 | gon.push(any_object) # any_object with respond_to? :each_pair 60 | ``` 61 | 62 | 2. In your js you get this by 63 | 64 | ``` js 65 | gon.variable_name 66 | ``` 67 | 68 | 3. profit? 69 | 70 | With the `gon.watch` feature you can easily renew data in gon variables! 71 | Simply call `gon.watch` from your js file. It's super useful 72 | in modern web applications! 73 | 74 | ## Usage 75 | 76 | ### More details about configuration and usage you can find in [gon wiki](https://github.com/gazay/gon/wiki) 77 | 78 | `app/views/layouts/application.html.erb` 79 | 80 | ``` erb 81 | 82 | some title 83 | <%= Gon::Base.render_data %> 84 | 85 | ... 86 | ``` 87 | 88 | For rails 3: 89 | ``` erb 90 | <%= include_gon %> 91 | ... 92 | ``` 93 | 94 | 95 | 96 | You can pass some [options](https://github.com/gazay/gon/wiki/Options) 97 | to `render_data` method. 98 | 99 | You put something like this in the action of your controller: 100 | 101 | ``` ruby 102 | @your_int = 123 103 | @your_array = [1,2] 104 | @your_hash = {'a' => 1, 'b' => 2} 105 | gon.your_int = @your_int 106 | gon.your_other_int = 345 + gon.your_int 107 | gon.your_array = @your_array 108 | gon.your_array << gon.your_int 109 | gon.your_hash = @your_hash 110 | 111 | gon.all_variables # > {:your_int => 123, :your_other_int => 468, :your_array => [1, 2, 123], :your_hash => {'a' => 1, 'b' => 2}} 112 | gon.your_array # > [1, 2, 123] 113 | 114 | # gon.clear # gon.all_variables now is {} 115 | ``` 116 | 117 | Access the variables from your JavaScript file: 118 | 119 | ``` js 120 | alert(gon.your_int) 121 | alert(gon.your_other_int) 122 | alert(gon.your_array) 123 | alert(gon.your_hash) 124 | ``` 125 | 126 | ### AMD compatible version: `include_gon_amd` 127 | 128 | If your site uses AMD modules you can use the `include_gon_amd` helper to 129 | include the variables and watch function as a module. Options are mostly 130 | the same as for `include_gon`, except for `namespace_check`, which does 131 | nothing and `namespace`, which is used as the name of the defined module. 132 | The end result will look somewhat like the following: 133 | 134 | ```js 135 | define('yourNameSpace', [], function() { 136 | var gon = {}; 137 | gon.yourVariable = yourValue; 138 | // etc... 139 | 140 | return gon; 141 | }); 142 | ``` 143 | 144 | A (very) simplified usage example: 145 | 146 | `app/views/layouts/application.html.erb` 147 | 148 | ```ruby 149 | include_gon_amd namespace: 'data' 150 | ``` 151 | 152 | `Some JavaScript module` 153 | 154 | ```js 155 | define(['data'], function(data) { 156 | alert(data.myVariable); 157 | }); 158 | ``` 159 | 160 | ## gon.watch - renew your data easily! 161 | 162 | You can use gon for renewing your data without reloading pages and 163 | writing long js functions! It's really great for some live values. 164 | 165 | Supports `gon.watch.rabl` and `gon.watch.jbuilder` usage. 166 | 167 | [Instruction](https://github.com/gazay/gon/wiki/Usage-gon-watch) for 168 | usage gon.watch. 169 | 170 | ## Usage with Rabl 171 | 172 | You can write your variables assign logic to templates with [Rabl](https://github.com/nesquena/rabl). 173 | The way of writing Rabl templates is very clearly described in their repo. 174 | 175 | Profit of using Rabl with gon: 176 | 177 | 1. You can clean your controllers now! 178 | 2. Work with database objects and collections clearly and easyly 179 | 3. All power of Rabl 180 | 4. You can still be lazy and don't use common way to transfer data in js 181 | 5. And so on 182 | 183 | [Instruction](https://github.com/gazay/gon/wiki/Usage-with-rabl) for 184 | usage gon with Rabl. 185 | 186 | ## Usage with Rabl-Rails 187 | `gon.rabl` works with [rabl-rails](https://github.com/ccocchi/rabl-rails). Learn to write RABL the rabl-rails way [here](https://github.com/ccocchi/rabl-rails). 188 | 189 | Add gon and rabl-rails to your environment: 190 | ```ruby 191 | gem 'gon' 192 | gem 'rabl-rails' 193 | ``` 194 | Define a rabl template using rabl-rails syntax: 195 | ```rabl 196 | #app/views/users/show.rabl 197 | object :@user 198 | attributes :id, :name, :email, :location 199 | ``` 200 | Call gon.rabl in your controller 201 | 202 | ```ruby 203 | #app/controllers/users_controller.rb 204 | def show 205 | @user = User.find(params[:id]) 206 | gon.rabl 207 | end 208 | ``` 209 | 210 | ## Usage with Jbuilder 211 | 212 | Use gon with [Jbuilder](https://github.com/rails/jbuilder) as with [Rabl](https://guthub.com/nesquena/rabl): 213 | 214 | [Instruction](https://github.com/gazay/gon/wiki/Usage-with-jbuilder) for 215 | usage gon with Jbuilder. 216 | 217 | ## gon.global 218 | 219 | You can use gon for sending your data to js from anywhere! It's really 220 | great for some init data. 221 | 222 | [Instruction](https://github.com/gazay/gon/wiki/Usage-gon-global) for 223 | usage gon.global. 224 | 225 | ## Speed up Gon 226 | 227 | You can use any [JSON Engine](https://github.com/intridea/multi_json#supported-json-engines) you want. 228 | Gon uses `MultiJson` with autodetect mode, so all you need is just require your JSON library. 229 | 230 | ## Contributors 231 | 232 | * @gazay 233 | * @takiy33 234 | 235 | Special thanks to @brainopia, @kossnocorp and @ai. 236 | 237 | ## License 238 | 239 | The MIT License 240 | 241 | ## Security Contact 242 | 243 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 244 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | desc 'Run all tests by default' 5 | task :default => :spec 6 | 7 | require 'rspec/core/rake_task' 8 | RSpec::Core::RakeTask.new do |t| 9 | t.rspec_opts = ["--color", '--format doc', '--require spec_helper'] 10 | end -------------------------------------------------------------------------------- /coffee/watch.coffee: -------------------------------------------------------------------------------- 1 | gon._timers = {} 2 | 3 | gon.watch = (name, possibleOptions, possibleCallback, possibleErrorCallback) -> 4 | return unless $? 5 | 6 | if typeof possibleOptions == 'object' 7 | options = {} 8 | for key, value of gon.watchedVariables[name] 9 | options[key] = value 10 | for key, value of possibleOptions 11 | options[key] = value 12 | callback = possibleCallback 13 | errorCallback = possibleErrorCallback 14 | else 15 | options = gon.watchedVariables[name] 16 | callback = possibleOptions 17 | errorCallback = possibleCallback 18 | 19 | performAjax = -> 20 | xhr = $.ajax 21 | type: options.type || 'GET' 22 | url: options.url 23 | data: 24 | _method: options.method 25 | gon_return_variable: true 26 | gon_watched_variable: name 27 | 28 | if errorCallback 29 | xhr.done(callback).fail(errorCallback); 30 | else 31 | xhr.done(callback) 32 | 33 | if options.interval 34 | timer = setInterval(performAjax, options.interval) 35 | gon._timers[name] ?= [] 36 | return gon._timers[name].push 37 | timer: timer 38 | fn: callback 39 | else 40 | return performAjax() 41 | 42 | gon.unwatch = (name, fn) -> 43 | for timer, index in gon._timers[name] when timer.fn == fn 44 | clearInterval(timer.timer) 45 | gon._timers[name].splice(index, 1) 46 | return 47 | 48 | gon.unwatchAll = -> 49 | for variable, timers of gon._timers 50 | for timer in timers 51 | clearInterval(timer.timer) 52 | gon._timers = {} 53 | -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gazay/gon/ab7e6113d0ba002fe4a7f2b72a423d602805c1ce/doc/logo.png -------------------------------------------------------------------------------- /doc/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gazay/gon/ab7e6113d0ba002fe4a7f2b72a423d602805c1ce/doc/logo_small.png -------------------------------------------------------------------------------- /doc/top_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gazay/gon/ab7e6113d0ba002fe4a7f2b72a423d602805c1ce/doc/top_sample.png -------------------------------------------------------------------------------- /gon.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'gon/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'gon' 7 | s.version = Gon::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ['gazay'] 10 | s.licenses = ['MIT'] 11 | s.email = ['alex.gaziev@gmail.com'] 12 | s.homepage = 'https://github.com/gazay/gon' 13 | s.summary = %q{Get your Rails variables in your JS} 14 | s.description = %q{If you need to send some data to your js files and you don't want to do this with long way trough views and parsing - use this force!} 15 | 16 | s.files = `git ls-files`.split("\n") 17 | s.require_paths = ['lib'] 18 | s.required_ruby_version = '>= 2.2.0' 19 | s.add_dependency 'actionpack', '>= 3.0.20' 20 | s.add_dependency 'i18n', '>= 0.7' 21 | s.add_dependency 'request_store', '>= 1.0' 22 | s.add_dependency 'multi_json' 23 | s.add_development_dependency 'rabl', '0.11.3' 24 | s.add_development_dependency 'rabl-rails' 25 | s.add_development_dependency 'rspec', '>= 3.0' 26 | s.add_development_dependency 'jbuilder' 27 | s.add_development_dependency 'railties', '>= 3.0.20' 28 | s.add_development_dependency 'rake' 29 | s.add_development_dependency 'pry' 30 | s.add_development_dependency 'pry-byebug' 31 | end 32 | -------------------------------------------------------------------------------- /js/watch.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | gon._timers = {}; 3 | 4 | gon.watch = function(name, possibleOptions, possibleCallback, possibleErrorCallback) { 5 | var callback, errorCallback, key, options, performAjax, timer, value, _base, _ref; 6 | if (typeof $ === "undefined" || $ === null) { 7 | return; 8 | } 9 | if (typeof possibleOptions === 'object') { 10 | options = {}; 11 | _ref = gon.watchedVariables[name]; 12 | for (key in _ref) { 13 | value = _ref[key]; 14 | options[key] = value; 15 | } 16 | for (key in possibleOptions) { 17 | value = possibleOptions[key]; 18 | options[key] = value; 19 | } 20 | callback = possibleCallback; 21 | errorCallback = possibleErrorCallback; 22 | } else { 23 | options = gon.watchedVariables[name]; 24 | callback = possibleOptions; 25 | errorCallback = possibleCallback; 26 | } 27 | performAjax = function() { 28 | var xhr; 29 | xhr = $.ajax({ 30 | type: options.type || 'GET', 31 | url: options.url, 32 | data: { 33 | _method: options.method, 34 | gon_return_variable: true, 35 | gon_watched_variable: name 36 | } 37 | }); 38 | if (errorCallback) { 39 | return xhr.done(callback).fail(errorCallback); 40 | } else { 41 | return xhr.done(callback); 42 | } 43 | }; 44 | if (options.interval) { 45 | timer = setInterval(performAjax, options.interval); 46 | if ((_base = gon._timers)[name] == null) { 47 | _base[name] = []; 48 | } 49 | return gon._timers[name].push({ 50 | timer: timer, 51 | fn: callback 52 | }); 53 | } else { 54 | return performAjax(); 55 | } 56 | }; 57 | 58 | gon.unwatch = function(name, fn) { 59 | var _i, index, _len, _ref, timer; 60 | _ref = gon._timers[name]; 61 | for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) { 62 | timer = _ref[index]; 63 | if (!(timer.fn === fn)) { 64 | continue; 65 | } 66 | clearInterval(timer.timer); 67 | gon._timers[name].splice(index, 1); 68 | return; 69 | } 70 | }; 71 | 72 | gon.unwatchAll = function() { 73 | var _i, _len, _ref, timer, timers, variable; 74 | _ref = gon._timers; 75 | for (variable in _ref) { 76 | timers = _ref[variable]; 77 | for (_i = 0, _len = timers.length; _i < _len; _i++) { 78 | timer = timers[_i]; 79 | clearInterval(timer.timer); 80 | } 81 | } 82 | return gon._timers = {}; 83 | }; 84 | -------------------------------------------------------------------------------- /lib/gon.rb: -------------------------------------------------------------------------------- 1 | require 'request_store' 2 | require 'action_view' 3 | require 'action_controller' 4 | require 'multi_json' 5 | 6 | require 'gon/base' 7 | require 'gon/env_finder' 8 | require 'gon/global' 9 | require 'gon/watch' 10 | require 'gon/request' 11 | require 'gon/helpers' 12 | require 'gon/escaper' 13 | require 'gon/rabl' 14 | require 'gon/jbuilder' 15 | require 'gon/jbuilder/parser' 16 | require 'gon/json_dumper' 17 | 18 | # NOTE : ActionDispatch::Request#uuid appears only in Rails 3.2.1 19 | unless ActionDispatch::Request.public_instance_methods.include?(:uuid) 20 | require 'gon/compatibility/old_rails' 21 | end 22 | 23 | require 'gon/spec_helpers' 24 | 25 | class Gon 26 | class << self 27 | 28 | def global 29 | Gon::Global 30 | end 31 | 32 | def watch 33 | Gon::Watch 34 | end 35 | 36 | def method_missing(method, *args, &block) 37 | if method.to_s =~ /=$/ 38 | if public_method_name?(method) 39 | raise "You can't use Gon public methods for storing data: #{method}" 40 | end 41 | if self == Gon && !current_gon 42 | raise 'Assign request-specific gon variables only through `gon` helper, not through Gon constant' 43 | end 44 | 45 | set_variable(method.to_s.delete('='), args[0]) 46 | else 47 | get_variable(method.to_s) 48 | end 49 | end 50 | 51 | def get_variable(name) 52 | current_gon.gon[name] 53 | end 54 | 55 | def set_variable(name, value) 56 | current_gon.gon[name] = value 57 | end 58 | 59 | def merge_variable(name, value) 60 | old_value = all_variables[name] 61 | if value.is_a?(Hash) && old_value.is_a?(Hash) 62 | value = old_value.deep_merge(value) 63 | end 64 | set_variable(name, value) 65 | end 66 | 67 | def push(data = {}, merge = false) 68 | raise 'Object must have each_pair method' unless data.respond_to? :each_pair 69 | 70 | if merge 71 | data.each_pair do |name, value| 72 | merge_variable(name.to_s, value) 73 | end 74 | else 75 | data.each_pair do |name, value| 76 | set_variable(name.to_s, value) 77 | end 78 | end 79 | end 80 | 81 | def all_variables 82 | current_gon ? current_gon.gon : {} 83 | end 84 | 85 | def clear 86 | current_gon.clear if current_gon 87 | end 88 | 89 | def rabl(*args) 90 | data, options = Gon::Rabl.handler(args) 91 | store_builder_data 'rabl', data, options 92 | end 93 | 94 | def jbuilder(*args) 95 | ensure_template_handler_is_defined 96 | data, options = Gon::Jbuilder.handler(args) 97 | store_builder_data 'jbuilder', data, options 98 | end 99 | 100 | def inspect 101 | 'Gon' 102 | end 103 | 104 | private 105 | 106 | def current_gon 107 | RequestStore.store[:gon] 108 | end 109 | 110 | def store_builder_data(builder, data, options) 111 | if options[:as] 112 | set_variable(options[:as].to_s, data) 113 | elsif data.is_a? Hash 114 | data.each { |k, v| set_variable(k, v) } 115 | else 116 | set_variable(builder, data) 117 | end 118 | end 119 | 120 | def public_method_name?(method) 121 | public_methods.include?(method.to_s[0..-2].to_sym) 122 | end 123 | 124 | # JbuilderTemplate will not be defined if jbuilder is required 125 | # before gon. By loading jbuilder again, JbuilderTemplate will 126 | # now be defined 127 | def ensure_template_handler_is_defined 128 | load 'jbuilder.rb' unless defined?(JbuilderTemplate) 129 | end 130 | 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /lib/gon/base.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | class Gon 4 | module Base 5 | VALID_OPTION_DEFAULTS = { 6 | namespace: 'gon', 7 | camel_case: false, 8 | camel_depth: 1, 9 | watch: false, 10 | need_tag: true, 11 | type: false, 12 | cdata: true, 13 | global_root: 'global', 14 | namespace_check: false, 15 | amd: false, 16 | nonce: nil 17 | } 18 | 19 | class << self 20 | 21 | def render_data(options = {}) 22 | _o = define_options(options) 23 | 24 | script = formatted_data(_o) 25 | script = Gon::Escaper.escape_unicode(script) 26 | script = Gon::Escaper.javascript_tag(script, _o.type, _o.cdata, _o.nonce) if _o.need_tag 27 | 28 | script.html_safe 29 | end 30 | 31 | private 32 | 33 | def define_options(options) 34 | _o = OpenStruct.new 35 | 36 | VALID_OPTION_DEFAULTS.each do |opt_name, default| 37 | _o.send("#{opt_name}=", options.fetch(opt_name, default)) 38 | end 39 | _o.watch = options[:watch] || !Gon.watch.all_variables.empty? 40 | _o.cameled = _o.camel_case 41 | 42 | _o 43 | end 44 | 45 | def formatted_data(_o) 46 | script = '' 47 | before, after = render_wrap(_o) 48 | script << before 49 | 50 | script << gon_variables(_o.global_root). 51 | map { |key, val| render_variable(_o, key, val) }.join 52 | script << (render_watch(_o) || '') 53 | 54 | script << after 55 | script 56 | end 57 | 58 | def render_wrap(_o) 59 | if _o.amd 60 | ["define('#{_o.namespace}',[],function(){var gon={};", 'return gon;});'] 61 | else 62 | before = \ 63 | if _o.namespace_check 64 | "window.#{_o.namespace}=window.#{_o.namespace}||{};" 65 | else 66 | "window.#{_o.namespace}={};" 67 | end 68 | [before, ''] 69 | end 70 | end 71 | 72 | def render_variable(_o, key, value) 73 | js_key = convert_key(key, _o.cameled) 74 | if _o.amd 75 | "gon['#{js_key}']=#{to_json(value, _o.camel_depth)};" 76 | else 77 | "#{_o.namespace}.#{js_key}=#{to_json(value, _o.camel_depth)};" 78 | end 79 | end 80 | 81 | def render_watch(_o) 82 | if _o.watch and Gon::Watch.all_variables.present? 83 | if _o.amd 84 | Gon.watch.render_amd 85 | else 86 | Gon.watch.render 87 | end 88 | end 89 | end 90 | 91 | def to_json(value, camel_depth) 92 | # starts at 2 because 1 is the root key which is converted in the formatted_data method 93 | Gon::JsonDumper.dump convert_hash_keys(value, 2, camel_depth) 94 | end 95 | 96 | def convert_hash_keys(value, current_depth, max_depth) 97 | return value if current_depth > (max_depth.is_a?(Symbol) ? 1000 : max_depth) 98 | 99 | case value 100 | when Hash 101 | Hash[value.map { |k, v| 102 | [ convert_key(k, true), convert_hash_keys(v, current_depth + 1, max_depth) ] 103 | }] 104 | when Enumerable 105 | value.map { |v| convert_hash_keys(v, current_depth + 1, max_depth) } 106 | else 107 | value 108 | end 109 | end 110 | 111 | def gon_variables(global_root) 112 | data = {} 113 | 114 | if Gon.global.all_variables.present? 115 | if global_root.blank? 116 | data = Gon.global.all_variables 117 | else 118 | data[global_root.to_sym] = Gon.global.all_variables 119 | end 120 | end 121 | 122 | data.merge(Gon.all_variables) 123 | end 124 | 125 | def convert_key(key, camelize) 126 | cache = RequestStore.store[:gon_keys_cache] ||= {} 127 | cache["#{key}_#{camelize}"] ||= camelize ? key.to_s.camelize(:lower) : key.to_s 128 | end 129 | 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /lib/gon/compatibility/old_rails.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | class Gon 4 | module ControllerHelpers 5 | private 6 | 7 | # override this since ActionDispatch::Request#uuid appears only in Rails 3.2.1 8 | def gon_request_uuid 9 | @gon_request_uuid ||= SecureRandom.uuid 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/gon/env_finder.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | module EnvFinder 3 | ENV_CONTROLLER_KEY = 'action_controller.instance' 4 | ENV_RESPONSE_KEY = 'action_controller.rescue.response' 5 | 6 | class << self 7 | 8 | def controller_env(options = {}) 9 | options[:controller] || 10 | ( 11 | current_gon && 12 | current_gon.env[ENV_CONTROLLER_KEY] || 13 | current_gon.env[ENV_RESPONSE_KEY]. 14 | instance_variable_get('@template'). 15 | instance_variable_get('@controller') 16 | ) 17 | end 18 | 19 | def template_path(options, extension) 20 | if options[:template] 21 | if right_extension?(extension, options[:template]) 22 | options[:template] 23 | else 24 | [options[:template], extension].join('.') 25 | end 26 | else 27 | controller = controller_env(options).controller_path 28 | action = controller_env(options).action_name 29 | "app/views/#{controller}/#{action}.json.#{extension}" 30 | end 31 | end 32 | 33 | private 34 | 35 | def right_extension?(extension, template_path) 36 | File.extname(template_path) == ".#{extension}" 37 | end 38 | 39 | def current_gon 40 | RequestStore.store[:gon] 41 | end 42 | 43 | end 44 | 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/gon/escaper.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | module Escaper 3 | extend ActionView::Helpers::JavaScriptHelper 4 | extend ActionView::Helpers::TagHelper 5 | 6 | class << self 7 | 8 | def escape_unicode(javascript) 9 | if javascript 10 | result = escape_line_separator(javascript) 11 | javascript.html_safe? ? result.html_safe : result 12 | end 13 | end 14 | 15 | def javascript_tag(content, type, cdata, nonce) 16 | options = {} 17 | options.merge!( { type: 'text/javascript' } ) if type 18 | options.merge!( { nonce: nonce } ) if nonce 19 | 20 | content_tag(:script, javascript_cdata_section(content, cdata).html_safe, options) 21 | end 22 | 23 | def javascript_cdata_section(content, cdata) 24 | if cdata 25 | "\n//#{cdata_section("\n#{content}\n//")}\n" 26 | else 27 | "\n#{content}\n" 28 | end 29 | end 30 | 31 | private 32 | 33 | def escape_line_separator(javascript) 34 | javascript.gsub(/\\u2028/u, '
') 35 | end 36 | 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/gon/global.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | class Global < Gon 3 | class << self 4 | 5 | def all_variables 6 | @global_vars || {} 7 | end 8 | 9 | def clear 10 | @global_vars = {} 11 | end 12 | 13 | def inspect 14 | 'Gon::Global' 15 | end 16 | 17 | def rabl(*args) 18 | data, options = Gon::Rabl.handler(args, true) 19 | store_builder_data 'rabl', data, options 20 | end 21 | 22 | def jbuilder(*args) 23 | ensure_template_handler_is_defined 24 | data, options = Gon::Jbuilder.handler(args, true) 25 | store_builder_data 'jbuilder', data, options 26 | end 27 | 28 | private 29 | 30 | def get_variable(name) 31 | @global_vars ||= {} 32 | @global_vars[name] 33 | end 34 | 35 | def set_variable(name, value) 36 | @global_vars ||= {} 37 | @global_vars[name] = value 38 | end 39 | 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/gon/helpers.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | module ViewHelpers 3 | def include_gon(options = {}) 4 | if variables_for_request_present? 5 | Gon::Base.render_data(options) 6 | elsif Gon.global.all_variables.present? || options[:init].present? 7 | Gon.clear 8 | Gon::Base.render_data(options) 9 | else 10 | '' 11 | end 12 | end 13 | 14 | def include_gon_amd(options={}) 15 | Gon::Base.render_data(options.merge({amd: true})) 16 | end 17 | 18 | private 19 | 20 | def variables_for_request_present? 21 | current_gon && current_gon.gon 22 | end 23 | 24 | def current_gon 25 | RequestStore.store[:gon] 26 | end 27 | end 28 | 29 | module ControllerHelpers 30 | def gon 31 | if wrong_gon_request? 32 | gon_request = Request.new(request.env) 33 | gon_request.id = gon_request_uuid 34 | RequestStore.store[:gon] = gon_request 35 | end 36 | Gon 37 | end 38 | 39 | private 40 | 41 | def wrong_gon_request? 42 | current_gon.blank? || current_gon.id != gon_request_uuid 43 | end 44 | 45 | def current_gon 46 | RequestStore.store[:gon] 47 | end 48 | 49 | def gon_request_uuid 50 | request.uuid 51 | end 52 | end 53 | end 54 | 55 | ActiveSupport.on_load :action_view do 56 | ActionView::Base.send :include, Gon::ViewHelpers 57 | end 58 | ActiveSupport.on_load :action_controller do 59 | ActionController::Base.send :include, Gon::ControllerHelpers 60 | end 61 | -------------------------------------------------------------------------------- /lib/gon/jbuilder.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | module Jbuilder 3 | class << self 4 | 5 | def handler(args, global = false) 6 | options = parse_options_from args 7 | valid_options? options, global 8 | 9 | controller = Gon::EnvFinder.controller_env(options) 10 | controller_name = global ? '' : controller.controller_path 11 | 12 | parser = Gon::Jbuilder::Parser.new( 13 | template_path: Gon::EnvFinder.template_path(options, 'jbuilder'), 14 | controller: controller, 15 | controller_name: controller_name, 16 | locals: options[:locals] 17 | ) 18 | data = parser.parse! 19 | 20 | [data, options] 21 | end 22 | 23 | private 24 | 25 | def valid_options?(options, global) 26 | if global && !options[:template] 27 | raise 'You should provide :template when use jbuilder with global variables' 28 | end 29 | end 30 | 31 | def parse_options_from(args) 32 | if old_api? args 33 | text = "[DEPRECATION] view_path argument is now optional. " 34 | text << "If you need to specify it, " 35 | text << "please use gon.jbuilder(:template => 'path')" 36 | warn text 37 | 38 | args.extract_options!.merge(:template => args[0]) 39 | elsif new_api? args 40 | args.first 41 | else 42 | {} 43 | end 44 | end 45 | 46 | def old_api?(args) 47 | args.first.is_a? String 48 | end 49 | 50 | def new_api?(args) 51 | args.first.is_a? Hash 52 | end 53 | 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/gon/jbuilder/parser.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | module Jbuilder 3 | class Parser 4 | include ::ActionView::Helpers 5 | 6 | attr_accessor :template_location, :controller, :_controller_name, :locals 7 | 8 | def initialize(parse_params) 9 | @template_location = parse_params[:template_path] 10 | @controller = parse_params[:controller] 11 | @_controller_name = parse_params[:controller_name] 12 | @locals = parse_params[:locals] || {} 13 | end 14 | 15 | def parse! 16 | assign_controller_variables controller 17 | eval_controller_helpers controller 18 | eval_controller_url_helpers controller 19 | locals['__controller'] = controller 20 | wrap_locals_in_methods locals 21 | 22 | partials = find_partials(File.readlines(template_location)) 23 | source = partials.join('') 24 | 25 | parse_source source, controller 26 | end 27 | 28 | def assign_controller_variables(controller) 29 | controller.instance_variables.each do |name| 30 | self.instance_variable_set \ 31 | name, 32 | controller.instance_variable_get(name) 33 | end 34 | end 35 | 36 | def eval_controller_helpers(controller) 37 | controller._helper_methods.each do |meth| 38 | self.class.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 39 | def #{meth}(*args, &blk) # def current_user(*args, &blk) 40 | __controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk) 41 | end # end 42 | ruby_eval 43 | end 44 | end 45 | 46 | def eval_controller_url_helpers(controller) 47 | if defined?(Rails) && Rails.respond_to?(:application) 48 | Rails.application.routes.url_helpers.instance_methods.each do |meth| 49 | self.class.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 50 | def #{meth}(*args, &blk) # def user_path(*args, &blk) 51 | __controller.send(%(#{meth}), *args, &blk) # controller.send(:user_path, *args, &blk) 52 | end # end 53 | ruby_eval 54 | end 55 | end 56 | end 57 | 58 | def wrap_locals_in_methods(locals) 59 | locals.each do |name, value| 60 | self.class.class_eval do 61 | define_method "#{name}" do 62 | return value 63 | end 64 | end 65 | end 66 | end 67 | 68 | def parse_source(source, controller) 69 | output = ::JbuilderTemplate.encode(controller) do |json| 70 | eval source 71 | end 72 | JSON.parse(output) 73 | end 74 | 75 | def parse_partial(partial_line) 76 | path = partial_line.match(/['"]([^'"]*)['"]/)[1] 77 | path = parse_path path 78 | options_hash = partial_line.match(/,(.*)/)[1] 79 | 80 | set_options_from_hash(options_hash) if options_hash.present? 81 | 82 | find_partials File.readlines(path) 83 | end 84 | 85 | def set_options_from_hash(options_hash) 86 | options = eval "{#{options_hash}}" 87 | options.each do |name, val| 88 | self.instance_variable_set("@#{name.to_s}", val) 89 | eval "def #{name}; self.instance_variable_get('@' + '#{name.to_s}'); end" 90 | end 91 | end 92 | 93 | def parse_path(path) 94 | return path if File.exists?(path) 95 | if (splitted = path.split('/')).blank? 96 | raise 'Something wrong with partial path in your jbuilder templates' 97 | elsif splitted.size == 1 98 | splitted.shift(@_controller_name) 99 | end 100 | construct_path(splitted) 101 | end 102 | 103 | def construct_path(args) 104 | last_arg = args.pop 105 | tmp_path = 'app/views/' + args.join('/') 106 | path = path_with_ext(tmp_path + "/_#{last_arg}") 107 | path || path_with_ext(tmp_path + "/#{last_arg}") 108 | end 109 | 110 | def path_with_ext(path) 111 | return path if File.exists?(path) 112 | return "#{path}.jbuilder" if File.exists?("#{path}.jbuilder") 113 | return "#{path}.json.jbuilder" if File.exists?("#{path}.json.jbuilder") 114 | end 115 | 116 | def find_partials(lines = []) 117 | lines.map do |line| 118 | if line =~ /partial!/ 119 | parse_partial line 120 | else 121 | line 122 | end 123 | end.flatten 124 | end 125 | 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/gon/json_dumper.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | module JsonDumper 3 | # Taken from ERB::Util 4 | JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u 5 | JSON_ESCAPE = { 6 | "&" => '\u0026', 7 | ">" => '\u003e', 8 | "<" => '\u003c', 9 | "\u2028" => '\u2028', 10 | "\u2029" => '\u2029' 11 | } 12 | 13 | def self.dump(object) 14 | dumped_json = MultiJson.dump object, 15 | mode: :compat, escape_mode: :xss_safe, time_format: :ruby 16 | escape(dumped_json) 17 | end 18 | 19 | def self.escape(json) 20 | json.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/gon/rabl.rb: -------------------------------------------------------------------------------- 1 | require 'action_view' 2 | 3 | begin 4 | require 'rabl' # use rabl gem if it's available 5 | rescue LoadError 6 | end 7 | begin 8 | require 'rabl-rails' # use rabl-rails gem if it's available 9 | rescue LoadError 10 | end 11 | 12 | class Gon 13 | module Rabl 14 | class << self 15 | 16 | def handler(args, global = false) 17 | options = parse_options_from args, global 18 | if global && !options[:template] 19 | raise 'You should provide :template when use rabl with global variables' 20 | end 21 | 22 | data = parse_rabl \ 23 | Gon::EnvFinder.template_path(options, 'rabl'), 24 | Gon::EnvFinder.controller_env(options), 25 | options[:locals] 26 | 27 | [data, options] 28 | end 29 | 30 | private 31 | 32 | def parse_rabl(rabl_path, controller, locals) 33 | if defined? ::Rabl 34 | parse_with_rabl rabl_path, controller, locals 35 | elsif defined? ::RablRails 36 | parse_with_rabl_rails rabl_path, controller, locals 37 | else 38 | raise 'rabl or rabl-rails must be required in order to use gon.rabl' 39 | end 40 | end 41 | 42 | def parse_with_rabl(rabl_path, controller, locals) 43 | locals ||= {} 44 | source = File.read(rabl_path) 45 | include_helpers 46 | rabl_engine = ::Rabl::Engine.new(source, :format => 'json', :template => rabl_path) 47 | output = rabl_engine.render(controller, locals) 48 | JSON.parse(output) 49 | end 50 | 51 | def parse_with_rabl_rails(rabl_path, controller, locals) 52 | locals ||= {} 53 | source = File.read(rabl_path) 54 | original_formats = controller.formats 55 | controller.formats = [:json] 56 | view_context = controller.send(:view_context) 57 | locals.each { |k, v| view_context.assigns[k.to_s] = v } 58 | output = RablRails::Library.instance.get_rendered_template(source, view_context) 59 | controller.formats = original_formats 60 | JSON.parse(output) 61 | end 62 | 63 | def parse_options_from(args, global) 64 | if old_api? args 65 | unless global 66 | text = "[DEPRECATION] view_path argument is now optional. " 67 | text << "If you need to specify it, " 68 | text << "please use gon.rabl(:template => 'path')" 69 | warn text 70 | end 71 | 72 | args.extract_options!.merge(:template => args[0]) 73 | elsif new_api? args 74 | args.first 75 | else 76 | {} 77 | end 78 | end 79 | 80 | def include_helpers 81 | unless ::Rabl::Engine.include? ::ActionView::Helpers 82 | ::Rabl::Engine.send(:include, ::ActionView::Helpers) 83 | end 84 | end 85 | 86 | def old_api?(args) 87 | args.first.is_a? String 88 | end 89 | 90 | def new_api?(args) 91 | args.first.is_a? Hash 92 | end 93 | 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/gon/request.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | class Request 3 | attr_reader :env, :gon 4 | attr_accessor :id 5 | 6 | def initialize(environment) 7 | @env = environment 8 | @gon = {} 9 | end 10 | 11 | def clear 12 | @gon = {} 13 | end 14 | 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/gon/spec_helpers.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | module SpecHelper 3 | module Rails 4 | extend ActiveSupport::Concern 5 | 6 | module ClassMethods 7 | module GonSession 8 | def process(*, **) 9 | # preload threadlocal & store controller instance 10 | if controller.is_a? ActionController::Base 11 | controller.gon 12 | Gon.send(:current_gon).env[Gon::EnvFinder::ENV_CONTROLLER_KEY] = 13 | controller 14 | end 15 | super 16 | end 17 | end 18 | 19 | def new(*) 20 | super.extend(GonSession) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | 27 | if ENV['RAILS_ENV'] == 'test' && defined?(ActionController::TestCase::Behavior) 28 | ActionController::TestCase::Behavior.send :include, Gon::SpecHelper::Rails 29 | end 30 | 31 | -------------------------------------------------------------------------------- /lib/gon/version.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | VERSION = '6.4.0' 3 | end 4 | -------------------------------------------------------------------------------- /lib/gon/watch.rb: -------------------------------------------------------------------------------- 1 | class Gon 2 | class Watch < Gon 3 | class << self 4 | 5 | JS_FUNCTION = File.read(File.expand_path('../../../js/watch.js', __FILE__)) 6 | 7 | def render 8 | JS_FUNCTION + "window.gon.watchedVariables=#{Gon::JsonDumper.dump all_variables};" 9 | end 10 | 11 | def render_amd 12 | JS_FUNCTION + "gon.watchedVariables=#{Gon::JsonDumper.dump all_variables};" 13 | end 14 | 15 | def all_variables 16 | @watch_variables || {} 17 | end 18 | 19 | def clear 20 | @watch_variables = {} 21 | end 22 | 23 | private 24 | 25 | def set_variable(name, value) 26 | if return_variable?(name) 27 | return_variable value 28 | elsif Gon.send(:current_gon) 29 | variable = {} 30 | @watch_variables ||= {} 31 | env = Gon.send(:current_gon).env 32 | variable['url'] = env['ORIGINAL_FULLPATH'] || env['REQUEST_URI'] 33 | variable['method'] = env['REQUEST_METHOD'] 34 | variable['name'] = name 35 | 36 | @watch_variables[name] = variable 37 | super 38 | end 39 | end 40 | 41 | def return_variable?(variable) 42 | controller = Gon::EnvFinder.controller_env 43 | params = controller.params 44 | variable = variable.to_s.gsub('=', '') 45 | 46 | controller.request.xhr? && 47 | params[:gon_return_variable] && 48 | params[:gon_watched_variable] == variable 49 | end 50 | 51 | def return_variable(value) 52 | controller = Gon::EnvFinder.controller_env 53 | controller.render json: Gon::Escaper.escape_unicode(Gon::JsonDumper.dump value) 54 | end 55 | 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/gon/basic_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gon do 2 | 3 | before(:each) do 4 | Gon.clear 5 | end 6 | 7 | describe '#all_variables' do 8 | 9 | it 'returns all variables in hash' do 10 | Gon.a = 1 11 | Gon.b = 2 12 | Gon.c = Gon.a + Gon.b 13 | expect(Gon.c).to eq(3) 14 | expect(Gon.all_variables).to eq({ 'a' => 1, 'b' => 2, 'c' => 3 }) 15 | end 16 | 17 | it 'supports all data types' do 18 | Gon.int = 1 19 | Gon.float = 1.1 20 | Gon.string = 'string' 21 | Gon.symbol = :symbol 22 | Gon.array = [1, 'string'] 23 | Gon.hash_var = { :a => 1, :b => '2' } 24 | Gon.hash_w_array = { :a => [2, 3] } 25 | Gon.klass = Hash 26 | end 27 | 28 | it 'can be filled with dynamic named variables' do 29 | check = {} 30 | 3.times do |i| 31 | Gon.set_variable("variable#{i}", i) 32 | check["variable#{i}"] = i 33 | end 34 | 35 | expect(Gon.all_variables).to eq(check) 36 | end 37 | 38 | it 'can set and get variable with dynamic name' do 39 | var_name = "variable#{rand}" 40 | 41 | Gon.set_variable(var_name, 1) 42 | expect(Gon.get_variable(var_name)).to eq(1) 43 | end 44 | 45 | it 'can be support new push syntax' do 46 | Gon.push({ :int => 1, :string => 'string' }) 47 | expect(Gon.all_variables).to eq({ 'int' => 1, 'string' => 'string' }) 48 | end 49 | 50 | it 'push with wrong object' do 51 | expect { 52 | Gon.push(String.new('string object')) 53 | }.to raise_error('Object must have each_pair method') 54 | end 55 | 56 | describe "#merge_variable" do 57 | it 'deep merges the same key' do 58 | Gon.merge_variable(:foo, { bar: { tar: 12 }, car: 23 }) 59 | Gon.merge_variable(:foo, { bar: { dar: 21 }, car: 12 }) 60 | expect(Gon.get_variable(:foo)).to eq(bar: { tar: 12, dar: 21 }, car: 12) 61 | end 62 | 63 | it 'merges on push with a flag' do 64 | Gon.push(foo: { bar: 1 }) 65 | Gon.push({ foo: { tar: 1 } }, :merge) 66 | expect(Gon.get_variable("foo")).to eq(bar: 1, tar: 1) 67 | end 68 | 69 | context 'overrides key' do 70 | specify "the previous value wasn't hash" do 71 | Gon.merge_variable(:foo, 2) 72 | Gon.merge_variable(:foo, { a: 1 }) 73 | expect(Gon.get_variable(:foo)).to eq(a: 1) 74 | end 75 | 76 | specify "the new value isn't a hash" do 77 | Gon.merge_variable(:foo, { a: 1 }) 78 | Gon.merge_variable(:foo, 2) 79 | expect(Gon.get_variable(:foo)).to eq(2) 80 | end 81 | end 82 | end 83 | 84 | end 85 | 86 | describe '#include_gon' do 87 | 88 | before(:each) do 89 | Gon::Request. 90 | instance_variable_set(:@request_id, request.object_id) 91 | expect(ActionView::Base.instance_methods).to include(:include_gon) 92 | @base = ActionView::Base.new(nil,{}, nil) 93 | @base.request = request 94 | end 95 | 96 | it 'outputs correct js with an integer' do 97 | Gon.int = 1 98 | expect(@base.include_gon).to eq(wrap_script( 99 | 'window.gon={};' + 100 | 'gon.int=1;')) 101 | end 102 | 103 | it 'outputs correct js with a string' do 104 | Gon.str = %q(a'b"c) 105 | expect(@base.include_gon).to eq(wrap_script( 106 | 'window.gon={};' + 107 | %q(gon.str="a'b\"c";)) 108 | ) 109 | end 110 | 111 | it 'outputs correct js with a script string' do 112 | Gon.str = %q() 113 | escaped_str = "\\u003c/script\\u003e\\u003cscript\\u003ealert('!')\\u003c/script\\u003e" 114 | expect(@base.include_gon).to eq(wrap_script( 115 | 'window.gon={};' + 116 | %Q(gon.str="#{escaped_str}";)) 117 | ) 118 | end 119 | 120 | it 'outputs correct js with an integer and type' do 121 | Gon.int = 1 122 | expect(@base.include_gon(type: true)).to eq('') 128 | end 129 | 130 | it 'outputs correct js with an integer, camel-case and namespace' do 131 | Gon.int_cased = 1 132 | expect(@base.include_gon(camel_case: true, namespace: 'camel_cased')).to eq( 133 | wrap_script('window.camel_cased={};' + 134 | 'camel_cased.intCased=1;') 135 | ) 136 | end 137 | 138 | it 'outputs correct js with camel_depth = :recursive' do 139 | Gon.test_hash = { test_depth_one: { test_depth_two: 1 } } 140 | expect(@base.include_gon(camel_case: true, camel_depth: :recursive)).to eq( 141 | wrap_script('window.gon={};' + 142 | 'gon.testHash={"testDepthOne":{"testDepthTwo":1}};') 143 | ) 144 | end 145 | 146 | it 'outputs correct js with camel_depth = 2' do 147 | Gon.test_hash = { test_depth_one: { test_depth_two: 1 } } 148 | expect(@base.include_gon(camel_case: true, camel_depth: 2)).to eq( 149 | wrap_script('window.gon={};' + 150 | 'gon.testHash={"testDepthOne":{"test_depth_two":1}};') 151 | ) 152 | end 153 | 154 | it 'outputs correct js for an array with camel_depth = :recursive' do 155 | Gon.test_hash = { test_depth_one: [{ test_depth_two: 1 }, { test_depth_two: 2 }] } 156 | expect(@base.include_gon(camel_case: true, camel_depth: :recursive)).to eq( \ 157 | wrap_script('window.gon={};' + 158 | 'gon.testHash={"testDepthOne":[{"testDepthTwo":1},{"testDepthTwo":2}]};') 159 | ) 160 | end 161 | 162 | it 'outputs correct key with camel_case option set alternately ' do 163 | Gon.test_hash = 1 164 | @base.include_gon(camel_case: true) 165 | 166 | expect(@base.include_gon(camel_case: false)).to eq( 167 | wrap_script('window.gon={};' + 168 | 'gon.test_hash=1;') 169 | ) 170 | end 171 | 172 | it 'outputs correct js with an integer and without tag' do 173 | Gon.int = 1 174 | expect(@base.include_gon(need_tag: false)).to eq( \ 175 | 'window.gon={};' + 176 | 'gon.int=1;' 177 | ) 178 | end 179 | 180 | it 'outputs correct js without variables, without tag and gon init if before there was data' do 181 | Gon::Request. 182 | instance_variable_set(:@request_id, 123) 183 | Gon::Request.instance_variable_set(:@request_env, { 'gon' => { :a => 1 } }) 184 | expect(@base.include_gon(need_tag: false, init: true)).to eq( \ 185 | 'window.gon={};' 186 | ) 187 | end 188 | 189 | it 'outputs correct js without variables, without tag and gon init' do 190 | expect(@base.include_gon(need_tag: false, init: true)).to eq( \ 191 | 'window.gon={};' 192 | ) 193 | end 194 | 195 | it 'outputs correct js without variables, without tag, gon init and an integer' do 196 | Gon.int = 1 197 | expect(@base.include_gon(need_tag: false, init: true)).to eq( \ 198 | 'window.gon={};' + 199 | 'gon.int=1;' 200 | ) 201 | end 202 | 203 | it 'outputs correct js without cdata, without type, gon init and an integer' do 204 | Gon.int = 1 205 | expect(@base.include_gon(cdata: false, type: false)).to eq( 206 | wrap_script( 207 | "\n" + 208 | 'window.gon={};' + 209 | 'gon.int=1;' + 210 | "\n", false) 211 | ) 212 | end 213 | 214 | it 'outputs correct js with type text/javascript' do 215 | expect(@base.include_gon(need_type: true, init: true)).to eq(wrap_script('window.gon={};')) 216 | end 217 | 218 | it 'outputs correct js with namespace check' do 219 | expect(@base.include_gon(namespace_check: true)).to eq(wrap_script('window.gon=window.gon||{};')) 220 | end 221 | 222 | it 'outputs correct js without namespace check' do 223 | expect(@base.include_gon(namespace_check: false)).to eq(wrap_script('window.gon={};')) 224 | end 225 | 226 | context "without a current_gon instance" do 227 | 228 | before(:each) do 229 | RequestStore.store[:gon] = nil 230 | allow(Gon).to receive(:current_gon).and_return(nil) 231 | end 232 | 233 | it "does not raise an exception" do 234 | expect { @base.include_gon }.to_not raise_error 235 | end 236 | 237 | it 'outputs correct js' do 238 | expect(@base.include_gon).to eq("") 239 | end 240 | 241 | it 'outputs correct js with init' do 242 | expect(@base.include_gon(init: true)).to eq(wrap_script('window.gon={};')) 243 | end 244 | 245 | end 246 | 247 | end 248 | 249 | describe '#include_gon_amd' do 250 | 251 | before(:each) do 252 | Gon::Request. 253 | instance_variable_set(:@request_id, request.object_id) 254 | @base = ActionView::Base.new(nil, {}, nil) 255 | @base.request = request 256 | end 257 | 258 | it 'is included in ActionView::Base as a helper' do 259 | expect(ActionView::Base.instance_methods).to include(:include_gon_amd) 260 | end 261 | 262 | it 'outputs correct js without variables' do 263 | expect(@base.include_gon_amd).to eq( wrap_script( \ 264 | 'define(\'gon\',[],function(){'+ 265 | 'var gon={};return gon;'+ 266 | '});') 267 | ) 268 | end 269 | 270 | it 'outputs correct js with an integer' do 271 | Gon.int = 1 272 | 273 | expect(@base.include_gon_amd).to eq( wrap_script( 274 | 'define(\'gon\',[],function(){'+ 275 | 'var gon={};gon[\'int\']=1;return gon;'+ 276 | '});') 277 | ) 278 | end 279 | 280 | it 'outputs correct module name when given a namespace' do 281 | expect(@base.include_gon_amd(namespace: 'data')).to eq(wrap_script( 282 | 'define(\'data\',[],function(){'+ 283 | 'var gon={};return gon;'+ 284 | '});') 285 | ) 286 | end 287 | end 288 | 289 | it 'returns exception if try to set public method as variable' do 290 | expect { Gon.all_variables = 123 }.to raise_error(RuntimeError) 291 | expect { Gon.rabl = 123 }.to raise_error(RuntimeError) 292 | end 293 | 294 | describe '#check_for_rabl_and_jbuilder' do 295 | 296 | let(:controller) { ActionController::Base.new } 297 | 298 | it 'should be able to handle constants array (symbols)' do 299 | allow(Gon).to receive(:constants) { Gon.constants } 300 | expect { Gon.rabl :template => 'spec/test_data/sample.rabl', :controller => controller }.not_to raise_error 301 | expect { Gon.jbuilder :template => 'spec/test_data/sample.json.jbuilder', :controller => controller }.not_to raise_error 302 | end 303 | end 304 | end 305 | -------------------------------------------------------------------------------- /spec/gon/global_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gon::Global do 2 | 3 | before(:each) do 4 | Gon::Global.clear 5 | Gon::Request.instance_variable_set(:@request_env, nil) 6 | end 7 | 8 | describe '#all_variables' do 9 | 10 | it 'returns all variables in hash' do 11 | Gon.global.a = 1 12 | Gon.global.b = 2 13 | Gon.global.c = Gon.global.a + Gon.global.b 14 | expect(Gon.global.c).to eq(3) 15 | expect(Gon.global.all_variables).to eq({ 'a' => 1, 'b' => 2, 'c' => 3 }) 16 | end 17 | 18 | it 'supports all data types' do 19 | Gon.global.int = 1 20 | Gon.global.float = 1.1 21 | Gon.global.string = 'string' 22 | Gon.global.symbol = :symbol 23 | Gon.global.array = [1, 'string'] 24 | Gon.global.hash_var = { :a => 1, :b => '2' } 25 | Gon.global.hash_w_array = { :a => [2, 3] } 26 | Gon.global.klass = Hash 27 | end 28 | 29 | end 30 | 31 | describe '#include_gon' do 32 | 33 | before(:each) do 34 | Gon.clear 35 | expect(ActionView::Base.instance_methods).to include(:include_gon) 36 | @base = ActionView::Base.new(nil, {}, nil) 37 | @base.request = request 38 | end 39 | 40 | it 'outputs correct js with an integer' do 41 | Gon.global.int = 1 42 | expect(@base.include_gon).to eq("") 48 | end 49 | 50 | it 'outputs correct js with an integer and integer in Gon' do 51 | Gon.int = 1 52 | Gon.global.int = 1 53 | expect(@base.include_gon).to eq("") 60 | end 61 | 62 | it 'outputs correct js with a string' do 63 | Gon.global.str = %q(a'b"c) 64 | expect(@base.include_gon).to eq("") 70 | end 71 | 72 | it 'outputs correct js with a script string' do 73 | Gon.global.str = %q() 74 | escaped_str = "\\u003c/script\\u003e\\u003cscript\\u003ealert('!')\\u003c/script\\u003e" 75 | expect(@base.include_gon).to eq("") 81 | end 82 | 83 | it 'outputs correct js with a unicode line separator' do 84 | Gon.global.str = "\u2028" 85 | expect(@base.include_gon).to eq("") 91 | end 92 | 93 | it 'outputs locally overridden value' do 94 | Gon.str = 'local value' 95 | Gon.global.str = 'global value' 96 | expect(@base.include_gon(global_root: '')).to eq("") 102 | end 103 | 104 | it "includes the tag attributes in the script tag" do 105 | Gon.global.int = 1 106 | expect(@base.include_gon(nonce: 'test')).to eq("") 112 | end 113 | 114 | end 115 | 116 | it 'returns exception if try to set public method as variable' do 117 | expect { Gon.global.all_variables = 123 }.to raise_error(RuntimeError) 118 | expect { Gon.global.rabl = 123 }.to raise_error(RuntimeError) 119 | end 120 | 121 | context 'with jbuilder and rabl' do 122 | 123 | before :each do 124 | controller.instance_variable_set('@objects', objects) 125 | end 126 | 127 | let(:controller) { ActionController::Base.new } 128 | let(:objects) { [1, 2] } 129 | 130 | it 'works fine with rabl' do 131 | Gon.global.rabl :template => 'spec/test_data/sample.rabl', :controller => controller 132 | expect(Gon.global.objects.length).to eq(2) 133 | end 134 | 135 | it 'works fine with jbuilder' do 136 | Gon.global.jbuilder :template => 'spec/test_data/sample.json.jbuilder', :controller => controller 137 | expect(Gon.global.objects.length).to eq(2) 138 | end 139 | 140 | it 'should throw exception, if use rabl or jbuilder without :template' do 141 | expect { Gon.global.rabl }.to raise_error(RuntimeError) 142 | expect { Gon.global.jbuilder }.to raise_error(RuntimeError) 143 | end 144 | 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /spec/gon/jbuilder_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gon do 2 | 3 | describe '.jbuilder' do 4 | context 'render jbuilder templates' do 5 | 6 | before do 7 | Gon.clear 8 | controller.instance_variable_set('@objects', objects) 9 | end 10 | 11 | let(:controller) { ActionController::Base.new } 12 | let(:objects) { [1, 2] } 13 | 14 | it 'render json from jbuilder template' do 15 | Gon.jbuilder :template => 'spec/test_data/sample.json.jbuilder', :controller => controller 16 | expect(Gon.objects.length).to eq(2) 17 | end 18 | 19 | it 'render json from jbuilder template with locals' do 20 | Gon.jbuilder :template => 'spec/test_data/sample_with_locals.json.jbuilder', 21 | :controller => controller, 22 | :locals => { :some_local => 1234, :some_complex_local => OpenStruct.new(:id => 1234) } 23 | expect(Gon.some_local).to eq(1234) 24 | expect(Gon.some_complex_local_id).to eq(1234) 25 | end 26 | 27 | it 'render json from jbuilder template with locals' do 28 | Gon.jbuilder :template => 'spec/test_data/sample_with_helpers.json.jbuilder', :controller => controller 29 | expect(Gon.date).to eq('about 6 hours') 30 | end 31 | 32 | it 'render json from jbuilder template with controller methods' do 33 | class << controller 34 | def private_controller_method 35 | 'gon test helper works' 36 | end 37 | helper_method :private_controller_method 38 | private :private_controller_method 39 | end 40 | 41 | Gon.jbuilder :template => 'spec/test_data/sample_with_controller_method.json.jbuilder', :controller => controller 42 | expect(Gon.data_from_method).to eq('gon test helper works') 43 | end 44 | 45 | it 'render json from jbuilder template with a partial' do 46 | controller.view_paths << 'spec/test_data' 47 | Gon.jbuilder :template => 'spec/test_data/sample_with_partial.json.jbuilder', :controller => controller 48 | expect(Gon.objects.length).to eq(2) 49 | end 50 | 51 | context 'within Rails' do 52 | before do 53 | module ::Rails 54 | end 55 | 56 | allow(Rails).to receive_message_chain("application.routes.url_helpers.instance_methods") { [:user_path] } 57 | controller.instance_variable_set('@user_id', 1) 58 | end 59 | 60 | after do 61 | Object.send(:remove_const, :Rails) 62 | end 63 | 64 | it 'includes url_helpers' do 65 | expect(controller).to receive(:user_path) { |id| "/users/#{id}" } 66 | Gon.jbuilder :template => 'spec/test_data/sample_url_helpers.json.jbuilder', :controller => controller 67 | expect(Gon.url).to eq '/users/1' 68 | end 69 | end 70 | 71 | end 72 | 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /spec/gon/rabl_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gon do 2 | describe '.rabl' do 3 | 4 | before :each do 5 | Gon.clear 6 | controller.instance_variable_set('@objects', objects) 7 | end 8 | 9 | let(:controller) { ActionController::Base.new } 10 | let(:objects) { [1, 2] } 11 | 12 | context 'render template with deprecation' do 13 | it 'still works' do 14 | Gon.rabl 'spec/test_data/sample.rabl', :controller => controller 15 | expect(Gon.objects.length).to eq(2) 16 | end 17 | end 18 | 19 | context 'option locals' do 20 | it 'works without locals object properly' do 21 | Gon.rabl( 22 | :template => 'spec/test_data/sample.rabl', 23 | :controller => controller 24 | ) 25 | expect(Gon.objects.map { |it| it['object']['inspect'] }).to eq(%w(1 2)) 26 | end 27 | 28 | it 'works with different locals object' do 29 | Gon.rabl( 30 | :template => 'spec/test_data/sample.rabl', 31 | :controller => controller, 32 | :locals => { :objects => [3, 4] } 33 | ) 34 | expect(Gon.objects.map { |it| it['object']['inspect'] }).to eq(%w(3 4)) 35 | end 36 | end 37 | 38 | it 'works if rabl is included' do 39 | Gon.rabl :template => 'spec/test_data/sample.rabl', :controller => controller 40 | expect(Gon.objects.length).to eq(2) 41 | end 42 | 43 | it 'works with ActionView::Helpers' do 44 | Gon.rabl :template => 'spec/test_data/sample_with_helpers.rabl', :controller => controller 45 | expect(Gon.objects.first['object']['time_ago']).to eq('about 6 hours') 46 | end 47 | 48 | it 'raise exception if rabl is not included' do 49 | Gon.send :remove_const, 'Rabl' 50 | expect { Gon.rabl :template => 'spec/test_data/sample.rabl', :controller => controller }.to raise_error(NameError) 51 | load 'rabl.rb' 52 | load 'gon/rabl.rb' 53 | end 54 | 55 | context '.template_path' do 56 | context 'template is specified' do 57 | 58 | it 'add the extension if not included in the template name' do 59 | expect(Gon::EnvFinder.send(:template_path, { :template => 'spec/test_data/sample' }, 'rabl')).to eql('spec/test_data/sample.rabl') 60 | end 61 | 62 | it 'return the specified template' do 63 | expect(Gon::EnvFinder.send(:template_path, { :template => 'spec/test_data/sample.rabl' }, 'rabl')).to eql('spec/test_data/sample.rabl') 64 | end 65 | 66 | end 67 | 68 | context 'template is not specified' do 69 | 70 | before do 71 | Gon.clear 72 | controller.instance_variable_set('@objects', objects) 73 | controller.action_name = 'show' 74 | end 75 | 76 | let(:controller) { ActionController::Base.new } 77 | let(:objects) { [1, 2] } 78 | 79 | context 'the action doesn as a template at a different format' do 80 | it 'return the same template as the action with rabl extension' do 81 | expect(Gon::EnvFinder.send(:template_path, { :controller => controller }, 'rabl')).to eql('app/views/action_controller/base/show.json.rabl') 82 | end 83 | end 84 | 85 | end 86 | end 87 | 88 | end 89 | 90 | end 91 | -------------------------------------------------------------------------------- /spec/gon/templates_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gon do 2 | 3 | describe '.template_path' do 4 | context 'template is specified' do 5 | 6 | it 'add the extension if not included in the template name' do 7 | expect(Gon::EnvFinder.send(:template_path, { :template => 'spec/test_data/sample' }, 'jbuilder')).to eql('spec/test_data/sample.jbuilder') 8 | end 9 | 10 | it 'return the specified template' do 11 | expect(Gon::EnvFinder.send(:template_path, { :template => 'spec/test_data/sample.jbuilder' }, 'jbuilder')).to eql('spec/test_data/sample.jbuilder') 12 | end 13 | 14 | end 15 | 16 | context 'template is not specified' do 17 | 18 | before do 19 | Gon.clear 20 | controller.instance_variable_set('@objects', objects) 21 | controller.action_name = 'show' 22 | end 23 | 24 | let(:controller) { ActionController::Base.new } 25 | let(:objects) { [1, 2] } 26 | 27 | context 'the action doesn as a template at a different format' do 28 | it 'return the same template as the action with rabl extension' do 29 | expect(Gon::EnvFinder.send(:template_path, { :controller => controller }, 'jbuilder')).to eql('app/views/action_controller/base/show.json.jbuilder') 30 | end 31 | end 32 | 33 | end 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /spec/gon/thread_spec.rb: -------------------------------------------------------------------------------- 1 | class GonTestWorker 2 | include Gon::ControllerHelpers 3 | 4 | def request 5 | @request ||= ActionDispatch::TestRequest.create 6 | end 7 | 8 | def env 9 | request.env 10 | end 11 | 12 | def execute 13 | gon.clear 14 | gon.a ||= 1 15 | gon.a += 1 16 | end 17 | 18 | def value 19 | gon.a 20 | end 21 | end 22 | 23 | describe 'threading behaviour' do 24 | before do 25 | allow(Gon).to receive(:current_gon).and_call_original 26 | end 27 | 28 | it 'is threadsafe' do 29 | threads = [] 30 | 10.times do 31 | threads << Thread.new do 32 | gtw = GonTestWorker.new 33 | gtw.execute 34 | expect(gtw.value).to eq 2 35 | end 36 | end 37 | threads.each(&:join) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/gon/watch_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gon::Watch do 2 | 3 | let(:controller) { ActionController::Base.new } 4 | let(:request) { ActionDispatch::Request.new({}) } 5 | 6 | before :each do 7 | controller.request = request 8 | controller.params = {} 9 | env = {} 10 | env['ORIGINAL_FULLPATH'] = '/foo' 11 | env['REQUEST_METHOD'] = 'GET' 12 | 13 | Gon::Watch.clear 14 | Gon.send(:current_gon).instance_variable_set(:@env, env) 15 | Gon.send(:current_gon).env['action_controller.instance'] = controller 16 | Gon.clear 17 | end 18 | 19 | it 'should add variables to Gon#all_variables hash' do 20 | Gon.a = 1 21 | Gon.watch.b = 2 22 | expect(Gon.all_variables).to eq({ 'a' => 1, 'b' => 2 }) 23 | end 24 | 25 | describe '#all_variables' do 26 | 27 | it 'should generate array with current request url, method type and variable names' do 28 | Gon.watch.a = 1 29 | expect(Gon.watch.all_variables).to eq({ 'a' => { 'url' => '/foo', 'method' => 'GET', 'name' => 'a' } }) 30 | end 31 | 32 | end 33 | 34 | describe '#render' do 35 | 36 | it 'should render function with variables in gon namespace' do 37 | Gon.watch.a = 1 38 | expect(Gon.watch.render).to match(/gon\.watch\s=/) 39 | expect(Gon.watch.render).to match(/gon\.watchedVariables/) 40 | end 41 | 42 | end 43 | 44 | describe 'Render concrete variable' do 45 | before do 46 | env = Gon.send(:current_gon).env 47 | env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' 48 | 49 | allow(controller).to receive_messages(request: ActionDispatch::Request.new(env)) 50 | Gon.send(:current_gon).env['action_controller.instance'] = controller 51 | end 52 | 53 | context 'when request variable is json safe content' do 54 | before do 55 | allow(controller).to receive_messages(params: { 56 | gon_return_variable: true, 57 | gon_watched_variable: 'safety'}) 58 | end 59 | 60 | it 'should return value of variable if called right request' do 61 | expect(controller).to receive(:render).with(json: '12345') 62 | Gon.watch.safety = 12345 63 | end 64 | end 65 | 66 | context 'when request variable is json unsafe content' do 67 | let(:expected) { %Q{"\\u003cscript\\u003e'\\"\\u003c/script\\u003e
Dangerous"} } 68 | 69 | before do 70 | allow(controller).to receive_messages(params: { 71 | gon_return_variable: true, 72 | gon_watched_variable: 'danger'}) 73 | end 74 | 75 | it 'should return value of variable if called right request' do 76 | expect(controller).to receive(:render).with(json: expected) 77 | Gon.watch.danger = %Q{\u2028Dangerous} 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rails/railtie' 2 | # We don't require rails for specs, but jbuilder works only in rails. 3 | # And it checks version of rails. I've decided to configure jbuilder for rails v4 4 | module Rails 5 | module VERSION 6 | MAJOR = 4 7 | end 8 | 9 | def self.version 10 | '4.2.0' 11 | end 12 | end 13 | 14 | require 'gon' 15 | 16 | require 'jbuilder' 17 | 18 | RSpec.configure do |config| 19 | config.before(:each) do 20 | RequestStore.store[:gon] = Gon::Request.new({}) 21 | @request = RequestStore.store[:gon] 22 | allow(Gon).to receive(:current_gon).and_return(@request) 23 | end 24 | end 25 | 26 | def request 27 | @request ||= double 'request', :env => {} 28 | end 29 | 30 | def wrap_script(content, cdata=true) 31 | script = "' 36 | end 37 | -------------------------------------------------------------------------------- /spec/test_data/_sample_partial.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.objects objects 2 | -------------------------------------------------------------------------------- /spec/test_data/sample.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.objects @objects 2 | -------------------------------------------------------------------------------- /spec/test_data/sample.rabl: -------------------------------------------------------------------------------- 1 | collection @objects => 'objects' 2 | attributes :inspect 3 | -------------------------------------------------------------------------------- /spec/test_data/sample_rabl_rails.rabl: -------------------------------------------------------------------------------- 1 | collection :@objects => 'objects' 2 | attributes :inspect 3 | -------------------------------------------------------------------------------- /spec/test_data/sample_url_helpers.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.url user_path(@user_id) 2 | -------------------------------------------------------------------------------- /spec/test_data/sample_with_controller_method.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.objects @objects 2 | json.data_from_method private_controller_method 3 | -------------------------------------------------------------------------------- /spec/test_data/sample_with_helpers.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.date distance_of_time_in_words(20000) 2 | -------------------------------------------------------------------------------- /spec/test_data/sample_with_helpers.rabl: -------------------------------------------------------------------------------- 1 | collection @objects => 'objects' 2 | attributes :id 3 | node(:time_ago) { |_| distance_of_time_in_words(20000) } 4 | -------------------------------------------------------------------------------- /spec/test_data/sample_with_helpers_rabl_rails.rabl: -------------------------------------------------------------------------------- 1 | collection :@objects => 'objects' 2 | attributes :inspect 3 | node(:time_ago) { distance_of_time_in_words(20000) } 4 | -------------------------------------------------------------------------------- /spec/test_data/sample_with_locals.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.some_local some_local 2 | json.some_complex_local_id some_complex_local.id 3 | -------------------------------------------------------------------------------- /spec/test_data/sample_with_partial.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! 'spec/test_data/_sample_partial.json.jbuilder', :objects => @objects 2 | --------------------------------------------------------------------------------