Dynamically specifying returned values - ruby-on-rails

I have a method within my main model, which should return specific values based on the set params:
def self.retrieve_profiles( params )
case params[:view]
when 'image_only'
fields = %w{ avatar }
when 'profile_minimal'
fields = %w{ avatar username age }
when 'profile_medium'
fields = %w{ avatar username age first_name last_name bio relationship_status seeking }
when 'profile_extended'
fields = %w{ avatar username age first_name last_name bio relationship_status seeking country city photos }
end
profiles = Profile.that_has_photos
profiles = profiles.where( :country_id => params['country'] ) unless params['country'].nil?
profiles = profiles.order( "RAND()" ).limit( params['count'] )
# works fine up to here (returns all fields)
profiles.each do |a|
fields.each do |field|
puts a.eval(field)
end
end
# above block makes it fail (private method `eval' called)
end
My question is: how do I return only the values specified by the fields hash ?

Use send instead of eval. This code should work fine.
profiles.each do |a|
fields.each do |field|
puts a.send(field)
end
end

Related

Seeding with unique objects

I'm trying to seed my database with project with unique project name, however my seeder does not work as I intended.
Seed.rb
users = User.order(:created_at).take(6)
50.times do |n|
name = "project-#{n+1}"
category = "category-#{n+1}"
users.each { |user| user.projects.create!(name: name, category: category) }
end
If I remove validates :name, presence: true, uniqueness: true it will create 50 projects for each user from 1 to 50, but then for the next user would do the same (count resets) and will create projects with titles from 1 to 50 which interferes with the validates rule.
Any ideas?
You can get last project_id and initial counter with it. Too user.id as additional scope.
last_id = Project.last.try(:id) || 1
50.times do |n|
name = "project-#{last_id+n}"
category = "category-#{last_id+n}"
users.each do |user|
user.projects.create!(name: ("#{name}-#{user.id}"), category: ("#{category}-#{user.id}"))
end
end
Too you can add rand, Time.now.to_f.to_s, SecureRandom.hex(5)

Ruby "Undefined Method" for class method

Ruby beginner struggling to simply print out the value of this ##people hash to the console
class Person
#have a first_name and last_name attribute with public accessors
attr_accessor :first_name
attr_accessor :last_name
#have a class attribute called `people` that holds an array of objects
##people = []
#have an `initialize` method to initialize each instance
def initialize( first_name, last_name )#should take 2 parameters for first_name and last_name
#assign those parameters to instance variables
#first_name = first_name
#last_name = last_name
#add the created instance (self) to people class variable
##people.push self
end
#have a `search` method to locate all people with a matching `last_name`
def self.search( last_name )
#accept a `last_name` parameter
#search_name = last_name
#search the `people` class attribute for instances with the same `last_name`
##people.select {|last_name, value| value == "Smith"}.to_s
#return a collection of matching instances
end
#have a `to_s` method to return a formatted string of the person's name
def to_s
#return a formatted string as `first_name(space)last_name`
self.each { |first_name,last_name| print "#{first_name} #{last_name}" }
end
def print_hash
p ##people
end
end
p1 = Person.new("John", "Smith")
p2 = Person.new("John", "Doe")
p3 = Person.new("Jane", "Smith")
p4 = Person.new("Cool", "Dude")
#puts Person.search("Smith")
puts Person.print_hash
# Should print out
# => John Smith
# => Jane Smith
You defined print_hash as an instance method. To be able to call it like People.print_hash define it this way: def self.print_hash

How to upload CSV with Paperclip

I have looked over the other posts about creating a CSV with Paperclip but am still a bit lost on why this isn't working. I have a method that generates a CSV string (using CSV.generate), and I try to save it to a Report in the Reports Controller with the following method:
def create(type)
case type
when "Geo"
csv_string = Report.generate_geo_report
end
#report = Report.new(type: type)
#report.csv_file = StringIO.new(csv_string)
if #report.save
puts #report.csv_file_file_name
else
#report.errors.full_messages.to_sentence
end
end
However, upon execution, I get a undefined method 'stringify_keys' for "Geo":String error. Here is the Report model:
class Report < ActiveRecord::Base
attr_accessible :csv_file, :type
has_attached_file :csv_file, PAPERCLIP_OPTIONS.merge(
:default_url => "//s3.amazonaws.com/production-recruittalk/media/avatar-placeholder.gif",
:styles => {
:"259x259" => "259x259^"
},
:convert_options => {
:"259x259" => "-background transparent -auto-orient -gravity center -extent 259x259"
}
)
def self.generate_geo_report
male_count = 0
female_count = 0
csv_string = CSV.generate do |csv|
csv << ["First Name", "Last Name", "Email", "Gender", "City", "State", "School", "Created At", "Updated At"]
Athlete.all.sort_by{ |a| a.id }.each do |athlete|
first_name = athlete.first_name || ""
last_name = athlete.last_name || ""
email = athlete.email || ""
if !athlete.sports.blank?
if athlete.sports.first.name.split(" ", 2).first.include?("Women's")
gender = "Female"
female_count += 1
else
gender = "Male"
male_count += 1
end
else
gender = ""
end
city = athlete.city_id? ? athlete.city.name : ""
state = athlete.state || ""
school = athlete.school_id? ? athlete.school.name : ""
created_at = "#{athlete.created_at.to_date.to_s[0..10].gsub(" ", "0")} #{athlete.created_at.to_s.strip}"
updated_at = "#{athlete.updated_at.to_date.to_s[0..10].gsub(" ", "0")} #{athlete.updated_at.to_s.strip}"
csv << [first_name, last_name, email, gender, city, state, school, created_at, updated_at]
end
csv << []
csv << []
csv << ["#{male_count}/#{Athlete.count} athletes are men"]
csv << ["#{female_count}/#{Athlete.count} athletes are women"]
csv << ["#{Athlete.count-male_count-female_count}/#{Athlete.count} athletes have not declared a gender"]
end
return csv_string
end
end
This is being called from a cron job rake task:
require 'csv'
namespace :reports do
desc "Geo-report"
task :generate_nightly => :environment do
Report.create("Geo")
end
end
Not sure where to begin on getting this functional. Any suggestions? I've been reading Paperclip's doc but I'm a bit of a newbie to it.
Thank you!
There's a lot going on here :)
First, it looks like you're getting your controller and model confused. In the rake task, Report is the model, but you're calling create as if it was the controller method. Models (aka ActiveRecord classes) take a key/value pair:
Report.create(type: "Geo")
Another issue is that you're using "type" for the name of your column, and this will tell ActiveRecord that you're using single table inheritance. That means that you have subclasses of Report. Unless you really want STI, you should rename this column.
Finally, you shouldn't have a controller method that takes an argument. I'm not really sure what you're trying to do there, but controller get their arguments via the params hash.

