Looking into Credits feature on DEV from Laravel Developer perspective (Code Dive)
Tags: meta, rails, webdev, laravel
Published: 2020-03-04 20:58:53

##Important!!
* I am a PHP/Laravel Developer and I have no idea how Ruby/Rails work.
* We will make assumptions based on Laravel's conventions.
* We will focus on **Credits** feature only to keep our interest piqued.
* This is just a code reading exercise 🤷🏻‍♂️

If you visit [https://dev.to/credits](https://dev.to/credits), you will see how many credits you have in your account. You can see use these credits to create [listings](https://dev.to/listings).


##Getting ready
* First, let's clone the [dev.to](https://github.com/thepracticaldev/dev.to) repository on local.
* Open it in your favorite code editor
 
> We are not going to look into how to set it up on local, you can read about that in [docs](https://docs.dev.to/installation/)
> 

##DIVE
Our first hint is the URI, */credits*.
Let's find out where **routes** are defined. A quick file search `config/routes.rb` can be found. 
```ruby
#...

  resources :credits, only: %i[index new create] do
    get "purchase", on: :collection, to: "credits#new"
  end

#...
```
Following the Resourceful controllers convention, we can safely assume that there must be `CreditsController` with `index` function.

`credits_controller.rb` [source](https://github.com/thepracticaldev/dev.to/blob/master/app/controllers/credits_controller.rb#L4)
```ruby
class CreditsController < ApplicationController
  before_action :authenticate_user!

  def index
    @user_unspent_credits_count = current_user.credits.unspent.size
    @ledger = Credits::Ledger.call(current_user)

    @organizations = current_user.admin_organizations
  end
```
* Current logged in user's unspent credits are being calculated
* Then there is some kind of **Ledger** is being maintained
* Finally, organizations of current user

###User and Credits
A straightforward assumption is that these both are **Models**.

`user.rb` [source](https://github.com/thepracticaldev/dev.to/blob/master/app/models/user.rb#L49)
```ruby
class User < ApplicationRecord

#...

has_many :credits, dependent: :destroy

#...
```

`credit.rb` [source](https://github.com/thepracticaldev/dev.to/blob/master/app/models/credit.rb#L4)
```ruby
class Credit < ApplicationRecord

#...

belongs_to :user, optional: true

#...

```
Relationships are defined as we would define in [Laravel Eloquent](https://laravel.com/docs/7.x/eloquent-relationships#one-to-many).

####*unspent* scope
unspent in `current_user.credits.unspent.size` is defined as a scope in `Credit` model.
```ruby
  scope :unspent, -> { where(spent: false) }
```
###Ledger
`ledger.rb` can be found in **Credits** module. [source](https://github.com/thepracticaldev/dev.to/blob/master/app/services/credits/ledger.rb#L13)
```ruby
    def call
      # build the ledger for the user
      ledger = {
        [User.name, user.id] => build_ledger_for(user.credits)
      }

      # build the ledger for the organizations the user is an admin at
      user.admin_organizations.find_each do |org|
        ledger[[Organization.name, org.id]] = build_ledger_for(org.credits)
      end

      ledger
    end
```
`build_ledger_for` method is responsible for the `Payment history` on */credits* view. [source](https://github.com/thepracticaldev/dev.to/blob/master/app/services/credits/ledger.rb#L44)

##How Credits are added?

* On every **Badge Achievement** on **DEV** you get *5 credits*
* You can purchase credits [here](https://dev.to/credits/purchase)

###Badge Achievement
`badge_achievement.rb` [source](https://github.com/thepracticaldev/dev.to/blob/master/app/models/badge_achievement.rb#L38)
```ruby
class BadgeAchievement < ApplicationRecord
  belongs_to :user
  belongs_to :badge

#...

after_create :award_credits

#...

  def award_credits
    Credit.add_to(user, 5)
  end
```
After creating `BadgeAchievement`, award 5 credits to user. We can relate this to [Laravel Eloquent Events](https://laravel.com/docs/7.x/eloquent#events)

###Purchasing Credits
In `CreditsController`, 

* [new](https://github.com/thepracticaldev/dev.to/blob/master/app/controllers/credits_controller.rb#L11) method is responsible for presenting the view somehow 🙈
* [create](https://github.com/thepracticaldev/dev.to/blob/master/app/controllers/credits_controller.rb#L22) method is responsible for the actual **Purchase** logic (Stripe) and adding credits to our account. 

##How Credits are subtracted?

Credits are subtracted/removed when we create a listing. Based on this, we can assume that there would be a `ListingsController` with `create` containing this logic.

* Well, its actually `ClassifiedListingsController` defined in `classified_listings_controller.rb` [source](https://github.com/thepracticaldev/dev.to/blob/master/app/controllers/classified_listings_controller.rb#L1)
* And `create` method is inside a module/concern named as `ClassifiedListingsToolkit` [source](https://github.com/thepracticaldev/dev.to/blob/master/app/controllers/concerns/classified_listings_toolkit.rb#L42)


```ruby
def create_listing(purchaser, cost)

#...

ActiveRecord::Base.transaction do
      # subtract credits
      Credits::Buyer.call(
        purchaser: purchaser,
        purchase: @classified_listing,
        cost: cost,
      )

#...

end
```
This is how credits are subtracted from our account when we create a listing.

##Conclusion
We looked at how **Credits** feature works on [DEV](https://dev.to) and where to find its implementation in the codebase. 

I hope someone finds it useful.