Search code examples
javascriptruby-on-railsrubybraintree

How to dynmically set instance variable through a loop?


I want to have have multiple braintree drop-in's on one page depending on the subscription plan a User chooses. I want to be able to have the drop-in appear depending on which is clicked. I'm having trouble attempting how to achieve multiple drop ins on one page without settings the tokens in advance. I tried creating an on-click event function but not sure if I am doing it correctly.

I attempted:

<% @plans.map do |plan| %>
  <button onclick='braintreeClick()-<%= "#{plan.id}" %>'... > Click </button>
...
<% end %>
<script>
function braintreeClick-<%= plan.id %>() {
  var client_token = "<%= @client_token %>";
  ...
}
</script>

I can have multiple forms on a page if I set multiple tokens from the controller.

I tried a few different ways the other night but all failed such as:

@plans.map do |plan|
  @client_token_"#{plan.id}" = gateway.client_token.generate
end

and setting the token:

var client_token = "<%= @client_token_"#{plan.id}" %>";

This breaks my app with Error: unexpected '=' in the controller for the line above

Tried a few other ways such as:

@client_token(plan.id)

Error: unexpected '('

Is there any way to dynamically set my instance variable or even a better way at achieving my javascript without hard coding the javascript and client_token?

I want to be able to dynamically set this in the case of where subscription plans are removed or added.


Solution

  • There are several ways, how to do this:

    The first one is using ruby methods: instance_variable_set and instance_variable_get:

    # set new instance variable
    instance_variable_set("@client_token_#{plan.id}", gateway.client_token.generate)
    
    # read the instance variable
    instance_variable_get("@client_token_#{plan.id}")
    

    You could also use hash, instead of instance variables and it would probably be better

    client_tokens = {}
    @plans.map do |plan|
      client_tokens[plan.id] = gateway.client_token.generate
    end
    
    # and than read it with
    client_tokens[plan.id]
    

    If you want it accessible from more places than directly in the view, you can define it as the instance variable @client_tokens = {}


    If you would like to change it to completely JS thing. you could do something like this:

    <% @plans.map do |plan| %>
      <button onclick='braintreeClick("<%= gateway.client_token.generate %>")'... > Click </button>
    ...
    <% end %>
    <script>
    function braintreeClick(client_token) {
      ...
    }
    </script>
    

    You would generate token and put it directly to braintreeClick function call, now you don't have to bother with instance variables or more ruby code at all. This is better solution than the previous two.


    The best solution would probably be to use unobtrusive javascript call. I don't know what JS framework you use, so I will demonstrate it with the jQuery:

    <% @plans.map do |plan| %>
      <button class="js-brain-tree-button" data-client-token="<%= gateway.client_token.generate %>" ... > Click </button>
    ...
    <% end %>
    <script>
    function braintreeClick(e) {
      var $button = $(e.currentTarget);
      var client_token = $button.data("client-token");
      ...
    };
    
    $(document).ready(function() {
      $(".js-brain-tree-button").on('click', braintreeClick);
    });
    </script>
    

    Note: sorry if there are some typos or errors, I didn't test the code, but it should show the concept