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?

jeudi 16 novembre 2023

Devise Registration Ruby on Rails - Migration Error: Duplicate column name

I'm encountering an issue while running a Rails migration that adds Devise to my Users table. The error message points to a duplicate column name, specifically "email." The migration file causing the problem is located at /Users/jaydenthelwell/pye-candles/pye-candles/db/migrate/20231115201715_add_devise_to_users.rb.

Here is the error:

`➜ pye-candles git:(master) ✗ rails db:migrate == 20231115201715 AddDeviseToUsers: migrating ================================= -- change_table(:users) rails aborted! StandardError: An error has occurred, this and all later migrations canceled:

SQLite3::SQLException: duplicate column name: email /Users/jaydenthelwell/pye-candles/pye-candles/db/migrate/20231115201715_add_devise_to_users.rb:7:in block in up' /Users/jaydenthelwell/pye-candles/pye-candles/db/migrate/20231115201715_add_devise_to_users.rb:5:in up'

Caused by: ActiveRecord::StatementInvalid: SQLite3::SQLException: duplicate column name: email /Users/jaydenthelwell/pye-candles/pye-candles/db/migrate/20231115201715_add_devise_to_users.rb:7:in block in up' /Users/jaydenthelwell/pye-candles/pye-candles/db/migrate/20231115201715_add_devise_to_users.rb:5:in up'

Caused by: SQLite3::SQLException: duplicate column name: email /Users/jaydenthelwell/pye-candles/pye-candles/db/migrate/20231115201715_add_devise_to_users.rb:7:in block in up' /Users/jaydenthelwell/pye-candles/pye-candles/db/migrate/20231115201715_add_devise_to_users.rb:5:in up' Tasks: TOP => db:migrate (See full trace by running task with --trace) ➜ pye-candles git:(master) ✗ `

Here's the relevant the migration file:

`# frozen_string_literal: true

class AddDeviseToUsers < ActiveRecord::Migration[7.0] def self.up change_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: ""

  ## Recoverable
  t.string   :reset_password_token
  t.datetime :reset_password_sent_at

  ## Rememberable
  t.datetime :remember_created_at

  ## Trackable
  # t.integer  :sign_in_count, default: 0, null: false
  # t.datetime :current_sign_in_at
  # t.datetime :last_sign_in_at
  # t.string   :current_sign_in_ip
  # t.string   :last_sign_in_ip

  ## Confirmable
  # t.string   :confirmation_token
  # t.datetime :confirmed_at
  # t.datetime :confirmation_sent_at
  # t.string   :unconfirmed_email # Only if using reconfirmable

  ## Lockable
  # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
  # t.string   :unlock_token # Only if unlock strategy is :email or :both
  # t.datetime :locked_at


  # Uncomment below if timestamps were not included in your original model.
  # t.timestamps null: false
end

add_index :users, :email,                unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token,   unique: true
# add_index :users, :unlock_token,         unique: true

end

def self.down # By default, we don't want to make any assumption about how to roll back a migration when your # model already existed. Please edit below which fields you would like to remove in this migration. raise ActiveRecord::IrreversibleMigration end end

`

I deleted the User migration file as I thought this was causing the issue, the users table also has "email" but the problem persists.

lundi 13 novembre 2023

how to send data to sidekiq queue from ruby app

im new with sideqik and i want to test it for verify that sideqik receive data from a very simple ruby app. exist one method to send data easily?

This is my ruby app:

require "redis"
redis =Redis.new(host: "127.0.0.1", port: 6379)
redis.set("mykey", "hello world!")
redis.post("mykey")

i tried with this script, the connection with sideqik works but when i accessed in its webUI i cant see data. Thank you for help.

Overriding object_changes on paper trails to store name corresponding to change in IDs

For associations, I am assigning IDs from different model to my model and therefore IDs are being changed. Papertrail tracks those changes, and this is the state of my object_changes:

{
  "updated_at": [
    "2023-11-13T08:54:26.346Z",
    "2023-11-13T08:56:06.961Z"
  ],
  "paying_id": [
    "ID1",
    "ID1 new"
  ],
  "company_ids": [
    [
      "ID1",
      "ID2",
      "ID3",
      "ID4"
    ],
    [
      "ID1 new",
      "ID2 new",
      "ID3 new"
    ]
  ]
}

However, in my view for the audit logs, I do not want to display the IDs, but display the names corresponding to those IDs. Right now, I am using if loops to query the names and send to the view. But there must be a better way than this. The docs is also against overriding object_changes, but if object_changes is not overriden, then how?

What is the correct way to do this?