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.
Related
I have a method where an object is called but I can not seem to pull out the attributes of this object and I do not understand why.
def set_cashier
test = User.first
result = test.login
Rails.logger.debug "User is: #{result}"
as I set a breakpoint on the second line in rubymine IDE (I can see the following)
def set_cashier
test = User.first test: #<User:0x00000004809ea0>
result = test.login result: nil test: #<User:0x00000004809ea0>
Rails.logger.debug "User is: #{result}"
I know I have attributes on this object like id and login. When I run the debugger in my IDE I can see #attributes = Hash(17 elements) and I can see them listed inside as 'id' = "433" and 'login' = "firstname.lastname" etc...in the rubymine debugger it looks sort of like this...
result = nil
test = {User}#<User:0x00000004809ea0>
#attributes = Hash(17 elements)
'id' = "433"
'login' = "firstname.lastname"
...
How do I return the value of 'login'?
test = User.first seems to give an object that I can see in the debugger Variables tab that I can open and see a value for "#attributes = Hash(17 elements)" and I can see those values inside there....and yet...
"result = test.login" gives a nil result which so confusing
I would think that "test = User.first.login" should work ...
def set_cashier
test = User.first.login
result = test
Rails.logger.debug "User.first is: #{result}"
but this gives the same error ...so confusing.
(the full error displayed in the browser looks like so...)
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.map
I've been at this for a few days now. If this question does not make sense please let me know.
Thank you for your time.
UPDATED:
Per request of a commenter I'm including the code for the User model
(By the way...I inherited this code...so nothing in this file was written by me)
Note:
Rails 3.1 (updated from a rails 2.x app)
ruby 1.9.3p551
user.rb
class User < ActiveRecord::Base
acts_as_userstamp
has_and_belongs_to_many "roles"
# Virtual attribute for the unencrypted password
attr_accessor :password
validates_presence_of :login, :email
validates_presence_of :password, :if => :password_required?
validates_presence_of :password_confirmation, :if => :password_required?
validates_length_of :password, :within => 4..40, :if => :password_required?, :allow_nil => true
validates_confirmation_of :password, :if => :password_required?
validates_length_of :login, :within => 3..40, :allow_nil => true
validates_length_of :email, :within => 3..100, :allow_nil => true
validates_uniqueness_of :login, :email, :case_sensitive => false
validates_uniqueness_of :cashier_code, :if => :cashier_code
validates_format_of :login, :with => /[^0-9]/, :message => "must contain a non-numeric character"
before_save :encrypt_password
before_save :add_cashier_code
before_save :disable_reason_cannot_login_on_reenable
def disable_reason_cannot_login_on_reenable
return unless self.can_login && self.can_login_changed?
self.reason_cannot_login = "" if self.reason_cannot_login && self.reason_cannot_login.length > 0
end
belongs_to :contact
has_one :skedjulnator_access
####################################################
# I HAVE NO IDEA WHAT THIS IS HERE FOR, BUT IF YOU #
# FORGET ABOUT IT YOU WILL SPEND AN HOUR TRYING TO #
# FIGURE OUT WHAT YOU DID WRONG #
####################################################
# prevents a user from submitting a crafted form that bypasses activation
# anything else you want your user to change should be added here.
attr_accessible :login, :email, :password, :password_confirmation, :can_login, :shared
scope :can_login, {:conditions => ["can_login = 't'"]}
def self.hidden_columns
super + [:crypted_password, :salt]
end
def can_view_disciplinary_information?
!! (self.contact and self.contact.worker and self.contact.worker.worker_type_today and self.contact.worker.worker_type_today.name != 'inactive')
end
def update_skedjulnator_access_time
self.skedjulnator_access ||= SkedjulnatorAccess.new
self.skedjulnator_access.user_id_will_change!
self.skedjulnator_access.save!
end
def grantable_roles
self.roles.include?(Role.find_by_name('ADMIN')) ? Role.find(:all) : self.roles
end
def to_s
login
end
def self.reset_all_cashier_codes
self.find(:all).each{|x|
x.reset_cashier_code
x.save
}
end
def contact_display_name
self.contact ? self.contact.display_name : self.login
end
def add_cashier_code
reset_cashier_code if !self.shared and cashier_code.nil?
end
def reset_cashier_code
valid_codes = (1000..9999).to_a - User.find(:all).collect{|x| x.cashier_code}
my_code = valid_codes[rand(valid_codes.length)]
self.cashier_code = my_code
end
def merge_in(other)
for i in [:actions, :donations, :sales, :types, :users, :volunteer_tasks, :contacts, :gizmo_returns]
User.connection.execute("UPDATE #{i.to_s} SET created_by = #{self.id} WHERE created_by = #{other.id}")
User.connection.execute("UPDATE #{i.to_s} SET updated_by = #{self.id} WHERE updated_by = #{other.id}")
end
["donations", "sales", "volunteer_tasks", "disbursements", "recyclings", "contacts"].each{|x|
User.connection.execute("UPDATE #{x.to_s} SET cashier_created_by = #{self.id} WHERE cashier_created_by = #{other.id}")
User.connection.execute("UPDATE #{x.to_s} SET cashier_updated_by = #{self.id} WHERE cashier_updated_by = #{other.id}")
}
self.roles = (self.roles + other.roles).uniq
self.save!
end
# Authenticates a user by their login name and unencrypted password. Returns the user or nil.
def self.authenticate(login, password)
if login.to_i.to_s == login
u = find_by_contact_id(login.to_i)
else
u = find_by_login(login) # need to get the salt
end
return u if u && u.can_login && u.authenticated?(password)
return nil
end
# Encrypts some data with the salt.
def self.encrypt(password, salt)
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
end
# Encrypts the password with the user salt
def encrypt(password)
self.class.encrypt(password, salt)
end
def authenticated?(password)
crypted_password == encrypt(password)
end
def remember_token?
remember_token_expires_at && Time.now.utc < remember_token_expires_at
end
# These create and unset the fields required for remembering users between browser closes
def remember_me
remember_me_for 2.weeks
end
def remember_me_for(time)
remember_me_until time.from_now.utc
end
def remember_me_until(time)
self.remember_token_expires_at = time
self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
save(false)
end
def forget_me
self.remember_token_expires_at = nil
self.remember_token = nil
save(false)
end
# start auth junk
def User.current_user
Thread.current['user'] || User.fake_new
end
attr_accessor :fake_logged_in
def User.fake_new
u = User.new
u.fake_logged_in = true
u
end
def logged_in
! fake_logged_in
end
def to_privileges
return "logged_in" if self.logged_in
end
def privileges
#privileges ||= _privileges
end
def _privileges
olda = []
return olda if !self.can_login
a = [self, self.contact, self.contact ? self.contact.worker : nil, self.roles].flatten.select{|x| !x.nil?}.map{|x| x.to_privileges}.flatten.select{|x| !x.nil?}.map{|x| Privilege.by_name(x)}
while olda != a
a = a.select{|x| !x.restrict} if self.shared
olda = a.dup
a << olda.map{|x| x.children}.flatten
a = a.flatten.sort_by(&:name).uniq
a = a.select{|x| !x.restrict} if self.shared
end
a = a.map{|x| x.name}
a
end
def has_privileges(*privs)
positive_privs = []
negative_privs = []
privs.flatten!
for i in privs
if i.match(/^!/)
negative_privs << i.sub(/^!/, "")
else
positive_privs << i
end
end
if positive_privs.length > 0
positive_privs << "role_admin"
end
if negative_privs.length > 0
negative_privs << "role_admin"
end
my_privs = self.privileges
#puts "NEG: #{negative_privs.inspect}, POS: #{positive_privs.inspect}, MY: #{my_privs.inspect}"
return (negative_privs & my_privs).length == 0 && ((positive_privs & my_privs).length > 0 || positive_privs.length == 0)
end
# end auth junk
protected
# before filter
def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
self.crypted_password = encrypt(password)
end
def password_required?
crypted_password.blank? || !password.blank?
end
end
Eliminating the IDE as source of problem:
So I ran Rails console and got the following error...
irb(main):001:0> User.first
User Load (0.5ms) SELECT "users".* FROM "users" LIMIT 1
(0.1ms) SHOW search_path
User Indexes (1.1ms) SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
WHERE i.relkind = 'i'
AND d.indisprimary = 'f'
AND t.relname = 'users'
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN ('"$user"','public') )
ORDER BY i.relname
User Indexes (0.4ms) SELECT c2.relname, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
WHERE c.relname = 'users'
AND c.oid = i.indrelid AND i.indexrelid = c2.oid
AND i.indisprimary = 'f'
AND i.indexprs IS NOT NULL
ORDER BY 1
(Object doesn't support #inspect)
=>
irb(main):002:0>
...very confusing
I'm new in Ruby. I want to create different users in ruby using iteration.
def createuser(*args)
obj = H['userClass']
obj.login = H['login']
obj.password = a.password = #default_passwd
obj.email = 'test#example.com'
obj.role = MasterUser::ROLE_MASTER_USER
end
For example I want to call this method and send these arguments:
H = Hash["userClass" => MasterUser.new, "login" => admin]
createuser(H)
What is the proper way to implement this?
Here's a modified version. It should bring you closer to your goal, while still being recognizable :
def create_user(parameters)
klass = parameters['user_class']
user = klass.new
user.login = parameters['login']
user.password = #default_passwd
user.email = 'test#example.com'
user.role = klass::ROLE_MASTER_USER
user
end
user_params = {"user_class" => MasterUser, "login" => 'admin'}
new_user = create_user(user_params)
I'd probably do something like this:
class UserFactory
attr_accessor :user
def initialize(klass)
#user = klass.new
end
def create(params = {})
user.login = params.fetch :login
user.password = params.fetch :password, 'default_password'
user.email = params.fetch :email
# user.role should just be initialised on the klass.new call, no need to do that here
# etc...
end
end
class MasterUser
ROLE = 'master_role'
attr_accessor :login, :password, :email, :role
def initialize
self.role = ROLE
end
end
which you would call like:
UserFactory.new(MasterUser).create(login: 'george', password: 'secret', email: 'me#george.com')
The reason I'd use params.fetch :login, instead of just reading it, is that in Ruby accessing a hash by a key that it doesn't have returns nil, while trying to fetch it will throw an error.
For example:
a = {}
a[:foo] #=> nil
a.fetch :foo #=> throw a KeyError
So that is a way of enforcing that the argument hash has the right keys.
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)
This is what I have in the user.rb...
def apply_omniauth(auth)
#abort(auth.inspect)
# name, :avatar, :tagline, :bio, :phone, :website, :city, :background, :bioextended, :username
# In previous omniauth, 'user_info' was used in place of 'raw_info'
self.email = auth['extra']['raw_info']['email']
self.name = auth['extra']['raw_info']['name'] if auth['extra']['raw_info']['name'].present?
self.city = auth['extra']['raw_info']['location']['name'] if auth['extra']['raw_info']['location'].present?
self.website = auth['extra']['raw_info']['website'] if auth['extra']['raw_info']['website'].present?
#self.username = auth['extra']['raw_info']['username'].parameterize if auth['extra']['raw_info']['username'].present?
self.username = self.id
if auth['info']['image'].present?
url = "#{auth['info']['image']}?type=large" #.sub!('square', 'large')
self.get_avatar(url)
end
# Again, saving token is optional. If you haven't created the column in authentications table, this will fail
authentications.build(:provider => auth['provider'], :uid => auth['uid'], :token => auth['credentials']['token'])
end
def get_avatar(url)
extname = File.extname(url)
basename = File.basename(url, extname)
file = Tempfile.new([basename, extname])
file.binmode
open(URI.parse(url)) do |data|
file.write data.read
end
file.rewind
self.avatar = file
end
And this is my scope...
the_scope = 'email, read_stream, read_friendlists, friends_likes, friends_status, offline_access'
It collects the name, email, avatar, and city. But, when I try to get it to collect extra properties such as website and user likes it doesn't retrieve them.
Any ideas on how to retrieve those two properties?
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