Rails eval Explained with Examples and Best Practices

Understanding eval in Ruby on Rails: Risks, Use Cases, and Best Practices
Ruby Eval in Rails

📘 Understanding eval in Ruby: What It Is and How It Works

In Ruby, the eval method is used to evaluate a string as Ruby code at runtime. This allows developers to write highly dynamic and flexible code that can adapt based on conditions during execution.

For example, you can pass a string like \"2 + 2\" to eval, and it will return the result of that expression—4. This makes eval incredibly powerful, but also potentially dangerous when used with untrusted input.

Common use cases include:

  • Evaluating dynamic formulas in admin panels
  • Building internal scripting tools
  • Constructing DSLs (Domain-Specific Languages)
  • Debugging and meta-programming tasks

However, eval should be used with extreme caution. Executing arbitrary strings as code can lead to serious security vulnerabilities such as remote code execution, data leaks, and file system access.

Warning: Never use eval on raw user input. Always validate, sanitize, or use safer alternatives like send, case/when, or parsing libraries such as Dentaku.

🚫 Why You Should Avoid eval in Rails Apps

While eval can dynamically execute Ruby code, it poses significant risks—especially in web applications like those built with Rails. Using eval on untrusted input opens up your application to severe security vulnerabilities.

Common dangers include:

  • Remote Code Execution (RCE): An attacker could inject and run arbitrary Ruby code.
  • Access to Sensitive Data: eval can access environment variables, database credentials, and more.
  • File System Access: Malicious input can delete or modify files on the server.
  • Hard to Debug: Debugging eval-related bugs is extremely difficult due to dynamic execution.
  • Slower Performance: Code evaluated with eval bypasses some Ruby optimizations.

Even in trusted admin panels, using eval should be avoided unless absolutely necessary—and even then, only with rigorous input validation and sandboxing techniques.

Developer Tip: Most use cases for eval can be replaced with safer alternatives like send, case statements, or expression parsers such as Dentaku.

🔐 Security Risks of Using eval in Rails

The eval method can be incredibly dangerous in a Rails application if not used with absolute caution. When it executes user-supplied input, it essentially gives the user the ability to run arbitrary Ruby code on your server—turning a small mistake into a critical vulnerability.

Common security risks of using eval include:

  • Remote Code Execution (RCE): Attackers can inject and execute malicious Ruby code.
  • Data Breaches: Sensitive data like user records, tokens, or credentials can be accessed and exfiltrated.
  • Privilege Escalation: If roles are improperly checked, an attacker might gain admin access through code injection.
  • File System Attacks: Attackers can read, write, or delete files using Ruby I/O methods like File.open or File.delete.
  • DoS (Denial of Service): Infinite loops or heavy operations inside eval can crash your app or exhaust resources.

Even if input seems safe (like mathematical formulas), attackers can sneak in harmful code using Ruby methods. There is no reliable way to sanitize arbitrary Ruby strings without removing all flexibility.

⚠️ Always Avoid: eval(params[:user_input]) or anything similar. Never trust user input—even from internal users—unless strictly validated.

✅ Safe Alternatives to eval in Rails

Instead of using eval to dynamically run code, Ruby and Rails provide safer and more structured alternatives. These options allow dynamic behavior without the security risks.

1. send and public_send

Use send to call methods dynamically. public_send is safer—it won’t call private methods.


        class User
          def greet  
          "Hello!"
          end
        end
        method = "greet"
        user = User.new
        puts user.public_send(method)  # => "Hello!"

2. define_method for Dynamic Methods

Dynamically define methods at runtime with proper scoping and control.


        class MathEngine
          [:add, :subtract].each do |operation|
            define_method(operation) do |a, b|
              operation == :add ? a + b : a - b
            end
          end
        end
        
        m = MathEngine.new
        puts m.add(5, 3)      # => 8
        puts m.subtract(5, 3) # => 2
        

3. case/when Dispatching

Use case for controlling allowed operations instead of open-ended string evaluation.


        def run_operation(op, a, b)
          case op
          when "add" then a + b
          when "sub" then a - b
          else "Unknown"
          end
        end
        
        puts run_operation("add", 2, 3)  # => 5
        

4. Gems like Dentaku

Dentaku is a safe, Ruby-based expression parser. It evaluates math and logic without running Ruby code.


        calculator = Dentaku::Calculator.new
        calculator.evaluate("10 * tax_rate", tax_rate: 0.15)  # => 1.5
        
Best Practice: Always choose structure over flexibility. Avoid letting users inject raw code and instead define clear rules or safe mappings for dynamic behavior.

🌍 Real-World Example: Refactoring Code That Uses eval

Imagine you built a reporting feature in an internal admin dashboard where users enter formulas (e.g., "revenue - cost") to calculate profit. A naive solution might use eval like this:


    def calculate_formula(formula, context)
      context.each { |k, v| eval("#{k} = #{v}") }
      eval(formula)
    end
    
    calculate_formula("revenue - cost", { revenue: 1000, cost: 300 })
    # => 700
      