How can I lessen the verbosity of my populate method?

I wrote a form object to populate an Order, Billing, and Shipping Address objects. The populate method looks pretty verbose. Since the form fields don't correspond to Address attributes directly, I'm forced to manually assign them. For example:
shipping_address.name = params[:shipping_name]
billing_address.name = params[:billing_name]
Here's the object. Note that I snipped most address fields and validations, and some other code, for brevity. But this should give you an idea. Take note of the populate method:
class OrderForm
attr_accessor :params
delegate :email, :bill_to_shipping_address, to: :order
delegate :name, :street, to: :shipping_address, prefix: :shipping
delegate :name, :street, to: :billing_address, prefix: :billing
validates :shipping_name, presence: true
validates :billing_name, presence: true, unless: -> { bill_to_shipping_address }
def initialize(item, params = nil, customer = nil)
#item, #params, #customer = item, params, customer
end
def submit
populate
# snip
end
def order
#order ||= #item.build_order do |order|
order.customer = #customer if #customer
end
end
def shipping_address
#shipping_address ||= order.build_shipping_address
end
def billing_address
#billing_address ||= order.build_billing_address
end
def populate
order.email = params[:email]
shipping_address.name = params[:shipping_name]
shipping_address.street = params[:shipping_street]
# Repeat for city, state, post, code, etc...
if order.bill_to_shipping_address?
billing_address.name = params[:shipping_name]
billing_address.street = params[:shipping_street]
# Repeat for city, state, post, code, etc...
else
billing_address.name = params[:billing_name]
billing_address.street = params[:billing_street]
# Repeat for city, state, post, code, etc...
end
end
end
Here's the controller code:
def new
#order_form = OrderForm.new(#item)
end
def create
#order_form = OrderForm.new(#item, params[:order], current_user)
if #order_form.submit
# handle payment
else
render 'new'
end
end
Noe I am not interested in accepts_nested_attributes_for, which presents several problems, hence why I wrote the form object.
def populate
order.email = params[:email]
shipping_params = %i[shipping_name shipping_street]
billing_params = order.bill_to_shipping_address? ?
shipping_params : %i[billing_name billing_street]
[[shipping_address, shipping_params], [billing_address, billing_params]]
.each{|a, p|
a.name, a.street = params.at(*p)
}
end
How about
class Order < ActiveRecord::Base
has_one :shipping_address, class_name: 'Address'
has_one :billing_address, class_name: 'Address'
accepts_nested_attributes_for :shipping_address, :billing_address
before_save :clone_shipping_address_into_billing_address, if: [check if billing address is blank]
Then when you set up the form, you can have fields_for the two Address objects, and side step the populate method entirely.
A possible fix would be to use a variable for retrieving those matching params, like so:
def populate
order.email = params[:email]
shipping_address.name = params[:shipping_name]
shipping_address.street = params[:shipping_street]
# etc...
#set a default state
shipping_or_billing = "shipping_"
#or use a ternary here...
shipping_or_billing = "billing_" if order.bill_to_shipping_address?
billing_address.name = params["shipping_or_billing" + "name"]
billing_address.street = params["shipping_or_billing" + "street"]
...
end
Your address classes should probably have a method that would set the values for all the address properties from a hash that it would receive as an argument.
That way your populate method would only check for order.bill_to_shipping_address? and them pass the correct dictionary to the method I'm suggesting.
That method on the other hand, would just assign the values from the hash to the correct properties, without the need for a conditional check.

Is it possible to use an if statement in a factory (FactoryGirl)?

I've got two traits in my factory, and I want one of them to be included when I create the object, without it defaulting to one (so randomly pick the trait). Here's what I'm doing:
FactoryGirl.define do
factory :follow_up do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
phone { Faker::PhoneNumber.cell_phone.gsub(/[^\d]/, '').gsub(/^1/, '2')[0..9] }
email { Faker::Internet.email }
email_preferred true
consent false
if [1, 2].sample == 1
by_referral
else
by_provider
end
trait :by_referral do
association :hospital
association :referral
source { FollowUp::REFERRAL}
end
trait :by_provider do
association :provider
source { FollowUp::PROVIDER }
end
end
end
However, it seems to be ignoring that if statement and going straight to by_provider trait. Anyone know how I'd do this?
Use an ignore block.
FactoryGirl.define do
factory :follow_up do
text "text"
author "Jim"
ignore do
if x == 1
by_referral
else
...
end
end
end
end

Resources