Alex Musayev

Joined

11,030 Experience
63 Lessons Completed
6 Questions Solved

Activity

I figured out the solution. Here are basic concepts:

1. To check if an instance method was called, it is possible to use a mock. For example, Minites::Mock.

2. To make sure a piece of code (Executor#action in the example above) performs a call to a class/instance method (Subject#call), it is possible to replace target method with a stub. Minitest library provides Object#stub method for this.

3. Testing logic can be simplified if Executor will be implemented with dependency injection in mind. So it relation between Executor and Subject will not be hardcoded, but configurable in runtime.

See also:

- https://semaphoreci.com/community/tutorials/mocking-in-ruby-with-minitest
- http://rubyblog.pro/2016/10/ruby-dependency-injection
- http://solnic.eu/2013/12/17/the-world-needs-another-post-about-dependency-injection-in-ruby.html
- https://medium.com/@Bakku1505/introduction-to-dependency-injection-in-ruby-dc238655a278

Hey guys! I'd like to figure out a way to test piece of code that suppose to create an instance of particular class. Here is an example:

class Subject
  def call
    ...
  end
end

class Executor
  def action
    Subject.new.call
  end
end

class ExecutorTest < Minitest::Test
  def test_subject_was_instantiated
    # How to ensure that Subject instance was created?
  end

  def test_subject_was_called
    # How to ensure that Executor send :call message to Subject instance?
  end
end

I guess, I'm missing some concept or testing pattern here. Any advice?

Posted in Anyone with experience using Quilljs?

Azeem: Just pick text editor contents using Quill API, and add it to the form payload, using onSubmit event handler. Check out this artice for example:

https://harlemsquirrel.github.io/jekyll/update/2016/12/11/rails-and-quill.html

Posted in API params validation in Rails app

I've implemented it with JSON Schema. So far, it works pretty fine. And here are few more references I found:

Posted in API params validation in Rails app

Hey guys!

I'm wondering, what could be a good and scalable way to validate if API request is well formed, before processing it. Rails has strong params for that, but I have a case when an API endpoint suppose to receive relatively complex chunk of data. About 20 params, plus couple of arrays of nested objects.

I was thinking about using JSON Schema for that. The good thing is that it will allow to do more validation for HTTP request payload. Like data types, string lengths, mandatory params, etc. More than what ActionController::Parameters can do.

But I would still appreciate to hear about alternatives, may be libraries or best practices people in Ruby world use to solve this.

Posted in Devise authentication and API

I'm working on JSON REST API for an existing Rails application. Authentication is currently implemented with Devise. What could be potential pros and cons of using cookie-based user sessions for API calls as well as web pages?
ges?

Posted in Access Rails constants from Webpack-managed JS

I'm just starting to use Webpack with Rails, and have a question. Let say there is a constant defined in Rails application. What could be a proper way to access this constant from JavaScript? Assuming this is Webpacker managed bundle.

I came up with the following solution so far, but still looking if there may be some better alternatives.

  1. yarn add rails-erb-loader
  2. Make sure .erb presents in "extensions" list in config/webpack/paths.yml
  3. Create new JS module and import the constant using ERB syntax:

    class Const {
      static get STEPS() {
        return <%= Const::Experiences::WIZARD_PAGES.to_json %>;
      }
    }
    
    export default Const;
    
  4. Use the constant like so:

    import Const from './const.js.erb';
    
    console.log(Const.STEPS);
    

ERB loader is pretty slow. So I'd love to know if there are some better approaches from performance and JS code structure perspectives.

Posted in Extend Sprockets to bundle mustache templates

Chris, you can create an episode about this. I'll share more detailed code example if you like :)

Posted in Extend Sprockets to bundle mustache templates

I solved it, LOL.

Transformer lambda was correct. Just needed to add a regexp to Rails.application.config.assets.precompile to enable *.mustache processing. Also had some troubles with overly smart Sprockets caching.

I've also replaced the labda with this class:

module Service
  class MustacheTransformer
    def self.call(input)
      Rails.logger.debug "---> Transforming Mustache: #{input[:filename]}"
      key = input[:name]
      body = Oj.dump(input[:data])
      obj = 'window.MustacheTemplates'
      { data: "#{obj} = #{obj} || {}; #{obj}['#{key}'] = #{body}" }
    end
  end
end

Seem to be working pretty neat.

Posted in Extend Sprockets to bundle mustache templates

I'm trying to include some Mustache templates into the main JS bundle. The idea is to take /app/templates/*.mustache files, generate a JSON object, and set a window-level variable, like this:

window.Templates = {
  post: "<h2>{{ title }}</h2> <p>{{ body }}</p>",
  comment: "..."
}

This way the templates will be preloaded with application.js and remain available from every Rails view.

I've read this: https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md, and now I'm trying to create a transformer for new MIME type. Like so:

module Sample
  class Application < Rails::Application

    // ...

    config.assets.configure do |env|
      env.register_mime_type 'text/mustache', extensions: ['.mustache']
      env.register_transformer 'text/mustache', 'application/javascript', -> (input) {
        { data: '// THIS IS SAMPLE JS CODE GENERATED BY A TRANSFORMER' }
      }
    end
  end
end

Adding this directive to app/assets/javascripts/application.js file suppose to inject sample output from this transformer for each file from templates directory:

//= require_tree ../../templates
//               ^ This should point to /app/templates

But I'm getting empty result after Sprockets compiles JS. Any advice?

I just want to understand how to set up Sprockets transformer. Actual JS generation is not a problem, I've added details just for the context.

I know that there are couple of gems that can do this for Mustache and Handlebars templates (like https://github.com/leshill/handlebars_assets), but I don't want to introduce another dependency here. The extension seem to be pretty simple.

I have a simple question about class naming: is it Ok to use Error as a model name in a Rails project?

This model will be used to persist Ruby exception objects for errors monitoring. Using Exception as a name for this model doesn't look like a good idea, because it will collide with stdlib's Exception.

I've started using Error for the class name, but still not sure about this. There are seem to be no class with same name anywhere in stdlib and in the gems I'm using so far. But this is still a standard suffix for most Ruby and Rails exception class names, like StandardError.

Just want to be sure I'm not missing anything, like unexpected conventio-over-configuration gotchas.

Update: Errbit uses Err for a model name with pretty much the same purposes as mine (https://github.com/errbit/errbit/blob/master/app/models/err.rb). For mysterious reason.

Posted in Dynamically create sass variables in rails

SASS files supposed to be compiled by Rails Assets Pipeline, and served as static assets. It depends on your project configuration, but in most common case, SASS-to-CSS compillation happens each time you update sources (in development), OR during deployment (in production). So there are no direct way to affect SASS variables from Rails app runtime. Technically it is possible, but will affect performance badly.

There are alternative options though. If I understand your request correctly, you need to alter a style for some element, according to the state on your server side. In this case it is possible to predefine multiple CSS classes, and use one of them in the view, that suits a condition:

Stylesheet:

.tenant-specific {
    .tenant-specific--tenant1 {
                color: red;
        }

    .tenant-specific--tenant2 {
                color: green;
        }
}

And the view:

<div class="tenant-specific tenant-specific--tenant<%= current_tenant_id %>">

In case you don't know beforehand, how much styles will be required, or if the color supposed to be dynamic, it is possible to use different approach. For example, you may generate some inline CSS:

<div style="color:<%= current_tenant_color %>">

Where current_tenant_color is a variable or some presenter method that returns hex color code for current tenant.

Third option is to add ERB code to your SASS file (just rename *.scss to *.scss.erb, and it will work as usual). Next example uses predefined values from Constants module to set CSS properties.

// application.sass.erb

.tenant-specific {
    .tenant-specific--tenant1 {
                color: <%= Constants::FIRST_TENANT_COLOR %>;
        }

    .tenant-specific--tenant2 {
                color: <%= Constants::SECOND_TENANT_COLOR %>;
        }
}

It is also possible to generate color code dynamically, instead of using constants, or get it in any different manner that suits you. ERB code within *.sass.erb files will behave pretty much like ordinary ERB code in Rails views. The only difference is that it will be executed during SASS compillation, but not each time Rails action fires.

Posted in Anyone with experience using Quilljs?

I'm using Quill for few month now. It seem like a pretty decent UI component. More lightweight and flexible than CKEditor that I was using before.

You don't need a gem to integrate it with Rails. It is possible to use Rails Assets: https://rails-assets.org/#/components/quill Or just use Yarn after upgrading to Rails 5.1. It is more straightforward way to install Quill.

Regarding question 1: Just add a custom parameter to your paths, like that:

<%= link_to 'csv1', users_path(format: "csv", set: 1) %>
<%= link_to 'csv2', users_path(format: "csv", set: 2) %>

You'll be able to receive this parameter from your controller (like an ordinary parameter: params[:set]), and use it to decide hov to populate your CSVs.

Posted in Cookies vs token for authentication

There are few options.

The first one is to embed session ID to your URLs. Some frameworks do that, but I won't recommend this approach for obvious reason of security. Another option is to add session ID to the header of each HTTP call on application level.

In other words, after client side of your application receives session token from the server, it will have to append it to each further request back to the server. Frankly, in this case you will have to implement cookie functionality by yourself. Session ID could be included to HTTP header or API params. Both options are better than embedding it to URL.

Major difference between this approach and normal cookies is that the app-level session will be terminated after user close the browser. Because you don't have a chance to persist session ID (if I understand the situation correctly).

To be honest, I won't recommend this approach either, regardless it is technically possible. Random bug in the implementation could cause major vulnerability.

Posted in Proper location for null objects

I've recently watched couple of screencasts on using Null Object Pattern: https://www.rubytapas.com/2017/01/31/two-screencasts-two-ways-eradicate-ruby-nil-values It was a good reminder about a question I've already been thinking a while ago. What could be a reasonable location within a project source tree to keep null object definitions?

Let's follow the situation from the first screencast, assuming we have a typical Rails project structure for determinancy. There is User model and related null objects called GuestUser. Should it be app/models/guest_user.rb? Or may be it is better to keep both classes together in the same file, as User and User::Guest? Or even implement base class to have a generic abstraction (like BaseUser) that will explicitly relate User with GuestUser?

The last one makes sense to me, but it doesn't feel like following Ruby way. It looks more like Java way :) Usually when I'm implementing null object, I just declare separate class. But I can't say I'm completely satisfied with that approach.

Thoughts?

PS: I really like the way URL slugs are working on this forum. /forum/proper-location-for-null-objects — neat!

@shakycode Here is stderr after rails assets:precompile. The weird thing is that I've just tried to reproduce it locally, and didn't get this error (with therubyracer gem is excluded from Gemfile).

Upd: oops sorry, that was wrong stack trace. Here is correct one:

rails aborted!
Bundler::GemRequireError: There was an error while trying to load the gem 'uglifier'.
Gem Load Error is: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.
Backtrace for gem load error is:
/var/www/app/shared/bundle/ruby/2.3.0/gems/execjs-2.7.0/lib/execjs/runtimes.rb:58:in `autodetect'
/var/www/app/shared/bundle/ruby/2.3.0/gems/execjs-2.7.0/lib/execjs.rb:5:in `<module:ExecJS>'
/var/www/app/shared/bundle/ruby/2.3.0/gems/execjs-2.7.0/lib/execjs.rb:4:in `<top (required)>'
/var/www/app/shared/bundle/ruby/2.3.0/gems/uglifier-3.0.4/lib/uglifier.rb:5:in `require'
/var/www/app/shared/bundle/ruby/2.3.0/gems/uglifier-3.0.4/lib/uglifier.rb:5:in `<top (required)>'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:91:in `require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:91:in `block (2 levels) in require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:86:in `each'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:86:in `block in require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:75:in `each'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:75:in `require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler.rb:106:in `require'
/var/www/app/releases/_/config/application.rb:5:in `<top (required)>'
/var/www/app/releases/_/Rakefile:4:in `require'
/var/www/app/releases/_/Rakefile:4:in `<top (required)>'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/rake_module.rb:28:in `load'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/rake_module.rb:28:in `load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:686:in `raw_load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:96:in `block in load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:178:in `standard_exception_handling'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:95:in `load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/rake_proxy.rb:13:in `block in run_rake_task'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:178:in `standard_exception_handling'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/rake_proxy.rb:11:in `run_rake_task'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/commands_tasks.rb:51:in `run_command!'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands.rb:18:in `<top (required)>'
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Bundler Error Backtrace:
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:94:in `rescue in block (2 levels) in require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:90:in `block (2 levels) in require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:86:in `each'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:86:in `block in require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:75:in `each'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:75:in `require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler.rb:106:in `require'
/var/www/app/releases/_/config/application.rb:5:in `<top (required)>'
/var/www/app/releases/_/Rakefile:4:in `require'
/var/www/app/releases/_/Rakefile:4:in `<top (required)>'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/rake_module.rb:28:in `load'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/rake_module.rb:28:in `load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:686:in `raw_load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:96:in `block in load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:178:in `standard_exception_handling'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:95:in `load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/rake_proxy.rb:13:in `block in run_rake_task'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:178:in `standard_exception_handling'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/rake_proxy.rb:11:in `run_rake_task'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/commands_tasks.rb:51:in `run_command!'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands.rb:18:in `<top (required)>'
bin/rails:4:in `require'
bin/rails:4:in `<main>'
ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.
/var/www/app/shared/bundle/ruby/2.3.0/gems/execjs-2.7.0/lib/execjs/runtimes.rb:58:in `autodetect'
/var/www/app/shared/bundle/ruby/2.3.0/gems/execjs-2.7.0/lib/execjs.rb:5:in `<module:ExecJS>'
/var/www/app/shared/bundle/ruby/2.3.0/gems/execjs-2.7.0/lib/execjs.rb:4:in `<top (required)>'
/var/www/app/shared/bundle/ruby/2.3.0/gems/uglifier-3.0.4/lib/uglifier.rb:5:in `require'
/var/www/app/shared/bundle/ruby/2.3.0/gems/uglifier-3.0.4/lib/uglifier.rb:5:in `<top (required)>'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:91:in `require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:91:in `block (2 levels) in require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:86:in `each'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:86:in `block in require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:75:in `each'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler/runtime.rb:75:in `require'
/home/deploy/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/bundler-1.13.6/lib/bundler.rb:106:in `require'
/var/www/app/releases/_/config/application.rb:5:in `<top (required)>'
/var/www/app/releases/_/Rakefile:4:in `require'
/var/www/app/releases/_/Rakefile:4:in `<top (required)>'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/rake_module.rb:28:in `load'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/rake_module.rb:28:in `load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:686:in `raw_load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:96:in `block in load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:178:in `standard_exception_handling'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:95:in `load_rakefile'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/rake_proxy.rb:13:in `block in run_rake_task'
/var/www/app/shared/bundle/ruby/2.3.0/gems/rake-11.3.0/lib/rake/application.rb:178:in `standard_exception_handling'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/rake_proxy.rb:11:in `run_rake_task'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands/commands_tasks.rb:51:in `run_command!'
/var/www/app/shared/bundle/ruby/2.3.0/gems/railties-5.0.1/lib/rails/commands.rb:18:in `<top (required)>'
bin/rails:4:in `require'
bin/rails:4:in `<main>'", 
    "stdout": "", 
    "stdout_lines": [], 
    "warnings": []

Posted in Can I assign dates to display a link?

As far as I understand situation, you don't need background jobs here. To display something on a web page within certain date interval, you just need to add this condition to your view:

<% if (start_date..end_date).cover? DateTime.now %>
  <%= link_to 'Your link', '#' %>
<% end %>

Identical condition check could be implemented within a helper or presenter to keep view cleaner.

The only question left is about therubyracer. Documentation says, it is possible to use alternative JS runtime, and keep gem 'therubyracer' line commented in Gemfile, as it is by default. But I keep getting execjs exception saying JS runtime not found, regardless nodejs is installed.

Tend to agree about keeping these gems in production after some experiments. Regardless asset pipeline is not directly used in production (in case frontend assets are precompiled, as they should be), removing these gems could cause less stable behavior in some situations. So may be saving some memory by removing them from Gemfile — which way my original motivation — is not worth it.

And I agree, RAILS_ENV was not the right choice in the original post. In the end I was trying to use different conditions to exclude asset pipeline gems from gemfile.

logo Created with Sketch.

Ruby on Rails tutorials, guides, and screencasts for web developers learning Ruby, Rails, Javascript, Turbolinks, Stimulus.js, Vue.js, and more. Icons by Icons8

© 2020 GoRails, LLC. All rights reserved.