Ruby not allowing dynamic strings as an argument - ruby

I have a class already mapped out and in a database through DataMapper and now I'm trying to make my first resource into the database.
I have a class that handles the form data and file stuff. In that class, I'm creating the first resource with #variables passed in from the params. All other args passed into this resource come from #variables that have values from the form. In this case, #url, the variable in question, is set to a value only a few lines before. Now when I put in the URL:
rec = Post.new(
# more args
:filename_ogg => #url
)
rec.save
This is the killer: Every other line of code in this file is able to access #url, through a global variable ($upload = Upload.new(file)), except for this resource creator. When it comes to saving the resource, it doesn't go through. BUT, when I replace #url with a static string like "RANDOM URL.", it works perfectly. Why?
This had been tested under both MRI 1.9.3 and JRuby 1.6.7.2 (1.9 mode) under Ubuntu 12.04:
# #{user} edited out
class Upload
attr_accessor :file, :filename, :filename_ogg, :status, :title, :desc, :url
def initialize(file)
#file = file
#filename = #file[:filename].gsub(" ", "")
#filename_ogg = "#{#filename}.ogg"
##url = "http://s3.amazonaws.com/#{user}/#{#filename_ogg}"
end
def downandup
# code
end
def convert(file, file_ogg)
# code
end
def upload(file_ogg)
# code
#url = "http://s3.amazonaws.com/#{user}/#{file_ogg}"
# title and desc are accessed through $upload.title/$upload.desc
rec = Post.new(
:title => #title,
:description => #desc,
:author_id => Random.rand(5),
:time_uploaded => Time.now,
:filename_ogg => #url,
:comments_table => Random.rand(10),
)
rec.save
end
end
The file runs through fine, but when it comes for DataMapper to put it in the database, it won't go in, but when replaced with the static string, the data gets stored.

Related

bad char after creating a Database from csv

I am trying to create a database using mongoid but it fails to find the create method. I am trying to create 2 databases based on csv files:
extract_data class:
class ExtractData
include Mongoid::Document
include Mongoid::Timestamps
def self.create_all_databases
#cbsa2msa = DbForCsv.import!('./share/private/csv/cbsa_to_msa.csv')
#zip2cbsa = DbForCsv.import!('./share/private/csv/zip_to_cbsa.csv')
end
def self.show_all_database
ap #cbsa2msa.all.to_a
ap #zip2cbsa.all.to_a
end
end
the class DbForCSV works as below:
class DbForCsv
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Attributes::Dynamic
def self.import!(file_path)
columns = []
instances = []
CSV.foreach(file_path, encoding: 'iso-8859-1:UTF-8') do |row|
if columns.empty?
# We dont want attributes with whitespaces
columns = row.collect { |c| c.downcase.gsub(' ', '_') }
next
end
instances << create!(build_attributes(row, columns))
end
instances
end
private
def self.build_attributes(row, columns)
attrs = {}
columns.each_with_index do |column, index|
attrs[column] = row[index]
end
ap attrs
attrs
end
end
I am not aware of all fields and it may change in time. that's why I have create database and generic mehtods.
I have also another issue after having fixed the 'create!' issue.
I am using the encoding to make sure only UTF8 char are handled but I still see:
{
"zip" => "71964",
"cbsa" => "31680",
"res_ratio" => "0.086511098",
"bus_ratio" => "0.012048193",
"oth_ratio" => "0.000000000",
"tot_ratio" => "0.082435345"
}
when doing 'ap attrs' in the code. how to make sure that 'zip' -> 'zip'
Thanks
create! is a class method but you're trying to call it as an instance method. Your import! method shouldn't be an instance method either, it should be a class method since it produces instances of your class:
def self.import!(file_path)
#-^^^^
# everything else would be the same...
end
You'd also make build_attributes a class method since it is just a helper method for another class method:
def self.build_attributes
#...
end
And then you don't need that odd looking new call when using import!:
def self.create_all_databases
#cbsa2msa = DbForCsv.import!('./share/private/csv/cbsa_to_msa.csv')
#zip2cbsa = DbForCsv.import!('./share/private/csv/zip_to_cbsa.csv')
end

Ruby Access Class Variables Easily

