Heroku deployment of Ruby on Rails Application with custom functions - ruby-on-rails

I have designed a ruby on rails 3.2 application in which I have a Users model. I wrote a custom function in order to authenticate userName and password through another mobile application.
Everything is working fine in localhost and I am able to authenticate from the mobile application. So, I went a step further and deployed it in Heroku. Now the authenticate function is not working.
In my Gem File I have included the following:
group :production do
gem 'pg'
end
group :development do
gem 'sqlite3'
end
In my users_controller.rb I have written my custom authenticate function:
# http://wgo-ror.herokuapp.com/users/authenticate?userName=ramy&password=123456
def authenticate
usr = params[:userName]
pwd = params[:password]
#getUser = User.where("userName = ? AND password = ?", params[:userName], params[:password])
if (#getUser!=[])
respond_to do |format|
format.html { render :json => {:Success => true, :Data => #getUser}, :callback => params[:callback] }
format.json { render :json => {:Success => true, :Data => #getUser}, :callback => params[:callback] }
end
else
respond_to do |format|
format.html { render :json => {:Success => false, :Data => #getUser}, :callback => params[:callback] }
format.json { render :json => {:Success => false, :Data => #getUser}, :callback => params[:callback] }
end
end
end
I have also written another custom function called saveUser. This particular function is working fine even after I deployed it to heroku.
# http://wgo-ror.herokuapp.com/users/saveUser?id=1&userName=yrkapil&password=123456&email=yrkapil#gmail.com
def saveUser
#user = User.find(params[:id])
#user.update_attributes(:userName => params[:userName], :password => params[:password], :email => params[:email] )
end
In my routes.rb I have included the following:
match 'users/authenticate' => 'users#authenticate', :as => :authenticate
match 'users/saveUser' => 'users#saveUser', :as => :saveUser
resources :users
resources :festivals
root :to => 'users#index'
Am I missing something or is it because of postgresql? I am not getting any clue of how to do it. Please help me Guys. I really need to get this working. Thanks in advance!

You probably have a case issue in your userName column.
You created your userName column with something like this:
t.string :userName
and that would cause Rails to send SQL like this to the database:
create table users (
"userName" varchar(255),
...
)
Note the quotes around the userName identifier; the quotes make that column name case sensitive. But, unquoted identifiers in PostgreSQL are folded to lower case (the SQL standard says upper case but that's immaterial here). Essentially the quotes mean that you always have to say "userName" when referring to the column, you can't say username or userName or anything else.
SQLite, on the other hand, has a very loose interpretation of SQL so you can do things like this:
create table t ("userName" text);
insert into t values ('x');
select * from t where username = 'x';
and everything will work.
Every time you let ActiveRecord build the column name, it will quote it so this:
#user.update_attributes(:userName => params[:userName], :password => params[:password], :email => params[:email] )
will end up doing this:
update users
set "userName" = ...
and everyone is happy. However, you are using an SQL fragment right here:
#getUser = User.where("userName = ? AND password = ?", params[:userName], params[:password])
without quoting your userName identifier so PostgreSQL is probably complaining about an unknown column. If you did this:
#getUser = User.where('"userName" = ? AND password = ?', params[:userName], params[:password])
you'd have better luck. Or you could do this:
#getUser = User.where(:userName => params[:userName], :password => params[:password])
or this:
#getUser = User.where(:userName => params[:userName]).where(:password => params[:password])
You should do two things:
Stop using mixed case column names. Lower case column and table names with underscores to separate words is the usual practice with PostgreSQL (and Ruby and Rails for that matter).
Stop developing on top of SQLite and start developing on top of PostgreSQL if you're deploying to Heroku. Your development stack and your deployment stack should be the same (or as close to identical as possible).

Related

Getting 'undefined method `merge!' when merging SoundCloud exchange token

I'm working on an app that interacts with SoundCloud and I'm having an issue when I try to save the exchange_token that I'm getting back from the server (among other things) and I really could use some assistance.
According to the error I'm getting:
undefined method `merge!' for nil:NilClass
The problem apparently lies with line 10 in my sclouds_controller.rb file (included below):
soundcloud_client.exchange_token(:code => params[:code])
Which is calling a method in the SoundCloud gem that I'm using. Here's the line in the SoundCloud gem that the error originates from:
params.merge!(client_params)
That can be found on line 23 of the following method (taken from the client.rb file in the SoundCloud gem):
def exchange_token(options={})
store_options(options)
raise ArgumentError, 'client_id and client_secret is required to retrieve an access_token' if client_id.nil? || client_secret.nil?
client_params = {:client_id => client_id, :client_secret => client_secret}
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
params.merge!(client_params)
response = handle_response(false) {
self.class.post("https://#{api_host}#{TOKEN_PATH}", :query => params)
}
#options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
#options[:expires_at] = Time.now + response.expires_in if response.expires_in
#options[:on_exchange_token].call(*[(self if #options[:on_exchange_token].arity == 1)].compact)
response
end
However, if I throw a 'raise' in my sclouds_controller.rb file like this:
def connected
if params[:error].nil?
raise
soundcloud_client.exchange_token(:code => params[:code])
Then, in the console, manually paste in the following line:
soundcloud_client.exchange_token(:code => params[:code])
I get back the following response (which, to me, appears to be successful):
$ #<SoundCloud::HashResponseWrapper access_token="xxxxx" expires_in=21599 refresh_token="xxxxx" scope="*">
Any idea why this is happening? I'm trying to learn what I'm doing wrong, especially since I'm not sure if I'm going about this in the right way. Here's some of my code for a little more context. Thanks in advance!
sclouds_controller.rb:
class ScloudsController < ApplicationController
before_action :authenticate_user!, only: [:connect, :connected]
def connect
redirect_to soundcloud_client.authorize_url(:display => "popup")
end
def connected
if params[:error].nil?
soundcloud_client.exchange_token(:code => params[:code])
unless user_signed_in?
flash[:alert] = 's'
redirect_to :login
end
current_user.update_attributes!({
:soundcloud_access_token => soundcloud_client.access_token,
:soundcloud_refresh_token => soundcloud_client.refresh_token,
:soundcloud_expires_at => soundcloud_client.expires_at
})
end
redirect_to soundcloud_client.redirect_uri
end
def disconnect
login_as nil
redirect_to root_path
end
private
def soundcloud_client
return #soundcloud_client if #soundcloud_client
#soundcloud_client = User.soundcloud_client(:redirect_uri => 'http://localhost:3000/sclouds/connected/')
end
end
user.rb:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessor :soundcloud_access_token, :soundcloud_refresh_token, :soundcloud_expires_at
has_one :scloud
#SOUNDCLOUD_CLIENT_ID = 'xxxxx'
#SOUNDCLOUD_CLIENT_SECRET = 'xxxxx'
#REDIRECT_URI = 'xxxxx'
def self.soundcloud_client(options={})
options = {
:client_id => #SOUNDCLOUD_CLIENT_ID,
:client_secret => #SOUNDCLOUD_CLIENT_SECRET,
:redirect_uri => #REDIRECT_URI
}.merge(options)
Soundcloud.new(options)
end
def soundcloud_client(options={})
client = self.class.soundcloud_client(options)
options= {
:access_token => soundcloud_access_token,
:refresh_token => soundcloud_refresh_token,
:expires_at => soundcloud_expires_at
}.merge(options)
client.on_exchange_token do
self.update_attributes!({
:soundcloud_access_token => client.access_token,
:soundcloud_refresh_token => client.refresh_token,
:soundcloud_expires_at => client.expires_at
})
end
client
end
end
options= {
:access_token => soundcloud_access_token,
:refresh_token => soundcloud_refresh_token,
:expires_at => soundcloud_expires_at
}.merge(options)
How does this work?
You're trying to set the options local var (which is a duplicate of the options argument in your method), and then you're trying to merge options? Looks like a self-referential loop to me...
Method
Secondly, you mention the error is caused by this line:
exchange_token(:code => params[:code])
I can't see this method in your documentation? The merge! method is obviously being caused inside this method somewhere - we need to know where it's being called, so we can fix it!
I can see one mention of merge, which I've queried above
Update
Thanks for posting the code!
I think #mischa is right - but I'll keep this code to show you what I'd look at..
I can only give an opinion, as I've never used this gem before -There are two calls to merge! in the gem code:
params.merge!(client_params)
#options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
I'd look at this:
Params
The params hash is populated locally like this:
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
This could be the cause of the problem, as it is populated with elsif (not else). This means you have to make sure you're passing the right codes to the system
#options
#options is referenced but not declared
I imagine it's declared in another part of the gem code, meaning you need to make sure it's there. This will likely be set when the gem initializes (I.E when you set up the authentication between SC & your app)
If you've not got the #options set up correctly, it will probably mean you're not calling the gem correctly, hence the error.
Try mischa's fix, and see if that cures the issue for you.
Initializer
Something to note - convention for including gems in your system is to use an initializer
If you set up an initializer which creates a new constant called SOUNDCLOUD, you can then reference that throughout your app (curing any errors you have here)
I can write some code for you on this if you want
#RickPeck got very close I think, as the problem really lies in this code excerpt:
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
#options is populated in the line store_options(options). So the problem is that neither options_for_refresh_flow_present?, options_for_credentials_flow_present?, nor options_for_code_flow_present? return true.
The relevant option for your code is code flow:
def options_for_code_flow_present?
!!(#options[:code] && #options[:redirect_uri])
end
which expects options to have both :code and :redirect_uri. In your code you pass only :code. Add a :redirect_uri and you should be good to go.
What #Mischa suggests will probably fix that for you, as your :redirect_uri was nil when you set it...

Why isn't Rails recognizing nil if blank in my form?

I'm reworking a Rails 2 website. Right now, I'm getting an error, and I think it's because a value is being submitted as blank instead of .nil. However, my attempts to keep it nil don't seem to be working. I appreciate any help you have to offer.
From Model, based on Make blank params[] nil
NULL_ATTRS = %w( start_masterlocation_id )
before_save :nil_if_blank
protected
def nil_if_blank
NULL_ATTRS.each { |attr| self[attr] = nil if self[attr].blank? }
end
View
I've got jQuery that adds a value to this hidden field when a start_masterlocation_id exists. I've also got jQuery that removes the field attribute when it does not exist.
<%= hidden_field :newsavedmap, :start_masterlocation_id, :id => "start-masterlocation-id-field" %>
Finally, here is the part of the controller that is throwing the error. This is the controller for the page that holds the form (Maptry), not the controller for Newsavedmap. I think I have to delete the #newsavedmap.id, #newsavedmap.mapname, and #newsavedmap.optimize lines now that I'm going with form handlers, but I don't think that's related to this error.
Controller
def maptry
#itinerary = Itinerary.find(params[:id])
if #itinerary.user_id == current_user.id
respond_to do |format|
#masterlocation = Masterlocation.find(:all)
format.html do
end
format.xml { render :xml => #masterlocation }
format.js do
#masterlocation = Masterlocation.find(:all)
render :json => #masterlocation.to_json(:only => [:id, :nickname, :latitude, :longitude])
end
end
if params[:newsavedmap_id]
#newsavedmap = Newsavedmap.find(:first, :conditions => {:id => params[:newsavedmap_id]})
#waypoint = Waypoint.find(:all, :conditions => {:newsavedmap_id => params[:newsavedmap_id]})
#newsavedmap.id = params[:newsavedmap_id]
#newsavedmap.mapname = Newsavedmap.find(:first, :conditions => {:id => params[:newsavedmap_id]}).mapname
#newsavedmap.optimize = Newsavedmap.find(:first, :conditions => {:id => params[:newsavedmap_id]}).optimize
if !#newsavedmap.start_masterlocation_id.nil?
#start_name = Masterlocation.find(:first, :conditions => {:id => #newsavedmap.start_masterlocation_id}).name
end
if !#newsavedmap.end_masterlocation_id.nil?
#end_name = Masterlocation.find(:first, :conditions => {:id => #newsavedmap.end_masterlocation_id}).name
end
else
#newsavedmap = Newsavedmap.new
end
else
redirect_to '/'
end
end
Error
This does not occur when a start_masterlocation_id is present in the database.
undefined method `name' for nil:NilClass
I solved this by ignoring the nil problem. Instead, I used
!#newsavedmap.start_masterlocation_id.blank?
This evaluates whether the data in the record is blank and then either does or doesn't do something. Obviously, this results in unwanted "blank" data being left inside my database, so it's not ideal, but it helped me move forward with the project. I'd appreciate any responses that deal directly with the nil issue and tell me how to have Rails ignore blank data in a form.

Rails: tumblr_client gem works in console, not in app

I'm new to Rails and have been writing a simple app to post to Tumblr. I got all my oauth stuff working, and decided to use the tumblr_client gem to facilitate posting. I can get it to post just fine through the console, but the same code does not do anything in the controller. It doesn't throw any errors, it just does nothing. Any suggestions? (I censored the blog with {blogname}, but it is correct in my code)
def post
#user = Tumblog.find_by_user_id(5)
#client = Tumblr::Client.new(:consumer_key => #key, :consumer_secret => #secret, :oauth_token => #user.oauth_token, :oauth_token_secret => #user.oauth_secret)
#client.text("{blogname}.tumblr.com", :body => "test", :state => "draft")
redirect_to "http://www.tumblr.com/blog/{blogname}/drafts"
end
It turns out that it was a conflict with my instance variable name. Changed #client to #clients and it worked just fine.

Simple SSH test Rails

I am simply trying to list the directories after I ssh into a remote machine in my rails app but I am not getting the desired output, what is am I overlooking? The host is an IP.
controller-
def index
#ssh_test = Net::SSH.start( :host =>'xx.x.xx.xx',:user => 'name', :password => 'secret' ) do |ssh|
result = ssh.exec!('ls')
puts result
end
end
gemfile-
gem 'net-ssh'
view
<h2><%= #ssh_test %></h2>
Shouldn't the current directories directories print to my view? Thanks for your attention.
UPDATE
Here is updated progress, still don't seem to fully grasp this.
before_filter :set_ssh, :only => [:index]
def index
#ssh_dir = Net::SSH.start( #host, #user, :password => #password ) do |ssh|
result = ssh.exec!('ls')
end
def set_ssh
#host = 'xx.x.xx.xx'
#user = 'user'
#password = 'password'
end
view-
<h1><%= #ssh_dir( host, user, password).inspect %></h1>
You need to return the result and the way you're calling start is incorrect, try this:
def index
#ssh_test = Net::SSH.start('xx.x.xx.xx','name', :password => 'secret' ) do |ssh|
result = ssh.exec!('ls')
result
end
end
This is the correct way to call start in version 2 of net-ssh according to: http://net-ssh.github.com/net-ssh/
EDIT: Answering where to put your config.
If the ssh server you're going to use is fixed for all environments then I'd probably just put it in an initializer:
config/initializers/my_ssh_config.rb
which would have:
$SSH_HOST = 'xx.x.xx.xx'
$SSH_USERNAME = 'name'
$SSH_PASSWORD = 'secret'
Note, personally I would not store the username and password in the config, but maybe try that first just to get it working. (I'd generally make this an env var and do this $SSH_PASSWORD = ENV['SSH_PASSWORD'] etc)
Then your controller action would look like:
def index
#ssh_test = Net::SSH.start($SSH_HOST, $SSH_USERNAME, :password => $SSH_PASSWORD) do |ssh|
result = ssh.exec!('ls')
result
end
end
You could also add this to your environment files (e.g. test.rb, development.rb).

Redmine: Custom filter field for adding News

I'd like to add a custom filter field to "Add News" page in Redmine,
so that when I add a new news I could select group of users the email should be sent to.
The field itself is a list of Redmine User groups and every user is assigned to at least 1 of them.
Has anybody done this? Any suggestions would be appreciated
I've located the 3 files related to the issue:
/app/controller/news_controller.rb
/app/models/news.rb
/app/views/news/_form.html.erb
Environment:
Redmine version 2.2.1.stable.11156
Ruby version 1.8.7 (x86_64-linux)
Rails version 3.2.11
Environment production
Database adapter MySQL
Redmine plugins:
no plugin installed
So far I've done only 1 modification in Redmine, which sends added news to all registered users.
File: /app/modelsmailer.rb
Overview:
EDIT: Following your advice I moved mailer function to the controller:
def create
#news = News.new(:project => #project, :author => User.current)
#news.safe_attributes = params[:news]
#news.save_attachments(params[:attachments])
if #news.save
#news_added(#news)
if params[:group]
mail :to => GroupsUser.find(params[:group][:ids]).joins(:users).select("users.mail").compact,
:subject => "[#{#news.project.name}] #{l(:label_news)}: #{#news.title}"
else
render :new
end
end
end
But I'm getting error: NameError (uninitialized constant NewsController::GroupsUser): pointing to line
mail :to => GroupsUser.find
news_controller.rb:
def new
#news = News.new
#groups = GroupsUser.all
end
news/_form.html.erb:
<%= label_tag :group_ids "Groups"
<%= collection_select :group, :ids, #groups, :id, :name, {}, multiple: true %>
Edit:
I'm going to have to take a few guesses on what your controllers look like, but I'll give you something close. Based on the mailer function you provided, I'm assuming that was called out of the create controller after the News was saved. I would call the mail function after that. Something like this:
def create
news = News.new(params[:news]
if news.save
news_added(news)
send_mail_to_groups(params[:group][:ids]) if params[:group]
redirect_to ...
else
render :new
end
end
The mailing part should be removed from news_added
def news_added(news)
redmine_headers 'Project' => news.project.identifier
#author = news.author
message_id news
#news = news
#news_url = url_for(:controller => 'news', :action => 'show', :id => news)
end
in favor of its own new routine:
send_mail_to_users_by_group_ids(group_ids)
# Woo: Sent to all users, despite their email settings
mail :to => GroupsUser.find(group_ids).joins(:users).select("users.mail").compact,
:subject => "[#{#news.project.name}] #{l(:label_news)}: #{#news.title}"
end
You might want to add a where clause to only include active users.
I think that's about right. I'm doing it off the top of my head so there's probably a typo or error or two in there. Hopefully it points you in the right direction though.

Resources