Andrea Wayte

Programmer. Crafter. Clothing maker. Baker. Cyclist.

Rails Active Record

31 Aug 2018

After several months of working with Ruby on Rails, I want to gain a deeper understanding of Active Record in efforts to optimize performance and understand more of the good ol’ Rails magic. I drew information from many resources, which I will include as links at the bottom. This is a general overview of common concepts of Active Record.

**Active Record Classes**

ActiveRecord::FinderRelations
Returns model instance, and run queries immediatelyraises error if not found
#find
returns nil if not found
#find_by
#first
#last
#exists?

**ActiveRecord::QueryMethods**
Returns relation, only runs query when calledreturns an array
#where
#group
#distinct
#includes
#joins
#select

Aggregations

Useful when performing mathematical operations, such as #count or #max.

Post.joins(:tags).group("tags.name").count
  # => {"tag1" => 4, "tag2" => 2, "tag3" => 5}

Scopes

Chains of ActiveRecord methods that are used with a relation. Scopes return relations, so you can chain them.

class User < ActiveRecord::Base
  scope :millennials, -> { where('age < 35') }
end

Retrieve large batches

Works for models and relations, and can chain methods

#find_eachUser.find_each(batch_size: 5000) do |user|
  NewsMailer.weekly(user).deliver_now
end
options: batch_size, start, finish, error_on_ignore#find_in_batches

Conditions can be used with ActiveRelations

Client.where("orders_count = ?", params[:orders])

Joins (lazy loading)

Lazy loading: queries are not fired until called upon

> user = User.where('age > 18')
> user.all #queries fired

# Returns all Users that have a blog post

users_with_posts = User.joins(:post)
users_with_posts.all

# Returns all User objects with posts in which have comments

User.joins(:post, :comment)

# join nested associations

User.joins(post: :something)

Includes (eager loading, minimal queries possible)

ActiveRecored lets you specify 1+ associations beforehand. It eagerly loads the table so there are not extra queries

User.includes(:posts, :followers)

N + 1 problem

We want to avoid unnecessary queries through associations.

#includes uses eager loading

An example of eager loading is #pluck. It pulls up a bunch of records and stores in memory, grabs the column and returns an array.

Joins vs Includes

Use joins when you don’t want to access data from the associated table

Use includes when you want to access data from the associated table

More Resources api.rubyonrails.org guides.rubyonrails.org (rubyinrails.com)[rubyinrails.com]


Another way to implement React with Ruby on Rails on Heroku

23 Apr 2018

A tutorial to set up a rails app on Heroku!

React is gaining popularity for good reason. It creates a virtual DOM and uses renderable components which results in a speedy application. For my application, I decided to use React for my client side and Ruby on Rails as a JSON API. I had spent much time searching for the best solution only to find… there are many solutions. After discussing with my partner, I decided to incorporate React as a bundle file that Rails will serve.

Rails will serve any file that is located in the public folder, so I used this to my advantage and set up wepback to bundle the Javascript file to the public folder.

My file structure looks like this:

-app
   //Rails files in here, such as controllers, models, etc
...
-client
   //React files in here, such as dist, src, etc
...
-public
   -app
      -assets
      -bundle.js
      -index.html
...
-package.json //include Webpack config in here!

I include the package.json file for the client in the root folder so Heroku will follow the instructions to bundle the files when deploying to production. You also need to tell webpack where to place the bundled Javascript file.

Change this in your webpack.config.js…

...
output: {
   filename: 'bundle.js',
   path: resolve(__dirname, '../public/app/'),
   publicPath: '/app/'
},
...

Now Webpack knows where to place the file. Next we want to configure Node to bundle after dependencies are installed. We do this with heroku-postbuild.

"scripts": {
   "start": "react-scripts start",
   "bundle": "node_modules/.bin/webpack --config   ./client/webpack.deploy.config.js --progress --colors",
   "test": "react-scripts test --env=jsdom",
   "build-css": "node-sass-chokidar --include-path ./client/src --include-path ./node_modules src/ -o src/",
   "watch-css": "npm run build-css && node-sass-chokidar --include-path ./client/src --include-path ./node_modules src/ -o src/ --watch --recursive",
   "build": "react-scripts build",
   "watch": "webpack --config ./client/webpack.config.js --watch",
   "heroku-postbuild": "npm run bundle"
}

Make sure to set up a Heroku buildpack. You can do this in the Heroku CLI.

heroku buildpacks:clear
heroku buildpacks:set heroku/nodejs
heroku buildpacks:add heroku/ruby --index 2

Now you are all set to develop a React/Rails project! All you got to do is npm run watch and rails server and start building!


Using Amazon S3 to store images for React on Rails

19 Apr 2018

A tutorial to set up Amazon S3 to store images for your Rails app on Heroku!

As a new developer, I am constantly learning new things. This is both exciting and terrifying, and luckily there are many developers out there who contribute their knowledge to help us. I found myself reading many tutorials to do this, and decided I should give back and write about how I implemented image uploading with S3 storage.