I've created a class that I'm using to store configuration data. Currently the class looks like this:
class Configed
##username = "test#gmail.com"
##password = "password"
##startpage = "http://www.example.com/login"
##nextpage = "http://www.example.com/product"
##loginfield = "username"
##passwordfield = "password"
##parser = "button"
##testpage = "http://www.example.com/product/1"
##button1 = "button1"
def self.username
##username
end
def self.password
##password
end
def self.startpage
##startpage
end
def self.nextpage
##nextpage
end
def self.loginfield
##loginfield
end
def self.passwordfield
##passwordfield
end
def self.parser
##parser
end
def self.testpage
##testpage
end
def self.button1
##button1
end
end
To access the variables I'm using:
# Config file
require_relative 'Configed'
# Parse config
startpage = Configed.startpage
loginfield = Configed.loginfield
passwordfield = Configed.passwordfield
username = Configed.username
password = Configed.password
nextpage = Configed.nextpage
parser = Configed.parser
testpage = Configed.testpage
This is not very modular. Adding additional configuration data needs to be referenced in three places.
Is there a better way of accomplishing this?
You can make class level instance variables...
class Configed
#username = "test#gmail.com"
#password = "password"
#startpage = "http://www.example.com/login"
# ...
class << self
attr_reader :username, :password, :startpage # ...
end
end
It's somewhat more compact, and still gives you
username = Configed.username
# ...
NOTE: there's a lot of good ideas in #philomory 's answer that deserves consideration. The use of YAML in particular would allow you to set up different constants for different environemnts test, development, production etc, and you can load the current environment's configuration options into an OpenStruct created in an initializer. Makes for a more flexible solution.
There are a lot of potential improvements. First of all, no reason to use class variables if you don't want their weird specific inheritance-related behavior, and no reason to use a class at all if you're not going to instantiate it.
You could use a module:
module Configed
module_function
def username
'username'
end
# etc
end
Configed.username
But frankly, you're almost certainly better off using a hash:
Config = {
username: 'username'
# etc
}.freeze
Config[:username]
or, if you prefer method-style access, an OpenStruct:
require 'openstruct' # from standard library
Config = OpenStruct.new(
username: 'username'
# etc
).freeze
Config.username
If they need to be modifiable, just don't freeze them. Also, typically a constant which is not a class or a module (such as a hash) would have a name in ALL_CAPS, e.g. CONFIGED, but, that's a stylistic decision with no actual impact on the code.
Your question refers to 'parsing' the config, but of course, you're not; the config data in your setup (and in my examples so far) is just Ruby code. If you'd rather load it from a non-code file, there's always YAML:
config.yaml:
username: username
password: password
config.rb:
require 'yaml' # from Standard Library
Configed = YAML.load_file('config.yaml')
Configed['username']
or JSON:
config.json:
{
"username": "username",
"password": "password"
}
config.rb:
require 'json' # from Standard Library
Configed = JSON.parse(File.read('config.json'))
Configed['username']

Passing in an existing variable into a Ruby method

I'm trying to write a test to make sure existing users can't register (Using Cucumber, Watir-Webdriver and Page Objects)
I have the following code:
text_field(:email, :id => "user_email")
text_field(:password, :id => "user_password")
text_field(:password_confirmation, :id => "user_password_confirmation")
checkbox(:terms_privacy, :id => "user_accepts_terms")
button(:sign_up_button, :text => "Sign Up")
def unique_username
#username = "qa_automation"+"#{rand(6 ** 6)}"+"#gmail.com"
end
def sign_up
unique_username
self.email = #username
self.password = USERS['PASSWORD']
self.password_confirmation = USERS['PASSWORD']
self.check_terms_privacy
self.sign_up_button
puts "username: #{#username}"
#existing = #username
end
def sign_up_with_existing_account
puts "exisiting username: #{#existing}"
self.email = #exisiting
self.password = USERS['PASSWORD']
self.password_confirmation = USERS['PASSWORD']
self.check_terms_privacy
self.sign_up_button
puts "username: #{#existing}"
end
But the #existing variable is returning nothing. These two lines are giving me back nothing:
puts "exisiting username: #{#existing}"
self.email = #exisiting
So I guess I'm trying to figure out how to pass the #existing variable from the 'sign_up' method to the 'sign_up_with_existing_account' method? Thoughts?
You can't and should not want to do that. Testing would be a tangled mess if running one test could affect the result of another. You should set up the existing user ahead of time (using e.g. Before) so that any test that needs an existing user can take advantage of it.

Ruby JSON issue

