Skip to content

TILs

Fueling Curiosity, One Insight at a Time

At Codemancers, we believe every day is an opportunity to grow. This section is where our team shares bite-sized discoveries, technical breakthroughs and fascinating nuggets of wisdom we've stumbled upon in our work.

  1. Optimizing Validations with Caching

    When we have a Member model where each user has a specific number of coins. When performing operations like capturing or deducting coins, we want to validate:

    1. The user exists or not.
    2. The user has enough coins to complete the operation.
    The basic validation will look like this (with dry-validation gem)


    Ruby

    class CoinsValidator < Dry::Validation::Contract
      params do
        required(:user).filled(:integer)
        required(:coins).filled(:integer, gt?: 0)
      end
    
      rule(:user) do
        member = Member.find_by(unique_id: value) // querying database for member details
        key.failure("does not exist") if member.nil?
      end
    
      rule(:coins, :user) do
        member = Member.find_by(unique_id: values[:user]) // querying database for member details
        if member && member.coins < values[:coins]
          key(:coins).failure("are insufficient")
        end
      end
    end


    But the problem here is, We query the database twice to fetch the same Member object once in the :user rule and again in the :coins rule. This redundant querying increases database load and slows down validation, especially in high-traffic applications.

    To avoid redundant queries, we can cache the Member object using the values hash provided by the dry-validation gem. The values hash allows us to store intermediate results, making them accessible to other validation rules.
    The optimized code looks like this:


    Ruby

    class CoinsValidator < Dry::Validation::Contract
      params do
        required(:user).filled(:integer)
        required(:coins).filled(:integer, gt?: 0)
      end
    
      rule(:user) do
        values[:member] = Member.find_by(unique_id: value)  // Caching the member object
        key.failure("does not exist") if values[:member].nil?
      end
    
      rule(:coins) do
        member = values[:member]  // accessing the cached Member from values[:member] rather than querying the database again.
        if member && member.coins < value
          key.failure("are insufficient")
        end
      end
    end


    #ruby on rails
  2. Amber Srivastava
    Mocking Timers for Controlled Testing

    If your code uses setTimeout or setInterval, Jest's timer mocking lets you fast-forward time.
    Example: Using jest.useFakeTimers()


    Code

    jest.useFakeTimers();
    
    test("delayed greeting is sent after 3 seconds", () => {
      const callback = jest.fn();
    
      setTimeout(() => callback("Hello!"), 3000);
      jest.runAllTimers();
    
      expect(callback).toHaveBeenCalledWith("Hello!");
    });


    #CCT1JMA0Z # testing #jest
  3. In Ruby, attr_reader and attr_accessor are used to create getter and setter methods for class attributes. These are part of a group of methods (attr_* methods) that make it easier to create getter and setter methods for class attributes.

    attr_reader: Creates a getter method, allowing read-only access to an instance variable.

    Ruby

    class Person
      attr_reader :name
    
      def initialize(name)
        @name = name
      end
    end
    
    person = Person.new("Sibtain")
    puts person.name  # Outputs: Sibtain


    attr_accessor: Creates both getter and setter methods, allowing read and write access to an instance variable.

    Ruby

    class Person
      attr_accessor :name
    
      def initialize(name)
        @name = name
      end
    end
    
    person = Person.new("Sibtain")
    puts person.name  # Outputs: Sibtain
    
    person.name = "John"
    puts person.name  # Outputs: John


    Furthermore, using attr_reader and attr_accessor promotes encapsulation by controlling how the attributes of a class are accessed and modified.

    #ruby #rubyonrails
  4. Amber Srivastava
    ------------------------- useFieldArray hook in react-hook-form -------------------------
    The useFieldArray hook is part of react-hook-form and is used for handling dynamic fields in forms, such as arrays of inputs that can be added or removed dynamically.
    For example, if you have a list of items (like questions, tasks, or other fields) that can be added, removed, or reordered during form submission, useFieldArray is the ideal solution to manage that dynamic behaviour without manually managing the state of each individual field.

    How useFieldArray works:
    • fields: An array of objects, where each object represents a field in the array.
    • append: A function to add new fields to the array.
    • remove: A function to remove fields from the array.
    • update: A function to update an individual field in the array.
    Example:-

    JavaScript

    import { useForm, useFieldArray } from "react-hook-form";
    
    function DynamicForm() {
      const { register, control, handleSubmit, formState: { errors } } = useForm({
        defaultValues: {
          questions: [{ question: "" }],
        },
      });
    
      // UseFieldArray to handle the questions array dynamically
      const { fields, append, remove } = useFieldArray({
        control,
        name: "questions",  // Name of the array in the form's state
      });
    
      const onSubmit = (data: any) => {
        console.log(data); // Submitting form data with dynamic fields
      };
    
      return (
        <form onSubmit={handleSubmit(onSubmit)}>
          {fields.map((item, index) => (
            <div key={item.id}>
              <input
                {...register(`questions.${index}.question`)}  // Register each question dynamically
                defaultValue={item.question}  // Set default value for each item in the array
              />
              {errors.questions?.[index]?.question && (
                <span>{errors.questions[index]?.question.message}</span>
              )}
              <button type="button" onClick={() => remove(index)}>Remove</button>
            </div>
          ))}
          
          <button type="button" onClick={() => append({ question: "" })}>
            Add Question
          </button>
    
          <button type="submit">Submit</button>
        </form>
      );
    }


    Key Points:
    1. name: The name prop in useFieldArray points to the field array in your form state (in this case, questions).
    2. fields: This array holds the current data of the dynamic fields. Each item corresponds to a field in the form array.
    3. append: This method is used to add a new item to the array. You can pass the new data for the item when appending.
    4. remove: This method is used to remove an item by its index.
    When to use useFieldArray:
    • Dynamic Forms: When you have a form where the number of fields can change over time, such as adding/removing questions, tasks, team members, etc.
    • Nested Fields: When you have an array of objects (e.g., an array of questions, where each question has a title and description).
    Advantages:
    • It reduces the need for manual state management when adding/removing fields.
    • It integrates seamlessly with react-hook-form to handle validations and form submission.
    • It improves performance by avoiding unnecessary re-renders when fields are added or removed.
    #CCT1JMA0Z #useForm #react-hook-form
  5. Slack's chat.deleteScheduledMessage API allows you to delete messages that were scheduled using chat.scheduleMessage but have not yet been sent.

    JavaScript

    const response = await slackClient.chat.deleteScheduledMessage({
      channel: channelId, {// ID of the channel where the message was scheduled }
      scheduled_message_id: scheduledMessageId, {// ID of the scheduled message to delete}
    });


    #slack #api
  6. How to write tests for external APIs?

    This can be achieved using the gem "webmock".

    Using webmock we will similate the external API call and use mockdata as the response to the APIs.

    step1: install the gem "webmock"
    step2: add the following lines to rails helper.

    Ruby

    require 'webmock/webmock_api.rb'
    
    config.before(:each) do
        stub_request(:any, /domain_name/).to_rack(WebmockApi
    end


    step3: create mock data responses for the APIs inside fixtures folder in spec.

    member_api_success.json

    Ruby

    # spec/fixtures/member_api_success.json
    [
      {
        "id": 1,
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "123-456-7890",
        "membership_type": "Gold",
        "status": "active"
      },
      {
        "id": 2,
        "name": "Jane Smith",
        "email": "[email protected]",
        "phone": "987-654-3210",
        "membership_type": "Silver",
        "status": "inactive"
      }
    ]


    step4: inside spec create webmock file. Inside webmock create webmock_api.rb file. In this file we will simulate the responses for the API using mock data we have created.

    Ruby

    class WebmockApi
      SPEC_FIXTURES_PATH = 'spec/fixtures'.freeze
      MEMBERS_SUCCESS = File.read("#{SPEC_FIXTURES_PATH}/members_api_success.json").freeze
      POSTS_SUCCESS = File.read("#{SPEC_FIXTURES_PATH}/posts_api_success.json").freeze
      ERROR = File.read("#{SPEC_FIXTURES_PATH}/error.json").freeze
    
      def self.call(env)
        new.call(env)
      end
    
      def call(env)
        action = env['REQUEST_METHOD']
        path = env['PATH_INFO']
        params = env['QUERY_STRING']
    
        case path
        when '/external_members_api_path'
          params.include?('test_user') ? [ 200, {}, [ MEMBERS_SUCCESS ] ] : [ 500, {}, [ ERROR ] ]
        when '/external_post_api_path'
          params.include?('new_post') ? [ 200, {}, [ POSTS_SUCCESS ] ] : [ 500, {}, [ ERROR ] ]
        end
      end
    end


    step5: write the test cases for the APIs in requests folder.

    Ruby

    require 'rails_helper'
    require 'webmock/rspec'
    
    RSpec.describe "Members", type: :request do
      describe "GET /members_search_path" do
        context "when the members API call is successful" do
          it "returns member details from the API" do
            get "/external_members_api_path", params: { member: "test_user" }
    
            expect(response).to have_http_status(:ok)
            user = response.parsed_body["member"]
            expect(["id"]).to eq("123456")
            expect(user["name"]).to eq("abc")
          end
        end
        
        context "when the members API call is successful" do
          it "returns error message from the API" do
            get "/external_members_api_path", params: { member: "unknown_user" }
    
            expect(response).to have_http_status(:ok)
            expect(response).to have_http_status(:internal_server_error)
            error = response.parsed_body["error"]
            expect(error).to eq("Something went wrong")
          end
        end
      end
    end


    #ruby on rails
  7. Amber Srivastava
    SEND SLACK MESSAGE AS A THREAD

    To send a message as a thread in Slack using the Slack API, we can use the chat.postMessage method with the thread_ts parameter. This parameter specifies the timestamp (ts) of the parent message you want to reply to, creating a thread.

    Here’s how to send a threaded message:
    1. Get the ts (timestamp) of the Parent Message
    • If you’re replying to a specific message, you’ll need its ts value. You can retrieve it by fetching messages in the channel, or from the response of a previously sent message.
    2. Send a Threaded Message Using thread_ts
    • Use thread_ts in the chat.postMessage payload to post your message as a reply in the thread.
    Example:-

    TypeScript

    import { WebClient } from "@slack/web-api";
    
    const client = new WebClient("YOUR_SLACK_BOT_TOKEN");
    
    async function sendThreadedMessage(channel: string, parent_ts: string, message: string) {
      try {
        // Post a new message as a reply in the thread
        const response = await client.chat.postMessage({
          channel,
          text: message,
          thread_ts: parent_ts, // This makes it a threaded message
        });
      } catch (error) {
        console.error("Error sending threaded message:", error);
      }
    }
    
    // Usage example
    sendThreadedMessage("C123456789", "1688852910.123456", "This is a reply in the thread.");


    If we don't have any parent message then,we can first send a message and then use its ts as the thread_ts for replies:

    TypeScript

    async function sendMessageWithThread(channel: string, message: string, replyMessage: string) {
      try {
        // Send the parent message
        const parentMessage = await client.chat.postMessage({
          channel,
          text: message,
        });
    
        // Reply to the message in a thread
        await client.chat.postMessage({
          channel,
          text: replyMessage,
          thread_ts: parentMessage.ts,
        });
      } catch (error) {
        console.error("Error sending messages:", error);
      }
    }
    
    // Usage example
    sendMessageWithThread("C123456789", "This is the main message", "This is a reply in the thread.");


    #C04A9DMK81E #slack #slackapi #thread
  8. When working with Stimulus, it's common to dynamically update DOM elements. While string interpolation works, using HTML <template> elements is a cleaner and more maintainable approach.
    #CU6U0R822 #stimulus #templates

    String interpolation

    JavaScript

    // In your stimulus controller 
    updateList() {
      this.listTarget.innerHTML = `
        <div class="flex gap-2">
          <span>${this.name}</span>
          <button>Delete</button>
        </div>
      `
    }


    HTML Templates

    JavaScript

    // In your view 
    <template data-list-target="template">
      <div class="flex gap-2">
        <span data-placeholder="name"></span>
        <button>Delete</button>
      </div>
    </template>
    
    // In your Stimulus controller
    updateList() {
      const template = this.templateTarget.content.cloneNode(true)
      template.querySelector('[data-placeholder="name"]').textContent = this.name
      this.listTarget.appendChild(template)
    }

  9. In a Rails application, we can provide different views and behaviours based on the type of device accessing our application. One of the ways to achieve this is by using the set_variant method along with mobile-specific templates like index.html+mobile

    1. First, determine if the request is coming from a mobile device and set variant in the controller.

    Ruby

    def set_variant
        browser = Browser.new(request.user_agent)
    
        if browser.device.mobile?
          request.variant = :mobile
        else
          request.variant = :desktop
        end
      end


    2. Now, create mobile-specific views. For example, if we have an index.html.erb view, we can create a mobile-specific version by adding +mobile to the filename.

    Ruby

    app/views/orders/index.html.erb
    app/views/orders/index.html+mobile.erb


    With the variant set, Rails will automatically choose the correct view.

    #rubyonrails
  10. useFetch is a Nuxt composable used to fetch data in a server-side or client-side context, ensuring data is fetched before rendering the component. It is primarily used for making HTTP requests and providing a reactive way of managing the fetched data.

    useAsyncData is very similar to useFetch, but it allows for fetching data asynchronously, without blocking the server-side rendering process. It's useful when you want to fetch data in a non-blocking way, enabling the page to render without waiting for the data.

    Key Difference:
    • useFetch fetches data synchronously during SSR, blocking the rendering process until the data is available.
    • useAsyncData fetches data asynchronously, allowing the page to render without waiting for the data.
    #fetch #nuxt #useFetch #useAsyncData
  11. In Rails, forms can be created in two ways: with a URL (using form_with url: ...) or with a model (using form_with model: ...).
    But, when to use which?

    Form with URL (form_with url: ...)
    This form is used when you specify a URL directly and typically use it for non-resourceful actions or when you don’t have a specific model associated with the form.

    Ruby

    <%= form_with url: some_path, method: :post do |form| %>
      <%= form.text_field :some_field %>
      <%= form.submit "Submit" %>
    <% end %>


    The form_with_url is suitable for forms that don't map directly to a model, like search forms, login forms, or other custom actions etc.

    Form with Model (form_with model: ...)
    This form is bound to an instance of a model, allowing Rails to automatically set the form action (URL) and method (POST or PATCH) based on whether the model is a new record or an existing one.

    Ruby

    <%= form_with model: @record do |form| %>
      <%= form.text_field :name %>
      <%= form.submit %>
    <% end %>


    Rails determines the correct URL and HTTP method based on the record's state:
    • New Record: If @record.new_record? is true, Rails generates a POST request to the model’s create route.
    • Existing Record: If @record.persisted? is true, Rails generates a PATCH request to update the model’s update route.
    The form_with_model is suitable for forms that directly interact with a model, such as forms for creating or editing a resource (like User, Post, etc.).

    #ruby on rails
  12. Adithya HebbarSystem Analyst
    To update a user’s password in AWS Cognito and set it as permanent, we can use the AWS CLI with the following admin command:

    Code

    aws cognito-idp admin-set-user-password \\
        --user-pool-id <pool-id> \\
        --username <cognito-username> \\
        --password <password> \\
        --permanent


    #auth #aws #cognito
  13. Anujeet SwainSystem Analyst
    Query Defaults in React-Query
    Any option we pass to React-Query besides the query key can have its default values and can be set by following ways:
    • Passing defaultOptions object to query client as global defaults.

    Code

    const queryClient = new QueryClient(
     defaultOptions: {
      queries: {
       staleTime: 10 * 1000
      }
     }
    }


    • Setting default options for subset of queries using Fuzzy Matching by setQueryDefaults method

    Code

    queryClient.setQueryDefaults(
     ['todos','list'],
     {staleTime: 10 * 1000}
    ) 
    //This sets default stale time of 10secs for all the matched queries having keys 'todos' and 'list'


    • Setting default options within useQuery for fine grain control over specific query.

    Code

    useQuery({
     queryKey: ['todo'],
     staleTime: 10 * 1000,
    });


    Each method takes precedence over the others in this order.
    #react-query #customizing-defaults
  14. Amber Srivastava
    Promise.allSettled() :

    Purpose: Executes multiple promises and waits for all of them to settle (either resolve or reject).
    Returns: A promise that resolves with an array of objects. Each object has:
    • status: Either "fulfilled" or "rejected".
    • value: The resolved value (if fulfilled) or reason: The rejection reason (if rejected).
    Example

    JavaScript

    const promises = [
      Promise.resolve(1),
      Promise.reject('Error'),
      Promise.resolve(2),
    ];
    
    Promise.allSettled(promises).then((results) => {
      results.forEach((result) => {
        if (result.status === 'fulfilled') {
          console.log('Result:', result.value);
        } else {
          console.log('Error:', result.reason);
        }
      });
    });
    
    Output :
    Result: 1
    Error: Error
    Result: 2


    #CCT1JMA0Z
  15. Git Rebase:

    It is similar to merge, but the difference is merging brings the changes from the main branch into your current branch by creating a merge commit that combines the histories of both branches.

    Rebasing applies your branch’s commits on top of the main branch, making it look as if your work was started from the latest main commit.

    • First pull the latest changes of main branch.
    • Then navigate to the working branch
    • run the command git rebase main
    • if there any conflicts resolve and continue rebase.
    • After rebasing completely, force push the changes.
    #git
  16. To avoid N+1 queries in Rails, you can use the .includes method to eager-load associated records, which reduces the number of database calls.

    Suppose you have two models: Order and Item, where an Order has many Items. Without eager-loading, querying each order’s items individually would lead to N+1 queries.


    Ruby

    orders = Order.all 
    orders.each do |order| 
      puts order.items # Each order triggers a separate query for items
    end


    By using .includes, Rails will fetch all associated items in a single additional query:


    Ruby

    orders = Order.includes(:items)
    orders.each do |order|
      puts order.items # No extra query is triggered here
    end


    This approach loads Order records in one query and then fetches all associated items in a second query, avoiding the N+1 issue.

    #CU6U0R822
  17. before_action runs a specified method before the controller action. It’s useful for tasks that need to happen before executing the main action, such as authentication, setting up a resource, or ensuring permissions.


    after_action runs a specified method after the controller action has executed. It’s useful for tasks that need to happen after the response is rendered, such as logging activity, tracking metrics, or cleaning up resources.



    Ruby

    class PointsController < ApplicationController
      before_action :set_user_points, only: [:show, :redeem]
      after_action :update_points_history, only: [:redeem]
    
      def show
        # Show points balance
      end
    
      def redeem
        # Redeem points logic
      end
    
      private
    
      def set_user_points
        @points = current_user.points
      end
    
      def update_points_history
        # Log the points redemption action
      end
    end



    In the above example set_user_points will execute before the controller actions show and redeem. update_points_history will execute after the redeem action.

    #CU6U0R822
  18. onDelete: Cascade in Prisma automatically deletes the child records when a parent record is deleted.

    Code

    model User {
      id    Int     @id @default(autoincrement())
      posts Post[]
    }
    
    model Post {
      id     Int   @id @default(autoincrement())
      userId Int
      user   User  @relation(fields: [userId], references: [id], onDelete: Cascade)
    }


    Now when you delete a user:

    Code

    await prisma.user.delete({ where: { id: 123 } })


    All their posts are automatically deleted too!

    #prisma #schema
  19. Combining Commits with Git Squash
    Squashing commits allows us to combine multiple related commits into a single one, helping to keep the commit history clean.

    Let's say if we want to combine 3 separate commits which are related to same thing into one clean commit,

    1. Checkout the branch

    Code

    git checkout branch-name


    2. Run the following command

    Code

    git rebase -i HEAD~3


    3. Modify the rebase file. Git will open a text window with last 3 commits

    Code

    pick 7f9d4bf first commit
    pick 3f8e810 second commit
    pick ec48d74 third commit


    • pick means to keep the commit as is.
    • To squash the second and third commits into the first one, change pick to squash / s for those commits.
    4. After we save and exit, another text editor will pop-up with commit messages

    Code

    # This is a combination of 3 commits.
    # The first commit message:
    
    fix for bug
    
    # Commit message for #2:
    Updated this
    
    # Commit message for #3:
    Added comments & updated README


    Simply saving this will result in a single commit with a commit message that is a concatination of all 3 messages.
    We can choose which one we want, or we can create a new message entirely.
    5. Complete the Rebase
    After saving, we now have a single commit representing the previous three.

    #git #rebase
  20. Concerns
    A Rails concern is just a plain Ruby module that extends the ActiveSupport::Concern module provided by Rails.
    They help in organizing and reusing code across controllers and models by extracting common functionality into modules.


    There are 2 main blocks in a concern

    1. included

    1. Any code inside this block is evaluated in the context of the including class.
    2. if sample class includes a concern, anything inside the included block will be \tevaluated as if it was written inside the sample class.
    3. This block can be used to define Rails macros like validations, associations, and scopes.
    4. Any method you create here becomes instance methods of the including class.
    2. class_methods

    1. Any methods that you add here become class methods on the including class.
    Example:

    typically concerns are located in app/controllers/concerns or app/models/concerns

    Ruby

    module ExampleConcern
      extend ActiveSupport::Concern
    
      included do
        # any code that you want inside the class
        # that includes this concern
      end
    
      class_methods do
        # methods that you want to create as
        # class methods on the including class
      end
    end


    Including this concern in a controller:


    Ruby

    class SomeController < ApplicationController
     include ExampleConcern
    end


    #RubyOnRails #concerns #CU6U0R822

Showing page 7 of 42

Your competitors are already using AI. The question is how fast you want to unlock the value.

  • Don't know where to start?

    AI is everywhere but it's unclear which investments will actually move your metrics and which are expensive experiments.

  • Your data isn't ready

    Most AI projects fail at the data layer. Pipelines, quality, access all need work before LLMs can deliver value.

  • Internal teams are stretched

    Your engineers are shipping product. They don't have capacity to also become AI specialists with production-grade experience.

  • Legacy systems block everything

    Aging, undocumented codebases make AI integration slow, risky, and expensive. They need to move first.

Don't worry. We've got you covered.

Start with the audit.

Book a free discovery call