Heroku — A cloud platform used for deploying applications using git, and includes lots of documentation for Rails applications.

Paperclip — A file attachment library for Rails models.

S3 — Cloud storage service provided by Amazon.

How it works:

Long story short, an image file will be sent from the client to the server, which is then sent to S3 for storage and receives a url for the image, in which Paperclip attaches to the model. It may sound like a lot of work, but these handy gems do most of the work for us!

Step 1: Get AWS credentials and set up S3 Bucket

Follow the AWS docs for instructions to set up a bucket.

Once you’ve set up your bucket, you need to set up the permissions. Head over to the S3 page and click the bucket you just made. Click the Permissions tab and you will set up both the Bucket Policy and CORS configuration (if you are using cross-origin requests).

For the bucket policy, click Policy Generator and fill in. Copy and paste the JSON file into the editor and save. Next, for CORS configuration copy, edit and paste this:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin> http://localhost:3000 </AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Repeat the process to create a bucket for production, just remember to replace localhost with your address!

Step 2: Set up Gemfile and configs

gem 'paperclip'gem 'aws-sdk-s3', '~> 1'gem 'aws-sdk-ec2', '~> 1'

Don’t forget to bundle install!

Next, add the configurations. Make sure to change the values depending on your region. You can find the region codes here.

In config/environments/development.rb...Paperclip.options[:command_path] = "/usr/bin/"config.paperclip_defaults = {    storage: :s3,    s3_host_name: 's3-us-east-2.amazonaws.com',    s3_credentials: {       bucket: ENV.fetch('S3_BUCKET_NAME'),       access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),       secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'),       s3_region: ENV.fetch('AWS_REGION'),    }}

Add the above code to config/environments/production.rb as well!

Now you need to set your environment variables in both your development and production (Heroku).

For development, you can either use Figaro, or add to your ~/.bashrc file.

export S3_BUCKET_NAME="INSERT_BUCKET_NAME"
export AWS_ACCESS_KEY_ID="INSERT_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="INSERT_SECRET_ACCESS_KEY_ID"
export AWS_REGION="us-east-2" //change to your region

For production on heroku:

heroku config:set S3_BUCKET_NAME="INSERT_BUCKET_NAME"
heroku config:set AWS_ACCESS_KEY_ID="INSERT_ACCESS_KEY_ID"
heroku config:set AWS_SECRET_ACCESS_KEY="_SECRET_ACCESS_KEY_ID"
heroku config:set AWS_REGION="us-east-2"

Step 3: Rails Set Up

Since we will be receiving a url from S3, we need to attach the file to the model. Update the model to attach the file.

class Post < ActiveRecord::Base

  has_attached_file :image, styles: {
    thumb: '100x100>',
    square: '200x200#',
    medium: '500x500#>'
  }

  validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]end

Then create a migration for the image url

$ rails g migration AddImageToPost

Since you are changing the column, create up/down methods in case you ever need to rollback.

class AddImageFileToPosts < ActiveRecord::Migration[5.1]   def self.up      add_attachment :posts, :image   end   def self.down      remove_attachment :posts, :image   endend

Now you are set up to migrate your database and update the schema.

$ rails db:migrate

Once migrated, you will notice the following columns added to the model:

create_table "posts", force: :cascade do |t|   ....   t.string "image_file_name"   t.string "image_content_type"   t.integer "image_file_size"   t.datetime "image_updated_at"
end

Step 4: Upload Form

Set up your Form Component to save the file data to the component state. When the file changes, save the file data from the event target. On submit, use the multi-part form header as well as a FormData object along with request.

import axios from 'axios';

constructor(props){
    super(props);
    this.state = {
      file: ''
  }
}

handleSubmit(event){
  event.preventDefault();
  let fileData = new FormData();    
  fileData.append('imagefile', this.state.file);   
  axios({method: 'post', url: '/posts.json', data: fileData,   
  headers: {'Content-Type': 'multipart/form-data'}}).then(resp => {
    //update state or whatever you want to do with the resp
  }).catch( err => {
    //catch the error
  })
}

fileChange(event){
  const { value } = event.target;     
  let ifImage = (/\.(gif|jpg|jpeg|png)$/i).test(value)    
  if(!ifImage){
    return;
  }     this.setState({...this.state, file: event.target.files[0])}
}

render(){   
  return (
  <form onSubmit={this.handleSubmit.bind(this)}>
    <div className="field">
      <label className="label">Upload image</label>
      <div className="control">
        <input className="input" type="file" name="file" value={filename} onChange={this.fileChange.bind(this)}/>
      </div>
    </div>
      <button className="button is-link">Submit</button>
    </div>
  </form>
  )
}

Step 5: Set up Rails Controller

This is where all the behind the scenes gem magic happens. All you have to do is create new and save. Nothing out of the ordinary here.

def create
    @post = Post.new(post_params)

    if @post.save
          render json: post
    else
         render json: @post.errors.full_messages
    end
end

Viola!

Now, accessing the image is a simple as …

post.image.url