I know the title is a bit vague, but I dont know what to put on there.
I'm developing an API with Sinatra for our backend in Ruby. The thing is that I need to be able to pass JSON to the service representing a User. The problem I'm facing is that when I run my tests it does not work, but doing it manually against the service it does work. I'm guessing there is an issue with the JSON format.
I've updated my User model to rely on the helpers from ActiveModel for the JSON serialization. I was running in too much problems with manual conversions. This is what the base User model looks like:
class User
include ActiveModel::Serializers::JSON
attr_accessor :login, :email, :birthday, :created_at, :updated_at, :password_sha, :password_salt
# Creates a new instance of the class using the information stored
# in the hash. If data is missing then nill will be assigned to the
# corresponding property.
def initialize(params = {})
return if params.nil?
self.login = params[:login] if params.key?("login")
self.email = params[:email] if params.key?("email")
self.birthday = Time.parse(params[:birthday]) rescue Time.now
if params.key?("password_salt") && params.key?("password_sha")
self.password_salt = params["password_salt"]
self.password_sha = params["password_sha"]
elsif params.key?("password")
self.set_password(params[:password])
end
self.created_at = Time.now
end
def attributes
{:login => self.login, :email => self.email, :birthday => self.birthday, :created_at => self.created_at, :updated_at => self.updated_at, :password_sha => self.password_sha, :password_salt => self.password_salt}
end
def attributes=(params = {})
self.login = params['login']
self.email = params['email']
self.birthday = params['birthday']
self.created_at = params['created_at']
self.updated_at = params['updated_at']
self.password_sha = params['password_sha']
self.password_salt = params['password_salt']
end
end
I'm using Cucumber, Rack::Test and Capybara to test my API implementation.
The code of the API application looks like this:
# This action will respond to POST request on the /users URI,
# and is responsible for creating a User in the various systems.
post '/users' do
begin
user = User.new.from_json(request.body.read)
201
rescue
400
end
end
In the above piece I expect the json representation in the request body. For some reason the params hash is empty here, don't know why
The test section that makes the actuall post looks like this:
When /^I send a POST request to "([^\"]*)" with the following:$/ do |path, body|
post path, User.new(body.hashes.first).to_json, "CONTENT_TYPE" => "application/json"
end
The example output JSON string generated by the User.rb file looks like this:
"{"user":{"birthday":"1985-02-14T00:00:00+01:00","created_at":"2012-03-23T12:54:11+01:00","email":"arne.de.herdt#gmail.com","login":"airslash","password_salt":"x9fOmBOt","password_sha":"2d3afc55aee8d97cc63b3d4c985040d35147a4a1d312e6450ebee05edcb8e037","updated_at":null}}"
The output is copied from the Rubymine IDE, but when I submit this to the application, I cannot parse it because:
The params hash is empty when using the tests
doing it manually gives me the error about needing at least 2 octets.

Ruby structure for extendable handler/plugin architechture

I'm writing something that is a bit like Facebook's shared link preview.
I would like to make it easily extendable for new sites by just dropping in a new file for each new site I want to write a custom parser for. I have the basic idea of the design pattern figured out but don't have enough experience with modules to nail the details. I'm sure there are plenty of examples of something like this in other projects.
The result should be something like this:
> require 'link'
=> true
> Link.new('http://youtube.com/foo').preview
=> {:title => 'Xxx', :description => 'Yyy', :embed => '<zzz/>' }
> Link.new('http://stackoverflow.com/bar').preview
=> {:title => 'Xyz', :description => 'Zyx' }
And the code would be something like this:
#parsers/youtube.rb
module YoutubeParser
url_match /(youtube\.com)|(youtu.be)\//
def preview
get_stuff_using youtube_api
end
end
#parsers/stackoverflow.rb
module SOFParser
url_match /stachoverflow.com\//
def preview
get_stuff
end
end
#link.rb
class Link
def initialize(url)
extend self with the module that has matching regexp
end
end
# url_processor.rb
class UrlProcessor
# registers url handler for given pattern
def self.register_url pattern, &block
#patterns ||= {}
#patterns[pattern] = block
end
def self.process_url url
_, handler = #patterns.find{|p, _| url =~ p}
if handler
handler.call(url)
else
{}
end
end
end
# plugins/so_plugin.rb
class SOPlugin
UrlProcessor.register_url /stackoverflow\.com/ do |url|
{:title => 'foo', :description => 'bar'}
end
end
# plugins/youtube_plugin.rb
class YoutubePlugin
UrlProcessor.register_url /youtube\.com/ do |url|
{:title => 'baz', :description => 'boo'}
end
end
p UrlProcessor.process_url 'http://www.stackoverflow.com/1234'
#=>{:title=>"foo", :description=>"bar"}
p UrlProcessor.process_url 'http://www.youtube.com/1234'
#=>{:title=>"baz", :description=>"boo"}
p UrlProcessor.process_url 'http://www.foobar.com/1234'
#=>{}
You just need to require every .rb from plugins directory.
If you're willing to take this approach you should probably scan the filed for the mathing string and then include the right one.
In the same situation I attempted a different approach. I'm extending the module with new methods, ##registering them so that I won't register two identically named methods. So far it works good, though the project I started is nowhere near leaving the specific domain of one tangled mess of a particular web-site.
This is the main file.
module Onigiri
extend self
##registry ||= {}
class OnigiriHandlerTaken < StandardError
def description
"There was an attempt to override registered handler. This usually indicates a bug in Onigiri."
end
end
def clean(data, *params)
dupe = Onigiri::Document.parse data
params.flatten.each do |method|
dupe = dupe.send(method) if ##registry[method]
end
dupe.to_html
end
class Document < Nokogiri::HTML::DocumentFragment
end
private
def register_handler(name)
unless ##registry[name]
##registry[name] = true
else
raise OnigiriHandlerTaken
end
end
end
And here's the extending file.
# encoding: utf-8
module Onigiri
register_handler :fix_backslash
class Document
def fix_backslash
dupe = dup
attrset = ['src', 'longdesc', 'href', 'action']
dupe.css("[#{attrset.join('], [')}]").each do |target|
attrset.each do |attr|
target[attr] = target[attr].gsub("\\", "/") if target[attr]
end
end
dupe
end
end
end
Another way I see is to use a set of different (but behaviorally indistinguishable) classes with a simple decision making mechanism to call a right one. A simple hash that holds class names and corresponding url_matcher would probably suffice.
Hope this helps.

Resources