⚠️ This approach is risky. If someone inputs `system('rm -rf /')` as the formula, the server could be destroyed.

🔒 Refactored Using Dentaku – a safe, embedded expression evaluator:


    calculator = Dentaku::Calculator.new
    calculator.evaluate("revenue - cost", revenue: 1000, cost: 300)
    # => 700
      
Why This Is Better: Dentaku ensures only math and logic expressions are allowed—no Ruby methods, no file access, no system calls—keeping your app secure while still flexible.

🧩 How eval Interacts with Rails Controllers and Models

In a Rails application, eval can execute dynamic Ruby code within the context of controllers, models, or views. This makes it powerful—but extremely dangerous when misused.

📌 Example: Eval in a Controller


    class ReportsController < ApplicationController
      def custom
        formula = params[:formula]  # e.g., "Order.sum(:amount)"
        result = eval(formula)
        render plain: result
      end
    end
      

⚠️ This allows dangerous methods like eval("User.destroy_all") or eval("`rm -rf /`") to be run from the URL!

📌 Example: Eval in a Model


    class Calculator < ApplicationRecord
      def run(expression)
        eval(expression)
      end
    end
    
    calc = Calculator.new
    calc.run("2 + 2")  # => 4
      

Although it works, this exposes model logic to arbitrary code execution. An attacker could inject destructive commands if input isn’t controlled.

Safe Advice: Use strict whitelisting, case-based handling, or safe parsers like Dentaku when processing user-provided expressions in Rails. Never use eval in production controllers or models.

📊 Benchmarking: eval vs. send vs. method_missing in Rails

When building dynamic behavior in Ruby or Rails, developers often choose between eval, send, or method_missing. Each has different trade-offs in terms of performance, safety, and readability.

🧪 Benchmark Example


    require 'benchmark'
    
    class Demo
      def greet; "Hello"; end
      def method_missing(method, *args); greet if method == :dynamic_greet; end
    end
    
    demo = Demo.new
    n = 100_000
    
    Benchmark.bm do |x|
      x.report("eval:")          { n.times { eval("demo.greet") } }
      x.report("send:")          { n.times { demo.send(:greet) } }
      x.report("method_missing:"){ n.times { demo.dynamic_greet } }
    end
      

⚖️ Results (Approximate)

  • send: ✅ Fast and safe (best performance)
  • method_missing: ⚠️ Slightly slower but flexible
  • eval: ❌ Significantly slower and unsafe

🏁 Summary Table

TechniquePerformanceSafetyUse Case
eval❌ Slow❌ DangerousAvoid unless sandboxed
send✅ Fast✅ Safe (if method list is controlled)Calling known dynamic methods
method_missing⚠️ Moderate⚠️ Prone to bugs if not handled carefullyMetaprogramming DSLs
Pro Tip: In most Rails applications, send gives you all the power you need—without the risk. Use method_missing sparingly, and avoid eval unless there’s no alternative.

🔄 Safe String Interpolation vs. eval in Dynamic Code

When you need to generate dynamic content, calculations, or method names in Ruby or Rails, it might seem tempting to use eval. However, in most cases, safe string interpolation or method dispatching will do the job—with none of the security concerns.

❌ Using eval (Unsafe)


    user_input = "2 + 3"
    result = eval(user_input)  # => 5
      

