Coder Social home page Coder Social logo

activerecord-lifecycle-reading's Introduction

Objectives

  1. Understand the concept of AR Lifecycle methods
  2. Use before_save, before_create, and before_validation
  3. Understand when to use before_validation vs. before_save

Callbacks

Now that we are integrating ActiveRecord into Rails, we should note that we can make bits of code run whenever something happens in our model: like when it's created (but not yet saved to the database), saved to the database, or even deleted. Everything we cover here is called an "Active Record Lifecycle Callback". Many people just call them callbacks. It's a bit shorter.

Take a look at the blog app that is included. Be sure to run the migrations before you start learning from Rails (we do this with rake db:migrate)! We have a Post model and a few views. The Post belongs_to an Author.

Note also that in the Post model you'll notice a validation to make sure that post titles are in title case. Title case means every word starts with a capital letter.

So, in order to make sure that our validation always passes, before every save, we want Rails to run our title-case algorithm on the title of the Post. Let's create the make_title_case method then.

# post.rb

def make_title_case
  self.title = self.title.titlecase
end

To make sure that all of our Posts have the correctly-formatted title, we're going to run make_title_case during the first of the available lifecycle "points:" before_save. Our validation and lifecycle callback will make sure our posts are always title-cased.

We write lifecycle callbacks similarly to how you use has_many or validates and place this "hook" onto saving at the top of our model file. Since lifecycle methods run "as if by magic," we won't see them being called explicitly in one method by another method versus Rails running it for us, we put such statements at the top so that it catches other programmers' eyes.

class Post < ActiveRecord::Base

  belongs_to :author
  validate :is_title_case

  # New Code!!
  before_save :make_title_case

  private
  def is_title_case
    if title.split.any?{|w|w[0].upcase != w[0]}
      errors.add(:title, "Title must be in title case")
    end
  end

  def make_title_case
    # Rails provides a String#titlecase method
    self.title = self.title.titlecase
  end
end

We'd expect that whenever Rails persists Post models to the database, (so #save and #create) this code will get run. Let's open up the console (rails c) and test it out:

p = Post.create(title: "testing")
#   (0.1ms)  begin transaction
#   (0.1ms)  rollback transaction
# => #<Post id: nil, title: "testing", description: nil, created_at: nil, updated_at: nil, post_status: nil, author_id: nil>

Wait! There was no INSERT SQL command issued. In fact, we see the rollback transaction line. That means that it didn't actually save to the database. If we do p.valid? right now it will return false.

This feels surprising. Most of the time when we have this feeling while programming it's because we didn't understand something subtle. This is true here.

It turns out that the before_save is called after validation occurs. So Rails goes is valid? "Nope! Stop!", and never makes it to before_save. We missed that subtlety.

Let's change our callback to the before_validation callback. This one happens before validation. That means that first our before_validation code works, which title cases the title, then the validation runs, which passes! Here is the final code:

class Post < ActiveRecord::Base

  belongs_to :author
  validate :is_title_case

  # New Code!!
  before_validation :make_title_case

  private

  def is_title_case
    if title.split.any?{|w|w[0].upcase != w[0]}
      errors.add(:title, "Title must be in title case")
    end
  end

  def make_title_case
    self.title = self.title.titlecase
  end
end

Here is a rule of thumb: Whenever you are modifying an attribute of the model, use before_validation. If you are doing some other action, then use before_save.

Before Save

Now let's do something that (properly) belongs in the before_save. We use before_save for actions that need to occur that aren't modifying the model itself. For example, whenever you save to the database, let's send an email to the Author alerting them that the post was just saved!

This is a perfect before_save action. It doesn't modify the model so there is no validation weirdness, and we don't want to email the user if the Post is invalid. That would be just mean! So if you had some method called email_author_about_post you would modify your Post model to look like this:

class Post < ActiveRecord::Base

  belongs_to :author
  validate :is_title_case

  before_validation :make_title_case

  # New Code!!
  before_save :email_author_about_post

  private

  def is_title_case
    if title.split.any?{|w|w[0].upcase != w[0]}
      errors.add(:title, "Title must be in title case")
    end
  end

  def email_author_about_post
    # Not implemented.
    # For more information: https://guides.rubyonrails.org/action_mailer_basics.html
  end

  def make_title_case
    self.title = self.title.titlecase
  end
end

Before Create

Before you move on, let's cover one last callback that is useful: before_create. before_create is very close to before_save with one major difference: it only gets called when a model is created for the first time. This means not every time the object is persisted, just when it is new.

For more information on all of the callbacks available to you, check out this amazing rails guide

activerecord-lifecycle-reading's People

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

activerecord-lifecycle-reading's Issues

Routes were missing

The tests were failing because no code for routes was included. Since this felt like a walk-through lab, maybe this was an oversight?

Fix Rspec test

On line 36 of post_spec.rb ,it says:
expect(page).to have_content("My edit")

It should expect "My Edit", with a capitalized "Edit", since it is being automatically capitalized before being validated.

incorrect test

This was a readme. upon running learn i got this error

Failures:

  1. form shows an update form that submits content and redirects and prints out params
    Failure/Error: expect(page).to have_content("My Edit")
    expected to find text "My Edit" in "My Post\nMy post desc"

in the spec it has:

describe 'form' do

it 'shows an update form that submits content and redirects and prints out params' do
@post = Post.create(title: "My Post", description: "My post desc")

visit edit_post_path(@post)

fill_in 'post[title]', with: "My edit"
fill_in 'post[description]', with: "My post description"

click_on "Update Post"

expect(page).to have_content("My Edit")

end
end

the fill in for "My edit" has lowercase e. i had to alter the test to uppercase to get it to pass

this lab only has a finished reading button on the right

when you click on it as though you are finished reading and then go on to the next lesson the website will not persist the fact that it is completed (ie the green light will not stay green after a refresh).

I went ahead and forked the lab down to my computer and made the changes and then did a pull request. Then I clicked the button as though I was finished reading and it did persist this time (ie the light stayed green even after a page refresh).

basically the page is requiring that you fork - pass - pull the lab to get credit for it but the page only shows the "finished reading" button (as though you don't need to fork - pass -pull on this lab.

trivial but confusing nonetheless. It would be best to either get the page to reflect that it actually a fork - pass - pull lab and not just a read lab.

thanks,

lucas

PostsController Update Method Not Working

Current code:

def update
	  @post = Post.find(params[:id])
	  @post.update(params.require(:post))
	  redirect_to post_path(@post)
	end

It gives me this error:

 1) form shows an update form that submits content and redirects and prints out params
     Failure/Error: @post.update(params.require(:post))
     
     ActiveModel::ForbiddenAttributesError:
       ActiveModel::ForbiddenAttributesError

What worked for me:

	def update
	  @post = Post.find(params[:id])
	  @post.update(params.require(:post).permit!)
	  redirect_to post_path(@post)
	end

I also don't understand why the routes aren't written in this ReadMe. If it's a ReadMe and it's not teaching routes, they should be written so we can focus on the material being taught. It's distracting.

Typo

we must should note that

Should or must, not must should :D

before_validation

The before_validation statement is already put in for you, but it's not supposed to be there until you get further down in the lab. You're actually supposed to write it in yourself.

Because of this, the Post.create doesn't behave as described in the lab. (It works when it "shouldn't" work.)

unknown error and unable to use rspec or learn.

This isnt a lab, more like a read along, but i found myself trying to decode this error message with the help of students and instructor Dylan. It seems as though we could not find a solution to what's going on with this error. I can not use learn or rspec or it will raise this error. If someone can help explain why this is going on, that would be helpful since all I did was fork and copy and ran rspec. I did not change any code except the one line in the model class.

