Undefined method error, but my method is defined? - ruby-on-rails

When I run my app, I get an error that states: undefined local variable or method `signup' for #, but I'm not sure why this is happening. According to the code below, Signup is a new class that I've defined.
Thanks for your help!
Controller code:
class SignupsController < ApplicationController
def new
#signup = Signup.new
end
def create
#signup = Signup.new(signup_params)
if #signup.save
signup.add_subscrip
else
redirect_to new_signup_path
end
end
end
Model code:
class Signup < ActiveRecord::Base
validates :email, presence: true, format: { with: /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i }
validates :name, presence: true, length: { maximum: 50 }
def add_subscrip
connection = GoogleDrive.login(ENV['g_username'], ENV['g_password'])
ss = connection.spreadsheet_by_title(ENV['spreadsheet_title'])
ws = ss.worksheets[0]
row = 3 + ws.num_rows
ws[row, 1] = self.name
ws[row, 2] = Time.new
ws[row, 3] = self.email
ws.save
end
end

In your create method
def create
#signup = Signup.new(signup_params)
if #signup.save
signup.add_subscrip
else
redirect_to new_signup_path
end
end
My sense is that
signup.add_subscrip
needs to be
#signup.add_subscrip

Related

How can I test these RSS parsing service objects?

I have some service objects that use Nokogiri to make AR instances. I created a rake task so that I can update the instances with a cron job. What I want to test is if it's adding items that weren't there before, ie:
Create an Importer with a url of spec/fixtures/feed.xml, feed.xml having 10 items.
Expect Show.count == 1 and Episode.count == 10
Edit spec/fixtures/feed.xml to have 11 items
Invoke rake task
Expect Show.count == 1 and Episode.count == 11
How could I test this in RSpec, or modify my code to be more testable?
# models/importer.rb
class Importer < ActiveRecord::Base
after_create :parse_importer
validates :title, presence: true
validates :url, presence: true
validates :feed_format, presence: true
private
def parse_importer
Parser.new(self)
end
end
# models/show.rb
class Show < ActiveRecord::Base
validates :title, presence: true
validates :title, uniqueness: true
has_many :episodes
attr_accessor :entries
end
# models/episode.rb
class Episode < ActiveRecord::Base
validates :title, presence: true
validates :title, uniqueness: true
belongs_to :show
end
#lib/tasks/admin.rake
namespace :admin do
desc "Checks all Importer URLs for new items."
task refresh: :environment do
#importers = Importer.all
#importers.each do |importer|
Parser.new(importer)
end
end
end
# services/parser.rb
class Parser
def initialize(importer)
feed = Feed.new(importer)
show = Show.where(rss_link: importer.url).first
if show # add new episodes
new_episodes = Itunes::Channel.refresh(feed.origin)
new_episodes.each do |new_episode|
show.episodes.create feed.episode(new_episode)
end
else # create a show and its episodes
new_show = Show.new(feed.show) if (feed && feed.show)
if (new_show.save && new_show.entries.any?)
new_show.entries.each do |entry|
new_show.episodes.create feed.episode(entry)
end
end
end
end
end
# services/feed.rb
class Feed
require "nokogiri"
require "open-uri"
require "formats/itunes"
attr_reader :params, :origin, :show, :episode
def initialize(params)
#params = params
end
def origin
#origin = Nokogiri::XML(open(params[:url]))
end
def format
#format = params[:feed_format]
end
def show
case format
when "iTunes"
Itunes::Channel.fresh(origin)
end
end
def episode(entry)
#entry = entry
case format
when "iTunes"
Itunes::Item.fresh(#entry)
end
end
end
# services/formats/itunes.rb
class Itunes
class Channel
def initialize(origin)
#origin = origin
end
def title
#origin.xpath("//channel/title").text
end
def description
#origin.xpath("//channel/description").text
end
def summary
#origin.xpath("//channel/*[name()='itunes:summary']").text
end
def subtitle
#origin.xpath("//channel/*[name()='itunes:subtitle']/text()").text
end
def rss_link
#origin.xpath("//channel/*[name()='atom:link']/#href").text
end
def main_link
#origin.xpath("//channel/link/text()").text
end
def docs_link
#origin.xpath("//channel/docs/text()").text
end
def release
#origin.xpath("//channel/pubDate/text()").text
end
def image
#origin.xpath("//channel/image/url/text()").text
end
def language
#origin.xpath("//channel/language/text()").text
end
def keywords
keywords_array(#origin)
end
def categories
category_array(#origin)
end
def explicit
explicit_check(#origin)
end
def entries
entry_array(#origin)
end
def self.fresh(origin)
#show = Itunes::Channel.new origin
return {
description: #show.description,
release: #show.release,
explicit: #show.explicit,
language: #show.language,
title: #show.title,
summary: #show.summary,
subtitle: #show.subtitle,
image: #show.image,
rss_link: #show.rss_link,
main_link: #show.main_link,
docs_link: #show.docs_link,
categories: #show.categories,
keywords: #show.keywords,
entries: #show.entries
}
end
def self.refresh(origin)
#show = Itunes::Channel.new origin
return #show.entries
end
private
def category_array(channel)
arr = []
channel.xpath("//channel/*[name()='itunes:category']/#text").each do |category|
arr.push(category.to_s)
end
return arr
end
def explicit_check(channel)
string = channel.xpath("//channel/*[name()='itunes:explicit']").text
if string === "yes" || string === "Yes"
true
else
false
end
end
def keywords_array(channel)
keywords = channel.xpath("//channel/*[name()='itunes:keywords']/text()").text
arr = keywords.split(",")
return arr
end
def entry_array(channel)
arr = []
channel.xpath("//item").each do |item|
arr.push(item)
end
return arr
end
end
class Item
def initialize(origin)
#origin = origin
end
def description
#origin.xpath("*[name()='itunes:subtitle']").text
end
def release
#origin.xpath("pubDate").text
end
def image
#origin.xpath("*[name()='itunes:image']/#href").text
end
def explicit
explicit_check(#origin)
end
def duration
#origin.xpath("*[name()='itunes:duration']").text
end
def title
#origin.xpath("title").text
end
def enclosure_url
#origin.xpath("enclosure/#url").text
end
def enclosure_length
#origin.xpath("enclosure/#length").text
end
def enclosure_type
#origin.xpath("enclosure/#type").text
end
def keywords
keywords_array(#origin.xpath("*[name()='itunes:keywords']").text)
end
def self.fresh(entry)
#episode = Itunes::Item.new entry
return {
description: #episode.description,
release: #episode.release,
image: #episode.image,
explicit: #episode.explicit,
duration: #episode.duration,
title: #episode.title,
enclosure_url: #episode.enclosure_url,
enclosure_length: #episode.enclosure_length,
enclosure_type: #episode.enclosure_type,
keywords: #episode.keywords
}
end
private
def explicit_check(item)
string = item.xpath("*[name()='itunes:explicit']").text
if string === "yes" || string === "Yes"
true
else
false
end
end
def keywords_array(item)
keywords = item.split(",")
return keywords
end
end
end
Before anything else, good for you for using service objects! I've been using this approach a great deal and find POROs preferable to fat models in many situations.
It appears the behavior you're interested in testing is contained in Parser.initialize.
First, I'd create a class method for Parser called parse. IMO, Parser.parse(importer) is clearer about what Parser is doing than is Parser.new(importer). So, it might look like:
#services/parser.rb
class Parser
class << self
def parse(importer)
#importer = importer
#feed = Feed.new(importer)
if #show = Show.where(rss_link: importer.url).first
create_new_episodes Itunes::Channel.refresh(#feed.origin)
else
create_show_and_episodes
end
end # parse
end
end
Then add the create_new_episodes and create_show_and_episodes class methods.
#services/parser.rb
class Parser
class << self
def parse(importer)
#importer = importer
#feed = Feed.new(importer)
if #show = Show.where(rss_link: #importer.url).first
create_new_episodes Itunes::Channel.refresh(#feed.origin)
else
create_show_and_episodes
end
end # parse
def create_new_episodes(new_episodes)
new_episodes.each do |new_episode|
#show.episodes.create #feed.episode(new_episode)
end
end # create_new_episodes
def create_show_and_episodes
new_show = Show.new(#feed.show) if (#feed && #feed.show)
if (new_show.save && new_show.entries.any?)
new_show.entries.each do |entry|
new_show.episodes.create #feed.episode(entry)
end
end
end # create_show_and_episodes
end
end
Now you have a Parser.create_new_episodes method that you can test independently. So, your test might look something like:
require 'rspec_helper'
describe Parser do
describe '.create_new_episodes' do
context 'when an initial parse has been completed' do
before(:each) do
first_file = Nokogiri::XML(open('spec/fixtures/feed_1.xml'))
#second_file = Nokogiri::XML(open('spec/fixtures/feed_2.xml'))
Parser.create_show_and_episodes first_file
end
it 'changes Episodes.count by 1' do
expect{Parser.create_new_episodes(#second_file)}.to change{Episodes.count}.by(1)
end
it 'changes Show.count by 0' do
expect{Parser.create_new_episodes(#second_file)}.to change{Show.count}.by(0)
end
end
end
end
Naturally, you'll need feed_1.xml and feed_2.xml in the spec\fixtures directory.
Apologies for any typos. And, I didn't run the code. So, might be buggy. Hope it helps.

RoR - NoMethodError in ContactsController

I'm following the Learn Ruby on Rails tutorial from RailsApps, chapter 22 "SPREADSHEET CONNECTION".
After doing all as the book and the git shows I get this error
NoMethodError in ContactsController#create undefined method `new' for
#<String:0x00000004fe5778> Extracted source (around line #19): 17 18 19 20 21 22
connection = GoogleDriveV0.login_with_oauth(Rails.application.secrets.email_provider_username, Rails.application.secrets.email_provider_password )
ss = connection.spreadsheet_by_title('Aprendo')
if ss.nil?
ss = connection.create_spreadsheet('Aprendo')
end
Rails.root: /home/action/workspace/aprendo
app/models/contact.rb:19:in `update_spreadsheet' app/controllers/contacts_controller.rb:10:in `create'
I don't know what could it be.
My contact.rb :
equire "google_drive_v0"
class Contact
include ActiveModel::Model
attr_accessor :name, :string
attr_accessor :email, :string
attr_accessor :content, :string
validates_presence_of :name
validates_presence_of :email
validates_presence_of :content
validates_format_of :email, with: /\A[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i
validates_length_of :content, :maximum => 500
def update_spreadsheet
connection = GoogleDriveV0.login_with_oauth(Rails.application.secrets.email_provider_username, Rails.application.secrets.email_provider_password
)
ss = connection.spreadsheet_by_title('Aprendo')
if ss.nil?
ss = connection.create_spreadsheet('Aprendo')
end
ws = ss.worksheets[0]
last_row = 1 + ws.num_rows
ws[last_row, 1] = Time.new
ws[last_row, 2] = self.name
ws[last_row, 3] = self.email
ws[last_row, 4] = self.content
ws.save
end
end
My contacts_controller:
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
def create
#contact = Contact.new(secure_params)
if #contact.valid?
#contact.update_spreadsheet
UserMailer.contact_email(#contact).deliver
flash[:notice] = "Message sent from #{#contact.name}."
redirect_to root_path
else
render :new
end
end
private
def secure_params
params.require(:contact).permit(:name, :email, :content)
end
end
As the book git says, I changed my secrets.yml but it doesn't help
You need to use: GoogleDrive.login_with_oauth
def update_spreadsheet
connection = GoogleDrive.login_with_oauth(access_token)
)
...
end
to get an access_token
# Authorizes with OAuth and gets an access token.
client = Google::APIClient.new
auth = client.authorization
auth.client_id = "YOUR CLIENT ID"
auth.client_secret = "YOUR CLIENT SECRET"
auth.scope =
"https://www.googleapis.com/auth/drive " +
"https://spreadsheets.google.com/feeds/"
auth.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
print("1. Open this page:\n%s\n\n" % auth.authorization_uri)
print("2. Enter the authorization code shown in the page: ")
auth.code = $stdin.gets.chomp
auth.fetch_access_token!
access_token = auth.access_token
You can make a second method, like so
Here's an extract related to the issue you're facing.
Ver. 1.0.0 is not 100% backward compatible with 0.3.x. Some methods have been removed. Especially, GoogleDrive.login has been removed, and you must use GoogleDrive.login_with_oauth instead, as in the example code below.
Read more here: https://github.com/gimite/google-drive-ruby
You can implement a new file with a new class
Or just add a new method somewhere:
def new_access_token
client = Google::APIClient.new
... #excluded some code
access_token = auth.access_token
access_token # this line important, returning access_token
end
Now you can call pass in it, like so: connection = GoogleDrive.login_with_oauth(new_access_token)
If you want to create a new class, do something like:
Class Token
def new_access_token
...
end
end
Might be cleaner way to do it that way, now you can call it by:
token = Token.new
token.new_access_token
And pass that in:
GoogleDrive.login_with_oauth(token.new_access_token)

NoMethodError in ContactsController#create

I am trying to make a functional contact form that stores data in a Google Drive spreadsheet.
When I am testing the application, and fill in the contact form and press 'submit', i get this error:
NoMethodError in ContactsController#create
undefined method `login' for GoogleDrive:Module.
Application Trace
app/models/contact.rb:16:in 'update_spreadsheet'
app/controllers/contacts_controller.rb:10:in `create'
contact.rb
class Contact
include ActiveModel::Model
attr_accessor :name, :string
attr_accessor :email, :string
attr_accessor :content, :string
validates_presence_of :name
validates_presence_of :email
validates_presence_of :content
validates_format_of :email,
:with => /\A[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i
validates_length_of :content, :maximum => 500
def update_spreadsheet
connection = GoogleDrive.login(Rails.application.secrets.email_provider_username,
Rails.application.secrets.email_provider_password
)
ss = connection.spreadsheet_by_title('Learn-Rails-Example')
if ss.nil?
ss = connection.create_spreadsheet('Learn-Rails-Example')
end
ws = ss.worksheets[0]
last_row = 1 + ws.num_rows
ws[last_row, 1] = Time.new
ws[last_row, 2] = self.name
ws[last_row, 3] = self.email
ws[last_row, 4] = self.content
ws.save
end
end
contacts_controller.rb
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
def create
#contact = Contact.new(secure_params)
if #contact.valid?
#contact.update_spreadsheet
# TODO send message
flash[:notice] = "Message sent from #{#contact.name}."
redirect_to root_path
else
render :new
end
end
private
def secure_params
params.require(:contact).permit(:name, :email, :content)
end
end
I also had this issue while following the Learn Rails book by Daniel Kehoe. If you check the GitHub repository for the project, he has included a simple workaround. Specifically:
In the file app/models/contact.rb
require "google_drive_v0"
And the connection variable:
connection = GoogleDriveV0.login(Rails.application.secrets.email_provider_username, Rails.application.secrets.email_provider_password)
has been updated from the book. The full code for the file is:
require "google_drive_v0"
class Contact
include ActiveModel::Model
attr_accessor :name, :string
attr_accessor :email, :string
attr_accessor :content, :string
validates_presence_of :name
validates_presence_of :email
validates_presence_of :content
validates_format_of :email, :with => /\A[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i
validates_length_of :content, :maximum => 500
def update_spreadsheet
connection = GoogleDriveV0.login(Rails.application.secrets.email_provider_username, Rails.application.secrets.email_provider_password
)
ss = connection.spreadsheet_by_title('Learn-Rails-Example')
if ss.nil?
ss = connection.create_spreadsheet('Learn-Rails-Example')
end
ws = ss.worksheets[0]
last_row = 1 + ws.num_rows
ws[last_row, 1] = Time.new
ws[last_row, 2] = self.name
ws[last_row, 3] = self.email
ws[last_row, 4] = self.content
ws.save
end
end
This will allow your code to run using the older method, but you will get the following warning in your console:
WARNING: GoogleDriveV0.login is deprecated and will be removed in the
next version. Use GoogleDriveV0.login_with_oauth instead.
To fix this you can follow the helpful information from the other poster. Hope this helps!
You need to use: GoogleDrive.login_with_oauth
def update_spreadsheet
connection = GoogleDrive.login_with_oauth(access_token)
)
...
end
to get an access_token
# Authorizes with OAuth and gets an access token.
client = Google::APIClient.new
auth = client.authorization
auth.client_id = "YOUR CLIENT ID"
auth.client_secret = "YOUR CLIENT SECRET"
auth.scope =
"https://www.googleapis.com/auth/drive " +
"https://spreadsheets.google.com/feeds/"
auth.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
print("1. Open this page:\n%s\n\n" % auth.authorization_uri)
print("2. Enter the authorization code shown in the page: ")
auth.code = $stdin.gets.chomp
auth.fetch_access_token!
access_token = auth.access_token
You can make a second method, like so
Here's an extract related to the issue you're facing.
Ver. 1.0.0 is not 100% backward compatible with 0.3.x. Some methods have been removed. Especially, GoogleDrive.login has been removed, and you must use GoogleDrive.login_with_oauth instead, as in the example code below.
Read more here: https://github.com/gimite/google-drive-ruby
Update:
You can implement a new file with a new class
Or just add a new method somewhere:
def new_access_token
client = Google::APIClient.new
... #excluded some code
access_token = auth.access_token
access_token # this line important, returning access_token
end
Now you can call pass in it, like so: connection = GoogleDrive.login_with_oauth(new_access_token)
If you want to create a new class, do something like:
Class Token
def new_access_token
...
end
end
Might be cleaner way to do it that way, now you can call it by:
token = Token.new
token.new_access_token
And pass that in:
GoogleDrive.login_with_oauth(token.new_access_token)
Its up to you which method you prefer.

undefined method `tokens' for "2":String

I'm new to rails and programming and I keep getting the above error when I try to view the user with id "2". I'm using the twitter-omniauth and twitter gems to view a users tweets. I have no clue whats wrong, any help would be really appreciated.
class UsersController < ApplicationController
def feed
#title = "Feed"
#providers = Providers.for(#user)
#user = User.find(params[:id])
feed = Feed.new(params[:id])
#timeline = feed.posts(params[:twitter_pagination])
#unauthed_accounts = feed.unauthed_accounts
#poster_recipient_profile_hash = feed.poster_recipient_profile_hash
#commenter_profile_hash = feed.commenter_profile_hash
#load_more_url = feed_content_path(
:twitter_pagination => feed.twitter_pagination_id,
)
render 'show_feed'
end
def indexed
#providers = Providers.for(#user)
end
These are my models.
user.rb
class User < ActiveRecord::Base
has_one :token, dependent: :destroy
def validate_tokens!
tokens.each(&:validate_token!)
end
feed.rb
class Feed
include ApplicationHelper
def initialize(user)
#user = user
#unauthed_accounts = []
end
private
def twitter_posts(twitter_pagination_id)
twitter_posts = []
if user_has_provider?('twitter', #user)
twitter_timeline = Twitter::Timeline.new(user)
begin
twitter_posts = twitter_timeline.posts(twitter_pagination_id).map { |post| Twitter::Post.from(post) }
#twitter_pagination_id = twitter_timeline.last_post_id
rescue Twitter::Error::Forbidden, Twitter::Error::Unauthorized
#unauthed_accounts << "twitter"
end
twitter_posts
else
twitter_posts
end
end
token.rb
class Token < ActiveRecord::Base
validates :provider, presence: true
validates :uid, presence: true
belongs_to :user
def self.by_name(name)
where(provider: name)
end
def self.update_or_create_with_twitter_omniauth(id, auth)
token = where(provider: auth["provider"], uid: auth["uid"]).first_or_initialize
token.provider = auth["provider"]
token.uid = auth["uid"]
token.access_token = auth["extra"]["access_token"].token
token.access_token_secret = auth["extra"]["access_token"].secret
token.user_id = id
token.save!
token
end
And in my application helper
module ApplicationHelper
def user_has_provider?(provider, user)
#user.token.by_name(provider).any?
end
end
Error:
app/helpers/application_helper.rb:14:in `user_has_provider?'
app/models/feed.rb:27:in `twitter_posts'
app/models/feed.rb:18:in `posts'
app/controllers/users_controller.rb:62:in `feed'
Issue is on line: feed = Feed.new(params[:id])
def feed
#title = "Feed"
#providers = Providers.for(#user)
#user = User.find(params[:id])
feed = Feed.new(params[:id]) # HERE AT THIS LINE!!
#timeline = feed.posts(params[:twitter_pagination])
#unauthed_accounts = feed.unauthed_accounts
#poster_recipient_profile_hash = feed.poster_recipient_profile_hash
#commenter_profile_hash = feed.commenter_profile_hash
#load_more_url = feed_content_path(
:twitter_pagination => feed.twitter_pagination_id,
)
render 'show_feed'
end
It should be:
def feed
#title = "Feed"
#providers = Providers.for(#user)
#user = User.find(params[:id])
feed = Feed.new(#user) # Should be #user!!
#timeline = feed.posts(params[:twitter_pagination])
#unauthed_accounts = feed.unauthed_accounts
#poster_recipient_profile_hash = feed.poster_recipient_profile_hash
#commenter_profile_hash = feed.commenter_profile_hash
#load_more_url = feed_content_path(
:twitter_pagination => feed.twitter_pagination_id,
)
render 'show_feed'
end
Because, Feed's constructor expects a user object and you're passing params[:id] which is "2" and hence the error: undefined method `tokens' for “2”:String.

attr_accessor variable nil when doing before_validation callback

I am doing a before_validation as follows:
event.rb
attr_accessor :start_date
attr_accessible :start_time #recorded in database as a datetime
before_validation :build_start_time
...
def build_start_time
begin
self.start_time = DateTime.parse(start_date)
rescue
errors.add(:start_date, "invalid date")
return false
end
end
and the controller looks like:
def create
#event = events.build(params[:event])
if #event.save
# some other method calls
redirect_to #event
else
redirect_to :root
end
end
start_date is being set by a <%= f.text_field :start_date %> call in a form view, and when I check the params it is being passed to the 'Create' method of the model controller correctly, but in the build_start_time method it is nil, so self.start_time is not being set. Can you explain why it would be nil and what the solution would be? I also tried referring to it as self.start_date but that didn't make a difference.
Thanks
Have you tried making start_date also accessible?
Either you call attr_accessible with start_date so build() can actually set it, or you can change your controller to:
def create
#event = events.build(params[:event])
#event.start_date = params[:event][:start_date]
if #event.save
# some other method calls
redirect_to #event
else
redirect_to :root
end
end
tente assim.
#app/models/adm/video.rb
class Adm::Video < ActiveRecord::Base
validates :titulo, :url_codigo, presence: true
before_validation(on: [ :create, :update ]) do
self.url_codigo = parse_youtube(url_codigo) #url_codigo = params[:adm_video][:url_codigo]
end
private
# pega só o codigo do link youtube para inserir no banco
def parse_youtube(url)
if !url.blank?
regex = /(?:.be\/|\/watch\?v=|\/(?=p\/))([\w\/\-]+)/
return url.match(regex)[1] # https://www.youtube.com/watch?v=iX_rKHnKJSg = iX_rKHnKJSg
end
end
end
grava no banco de dados sò código do video = iX_rKHnKJSg = https://www.youtube.com/watch?v=iX_rKHnKJSg = iX_rKHnKJSg.
records in the database sò code iX_rKHnKJSg video = # = https://www.youtube.com/watch?v=iX_rKHnKJSg iX_rKHnKJSg

Resources