mercredi 6 décembre 2023

Filtering Users with Associated Records by Specific Date in Rails

I'm facing a challenge in a Ruby on Rails application where I need to filter users along with their associated records (Program and Learning), based on a specific date range, but only include those associated records that fall within the given date range.

Models I have the following models:


class User < ApplicationRecord
  has_many :programs
  has_many :learnings
end

class Program < ApplicationRecord
  belongs_to :user
end

class Learning < ApplicationRecord
  belongs_to :user
end

Data Example Consider this data scenario:

User1 has: Program with created_at: Yesterday Learning with created_at: Today User2 has: Program with created_at: Today Learning with created_at: Yesterday

For instance, if I filter for Date.yesterday, I want to get:

User1 with only the Program from yesterday. User2 with only the Learning from yesterday.

Current Approach I've tried various approaches using ActiveRecord queries with joins, where, and eager_load, but I'm either getting users with all their associated records (regardless of the date) or facing issues with structurally incompatible queries.

Can someone suggest a Rails way to achieve this filtering effectively, ensuring that the result is an ActiveRecord::Relation object?

Requirement Given a start and finish date (for example, Date.yesterday), I need to fetch users with their Program and Learning records that were created within this date range. However, the catch is that for a user, I only want to include the Program and Learning records that fall within the specified date range.

I've tried several methods, but none have given me the desired outcome:

  1. Using joins and where: I attempted to use joins with where conditions to filter records. However, this approach either returns users with all their associated records (ignoring the date criteria) or leads to structurally incompatible queries due to the use of or.
Copy code
User.joins(:program, :learnings)
    .where(program: { created_at: start_date..finish_date })
    .or(User.joins(:program, :learnings)
        .where(learnings: { created_at: start_date..finish_date }))

OR

User.joins("LEFT JOIN programs ON programs.user_id = users.id AND programs.created_at BETWEEN '#{start_date}' AND '#{finish_date}'").joins("LEFT JOIN learnings ON learnings.user_id = users.id AND learnings.created_at BETWEEN '#{start_date}' AND '#{finish_date}'")
  1. Subqueries with where: I also experimented with subqueries inside where, but it didn't filter the associated records based on the date criteria.
User.where(id: Program.select(:user_id).where(created_at: start_date..finish_date))
    .or(User.where(id: Learning.select(:user_id).where(created_at: start_date..finish_date)))

mardi 5 décembre 2023

How to handle the params for accepts_nested_attributes_for for has_many association containing a lot of fields on both associated table

I have user model which has has_many association with building model. Initially I was creating the user and the building seperately by using user.create and user.buildings.create because there were some field in the table such that if that field is true then only it create the building and in building also there were condition that if a conditional field in building will be true then more field data will be added to the building. Everything was running smooth till a user create a single building. But when a user started to create more building it bursts the code. Below is the code of users_controller

def create
      role = Role.find_by(id: params[:user][:role_id])
      if role.nil?
        render json: { error: 'invalid role' }, status: :unprocessable_entity
      else
        user = User.new(user_params)
        user.role_id = role.id
        ActiveRecord::Base.transaction do
          if user.save
            # If the user is a technician, handle equipment_params
            if role.name.downcase == 'technician'
              equipment_ids = params[:user][:equipment_id]
              handle_technician_params(user, equipment_ids)
            end
            # If the user is a customer, handle customer_params
            if role.name.downcase == 'customer'
              handle_customer_params(user)
            end
            # Generate a new authentication token for the user
            token, refresh_token = generate_tokens(user.id)
            render json: {  message: 'User created successfully', authentication_token: token, user: user, meta: {photos: UserSerializer.new(user) }}, status: :ok
          else
            render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
          end
        end
      end
    end
def handle_customer_params(user)
      if user_params[:is_customer_direct_point_of_contact] == 'true'
        handle_building_params(user)
      else
        handle_service_params(user)
      end
    end

    def handle_building_params(user)
      building_params = params.require(:building).permit(:service_address_line1, :service_address_line2, :service_zip_code, service_images: [])
      building = user.buildings.create(building_params)
    end
    def handle_service_params(user)
      service_params = params.require(:building).permit(:service_address_line1, :service_address_line2, :service_zip_code, :name, :phone_number, :email, :tax_id, service_images: [])
      building = user.buildings.create(service_params)
    end

I tried to change it to use accepts_nested_attributes_for for direct creating the user and building but did'nt understand how to do that. Is there any also other way to do that?

lundi 4 décembre 2023

Issue when Rounding Decimal values

I am building a test billing application, built in rub on rails, Jquery and Postgres DB (using decimal columns)

This is the below way I am storing values, but I think that is not the way it should save the values

Product 1: 57.5

Charge: 1.7249999999999999

S-Tax: 0.13799999999999998

C-Tax: 4.6

Total: 63.97

Here, I am not rounding any values other than Total when submitting the form; without rounding the Total will be 63.963. So, doing this rounding only for Total and not for others creates issues. For some countries I need to use the precision 2, and others 3

Moreover, I would like to know if this is the correct way to store these values in DB.

Is there any rule like doing the rounding for each column (Charge, S-Tax, C-Tax, and Total)? or any rules for the precision & scale? OR should we convert this to integer?

If we go for integer, should we round it and convert to integer?

What would be the correct data we should store when we submit it? It would be great if someone could suggest, as this has been haunting for some time.

vendredi 1 décembre 2023

Map an activerecord array to avoid that two item with the same attribute are in a sequential position

I have an issue to solve.

I have an array of elements, and on each element I can call the method 'content.sponsored?' that return me true or false.

The items that return true are 2/3 every 20 elements and they are always in the first position.

I need to map this array to avoid consecutive 'true'.

For example

contents = [
  { id: 1, sponsored: true },
  { id: 2, sponsored: true },
  { id: 3, sponsored: false },
  { id: 4, sponsored: false },
  { id: 5, sponsored: false },
  { id: 6, sponsored: false }...
]

I need

contents = [
  { id: 1, sponsored: true },
  { id: 2, sponsored: false },
  { id: 3, sponsored: true },
  { id: 4, sponsored: false },
  { id: 5, sponsored: false },
  { id: 6, sponsored: false }...
]

Which is the most efficient way to map these elements?