This opens the door to malicious input like \`rm -rf /\` or accessing Rails internals—dangerous in any environment!

✅ Safe Interpolation (Preferred)


    user = "John"
    message = "Hello, #{user}!"  # => "Hello, John!"
      

Interpolation evaluates values already present in memory or variables—no code execution involved.

🏷️ Use Case: Dynamic SQL with Placeholders (Rails Way)


    min_price = 100
    Product.where("price > ?", min_price)
      

Avoid eval("Product.where(\"price > #{user_input}\")"). Instead, use ActiveRecord’s built-in safety with placeholders to prevent SQL injection.

Summary: Use string interpolation for building safe output and Rails/ActiveRecord mechanisms for query construction. eval should be the last resort and always sandboxed if used.

🔬 Behind the Scenes: How eval Works in Ruby

Ruby’s eval method takes a string and executes it as Ruby code. But internally, it’s much more than just “running a string”.

🛠️ What Happens Internally

  1. Parsing: The string is parsed into an Abstract Syntax Tree (AST) using Ruby’s built-in parser.
  2. Compilation: The AST is converted into bytecode that the Ruby virtual machine (YARV) can execute.
  3. Execution: The bytecode is executed in the current scope and binding context, which can access local and instance variables.

🧠 Example (With Explanation)


    x = 10
    eval("x + 5")  # => 15
      

Here, eval sees the current binding (where x exists), parses the string "x + 5", compiles it, and executes it in that same scope.

📦 Custom Bindings


    context = binding
    x = 100
    eval("x * 2", context)  # => 200
      

You can pass a binding object to control the scope where eval runs. This makes it more flexible—but still risky.

Important Note: Because eval parses and compiles at runtime, it’s slower and more dangerous than regular code. Avoid it unless you’re building a trusted DSL or debugging tool.

🔍 Examples of eval in Different Ruby/Rails Scenarios

  1. eval("2 + 2") → 4
  2. x = 10; eval("x * 3") → 30
  3. eval("'Hello'.upcase") → "HELLO"
  4. eval("[1,2,3].map { |n| n * 2 }") → [2,4,6]
  5. eval("Time.now.year") → current year
  6. eval("Rails.env") → environment like "development"
  7. eval("params[:id]") → dangerous if params are unsafe
  8. eval("'a' * 5") → "aaaaa"
  9. eval("x = 100; x + 50") → 150 but pollutes scope
  10. eval("[1, 2, 3].reduce(:+)") → 6
  11. eval("`ls`") → ❌ executes system command
  12. eval("Math.sqrt(144)") → 12.0
  13. eval("'#{2 + 2}'") → "4"
  14. eval("'#{Rails.root}'") → expands root path
  15. eval("{ a: 1, b: 2 }[:a]") → 1
  16. eval("10.times.map { |i| i * i }") → array of squares
Note: These examples show both the flexibility and danger of eval. Use them only in trusted contexts, and never expose eval to user input.

🧠 Interview Questions and Answers on eval in Ruby/Rails

  1. Q1: What is eval in Ruby?
    A: eval is a method that evaluates a string as Ruby code at runtime.
    eval("2 + 2") returns 4.
  2. Q2: Why is using eval considered dangerous?
    A: It executes arbitrary code, which can lead to security vulnerabilities like remote code execution (RCE).
    Example: eval(params[:code]) can let a user run anything on your server.
  3. Q3: Can you give a real-world misuse of eval in Rails?
    A: Evaluating user-generated formulas in a controller using eval.
    eval(params[:formula]) is dangerous—users can run system commands.
  4. Q4: What are safer alternatives to eval?
    A: Use send, public_send, define_method, or parsing gems like Dentaku.
    Example: user.send(:name) instead of eval("user.name").
  5. Q5: How does send differ from eval?
    A: send calls an existing method by symbol, while eval interprets any code.
    user.send(:email) is safer and more performant.
  6. Q6: Is eval ever acceptable to use?
    A: Only in controlled environments like internal developer tools or sandboxes—never with user input.
  7. Q7: How do you protect against eval-based attacks?
    A: Never use eval on user input. Use strong parameter validation, whitelisted operations, and gems like Dentaku for expression parsing.
  8. Q8: What’s a safe way to evaluate math formulas from user input?
    A: Use the Dentaku gem:
    calculator.evaluate("price * qty", price: 10, qty: 5) safely returns 50.
  9. Q9: Can eval affect performance?
    A: Yes, eval is slower than method dispatch like send because it parses and compiles code at runtime.
  10. Q10: How do you refactor existing eval usage?
    A: Identify the purpose of dynamic behavior. Replace with send, case-based routing, or metaprogramming methods like define_method.
    Example:
    
        # BAD
        eval("user.#{param}")
        
        # GOOD
        if %w[name email].include?(param)
          user.public_send(param)
        end
              
Note: In interviews, always emphasize security awareness and demonstrate how you’d replace eval with structured, predictable logic.

✅ Best Practices: Using or Avoiding eval in Ruby/Rails

While eval gives dynamic capabilities in Ruby, its use comes with significant risks. Below are key practices to follow when dealing with dynamic evaluation in any Ruby or Rails codebase:

  • Avoid eval with User Input: Never use eval to execute code that includes user-provided values. This is the most common source of vulnerabilities.
  • Use send or public_send: For calling methods dynamically, these are far safer and more efficient than eval.
  • Use case or if for Dynamic Logic: Handle known operations using whitelisted logic instead of evaluating strings.
  • Whitelist Inputs: If dynamic behavior is required, strictly whitelist allowed values or operations before processing.
  • Use a Safe Expression Parser: Use gems like Dentaku for user-defined formulas or simple math evaluation.
  • Don’t use eval in Controllers or Models: Instead, move dynamic logic into service objects or use Ruby’s metaprogramming tools responsibly.
  • Use define_method for Dynamic Method Creation: If you must create methods dynamically, use define_method inside classes or modules.
  • Isolate with Binding (if you must use eval): Always provide a binding object to contain scope and reduce exposure.
  • Log and Audit Any eval Usage: If eval must exist, log usage, inputs, and context, and periodically audit the code.
  • Benchmark Before Use: eval is slow. Always compare against send, method_missing, or lambdas for performance-critical paths.
Final Advice: Treat eval like a loaded weapon—use it only when absolutely necessary, in trusted environments, and with strict input controls. Always prefer structured alternatives.

📚 External Resources on eval in Ruby/Rails

✅ Always favor safe metaprogramming techniques like send, public_send, or define_method in production-grade Rails apps.

6 thoughts on “Rails eval Explained with Examples and Best Practices”

Comments are closed.

Scroll to Top