Animesh
Animesh

Reputation: 437

Rails caching using fetch

Rails.cache.fetch(plan_list_cache_key(project), expires_in: EXPIRES_TIME) do
      plans = Plan.all.where(is_active: true)
      project_plan = project.project_plan
      hash_array = []
      plans.each do |plan|
        plan = plan.attributes
        expired = if (plan["id"] == project.plan.id)
                    (project_plan.end_date.to_date - Date.current).to_i.positive? ? false : true
                  else
                    false
                  end
        subscribed = plan["id"] == project.plan.id
        hash_array << plan.merge!({is_expired: expired , subscribed: subscribed})
      end
      hash_array
    end

Models:

class Plan < MysqlBase
  has_many :project_plans
end

class ProjectPlan < MysqlBase
  belongs_to :plan
  belongs_to :project
end

class Project < MysqlBase
  has_one :project_plan
  has_one :plan, :through => :project_plan
end

Schema:

create_table "plans", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
    t.string "name"
    t.text "description"
    t.integer "email_limit"
    t.integer "validity"
    t.float "unit_price", limit: 24
    t.string "currency"
    t.integer "base_user_count"
    t.boolean "is_renewable"
    t.boolean "is_active"
    t.boolean "is_free"
    t.string "terms"
    t.integer "trial_days"
    t.boolean "default"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

create_table "project_plans", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
    t.integer "plan_id"
    t.integer "project_id"
    t.datetime "start_date"
    t.datetime "end_date"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

Response

{
    "status": "OK",
    "status_code": 200,
    "message": "ok",
    "data": {
        "plans": [
            {
                "id": 1,
                "name": "Free",
                "description": "30 days free trial",
                "email_limit": 10000,
                "validity": 30,
                "unit_price": 0.0,
                "currency": "USD",
                "base_user_count": 0,
                "trial_days": 30,
                "is_free": true,
                "subscribed": false,
                "is_expired": false
            },
            {
                "id": 2,
                "name": "Standard",
                "description": "$3 for 100 users",
                "email_limit": 0,
                "validity": 30,
                "unit_price": 0.03,
                "currency": "USD",
                "base_user_count": 100,
                "trial_days": 0,
                "is_free": false,
                "subscribed": true,
                "is_expired": false
            }
        ]
    }
}

Each project has a separate project_plan. Plans are the same for all users. But what I have to do is to add two additional fields is_expired & subscribed with the API response

I am caching my response here using fetch. But is it the right way to doing cache as there are some DB & logical operations

If that isn't the right way what type of error can be happened here?

What will be the best approach to do this kind of caching?

Upvotes: 0

Views: 251

Answers (1)

max
max

Reputation: 102368

One solution here is to join the project plans table and select aggregates:

SELECT 
  plans.*,
  -- you will get integers since MySQL is a peasant database and does not have real boolean types
  COUNT(pp.id) > 0 AS subscribed,
  COALESCE(MAX(pp.ends_at) < NOW(), FALSE) AS is_expired
FROM plans
LEFT OUTER JOIN project_plans pp 
  ON pp.plan_id = plans.id
  AND pp.project_id = ?
GROUP BY plans.id
class Plan
  def with_statuses_for_project(project)
    pp = ProjectPlan.arel_table
    j = pp.join(arel_table).on(
      pp[:plan_id].eq(arel_table[:id])
                  .and(
                    pp[:project_id].eq(project.id)
                  )
    )
    .select(
      arel_table[Arel.star],
      "COUNT(pp.id) > 0 AS subscribed",
      "COALESCE(MAX(pp.ends_at) < NOW(), FALSE) AS is_expired"
    )
    .joins(j.join_sources)
    .group(:id)
  end
end

Upvotes: 1

Related Questions