// โ™ฅ learn
/Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.5/lib/abstract_controller/helpers.rb:151:in rescue in block in modules_for_helpers': Missing helper file helpers//users/aaronfigueroa/development/rails/associations_and_rails/activerecord_lifecycle_reading_v_000/app/helpers/application_helper.rb_helper.rb (AbstractController::Helpers::MissingHelperError) from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.5/lib/abstract_controller/helpers.rb:148:inblock in modules_for_helpers'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.5/lib/abstract_controller/helpers.rb:144:in map!' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.5/lib/abstract_controller/helpers.rb:144:inmodules_for_helpers'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.5/lib/action_controller/metal/helpers.rb:93:in modules_for_helpers' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.5/lib/abstract_controller/helpers.rb:108:inhelper'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.5/lib/action_controller/railties/helpers.rb:17:in inherited' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionview-4.2.5/lib/action_view/test_case.rb:11:inclass:TestCase'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionview-4.2.5/lib/action_view/test_case.rb:10:in <module:ActionView>' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/actionview-4.2.5/lib/action_view/test_case.rb:8:in<top (required)>'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-rails-3.4.0/lib/rspec/rails/example/helper_example_group.rb:10:in <module:HelperExampleGroup>' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-rails-3.4.0/lib/rspec/rails/example/helper_example_group.rb:7:inmodule:Rails'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-rails-3.4.0/lib/rspec/rails/example/helper_example_group.rb:4:in <module:RSpec>' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-rails-3.4.0/lib/rspec/rails/example/helper_example_group.rb:3:in<top (required)>'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:in require' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:inblock in require'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:240:in load_dependency' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:inrequire'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-rails-3.4.0/lib/rspec/rails/example.rb:4:in <top (required)>' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:inrequire'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:in block in require' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:240:inload_dependency'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:in require' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-rails-3.4.0/lib/rspec/rails.rb:12:in<top (required)>'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:in require' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:inblock in require'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:240:in load_dependency' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.5/lib/active_support/dependencies.rb:274:inrequire'
from /Users/aaronfigueroa/Development/rails/associations_and_rails/activerecord-lifecycle-reading-v-000/spec/rails_helper.rb:7:in <top (required)>' from /Users/aaronfigueroa/Development/rails/associations_and_rails/activerecord-lifecycle-reading-v-000/spec/features/post_spec.rb:1:inrequire'
from /Users/aaronfigueroa/Development/rails/associations_and_rails/activerecord-lifecycle-reading-v-000/spec/features/post_spec.rb:1:in <top (required)>' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/lib/rspec/core/configuration.rb:1361:inload'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/lib/rspec/core/configuration.rb:1361:in block in load_spec_files' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/lib/rspec/core/configuration.rb:1359:ineach'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/lib/rspec/core/configuration.rb:1359:in load_spec_files' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/lib/rspec/core/runner.rb:102:insetup'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/lib/rspec/core/runner.rb:88:in run' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/lib/rspec/core/runner.rb:73:inrun'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/lib/rspec/core/runner.rb:41:in invoke' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/gems/rspec-core-3.4.1/exe/rspec:4:in<top (required)>'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/bin/rspec:23:in load' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/bin/rspec:23:in

'
from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/bin/ruby_executable_hooks:15:in eval' from /Users/aaronfigueroa/.rvm/gems/ruby-2.2.3/bin/ruby_executable_hooks:15:in'

rails c error // "Traceback"

Hi there! I received this error when trying to follow along with the instructions for this code-along while inside rails c running this command:

p = Post.create(title: "testing")

Error below:

Screen Shot 2020-11-11 at 8 54 31 AM

It wasn't the output that was described in the lesson.

Hope this helps! Thanks again :-)

Seems odd that this is a lab

All of the tests pass without modifying any files.

Maybe remove some code and at least make it a somewhat active, copy-and-paste code-along?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.