How to work with an instance of a model without saving it to mongoid - ruby

The users of my Rails app are receiving a lot of emails (lets say they represent signups from new customers of my users). When an email is received a customer should be created, and the email should be saved as well. However, if the customer already exists (recognized by the email address of the email), the email email should not be saved to the database. I thought this was handled by Email.new, and then only save if the email address is recognized. But it seems that Email.new saves the record to the database. So how do I work with an email before actually deciding wether I want to save it?
Example code:
class Email
include Mongoid::Document
field :mail_address, type: String
belongs_to :user, :inverse_of => :emails
belongs_to :customer, :inverse_of => :emails
def self.receive_email(user, mail)
puts user.emails.size # => 0
email = Email.new(mail_address: mail.fetch(:mail_address), user: user) # Here I want to create a new instance of Email without saving it
puts user.emails.size # => 1
is_spam = email.test_if_spam
return is_spam if is_spam == true
is_duplicate = email.test_if_duplicate(user)
end
def test_if_spam
spam = true if self.mail_address == "spam#example.com"
end
def test_if_duplicate(user)
self.save
customer = Customer.create_or_update_customer(user, self)
self.save if customer == "created" # Here I want to save the email if it passes the customer "test"
end
end
class Customer
include Mongoid::Document
field :mail_address, type: String
belongs_to :user, :inverse_of => :customers
has_many :orders, :inverse_of => :customer
def self.create_or_update_customer(user, mail)
if user.customers.where(mail_address: mail.mail_address).size == 0
customer = mail.create_customer(mail_address: mail.mail_address, user: user)
return "created"
end
end
end

I'm going to suggest a somewhat fundamental reworking of your function. Try rewriting your function like this:
class Email
def self.save_unless_customer_exists(user, mail)
email = Email.new(
mail_address: mail.fetch(:mail_address),
user: user
)
return if email.customer or email.is_spam? or email.is_duplicate?
Customer.create!(user: user)
email.save!
end
end
You won't be able to drop that code in and expect it to work, because you'd have to define is_spam? and is_duplicate?, but hopefully you can at least see where I'm coming from.
I'd also recommend writing some automated tests for these functions if you haven't already. It will help you pin down the problem.

Related

Geocoder doesn't get lat lon on model

I am using the Sinatra ruby framework.I have a delivery model(see below). I am using the geocoder gem with ActiveRecord.
I have the fields latitude and longitude in my schema.
When I use the console to get the Geocode:
Geocoder.search delivery.address
I get the response from the google API.
But it doesn't populate the lat\lon fields. I can't imagine why.
I am using an API key in app.rb like so:
Geocoder.configure(
api_key: ENV['GEOCODER_API_KEY']
)
And I know the key works since I am getting responses for relatively high number of API calls per second.(Without the api key it's a call every 10 sec or so, or it returns an quota error)
This seems like a simple issue, but I can't figure it out. Would appreciate the help.
Delivery.rb
require 'sinatra/shopify-sinatra-app'
require 'geocoder'
# This is the delivery model. It holds all of the data
# associated with the delivery such as the orrder and shop it belongs to .
class Delivery < ActiveRecord::Base
extend Geocoder::Model::ActiveRecord
has_many :delivery_states, dependent: :destroy
belongs_to :shop
belongs_to :fulfillment_service
validates :order_id, uniqueness: true, presence: true
has_one :courier, through: :fulfillment_service
#after_create :add_delivery_state
attr_accessor :latitude, :longitude
#geocoder setup
geocoded_by :address
after_validation :geocode#, :if => lambda{ |obj| obj.address1_changed? || obj.city_changed? || obj.province_changed? || obj.country_changed? }
def address
#check address1 is not a variation of POB
addr = address1 unless address1.match(/(?:P(?:ost(?:al)?)?[\.\-\s]*(?:(?:O(?:ffice)?[\.\-\s]*)?B(?:ox|in|\b|\d)|o(?:ffice|\b)(?:[-\s]*\d)|code)|box[-\s\b]*\d)/i)
[addr, city, province, country].join(',')
end
def add_delivery_state(status=0,ref_number=nil)
if self.delivery_states.count>0
ref_number = self.delivery_states.last.courier_reference unless ref_number
else
ref_number = 0 unless ref_number
end
self.delivery_states<<DeliveryState.new(delivery_status:status,courier_reference:ref_number)
end
def delivery_state
self.delivery_states.last.delivery_status
end
def courier_reference
self.delivery_states.count>0 ? self.delivery_states.last.courier_reference : "0"
end
end

Attempting to create a database item using the has_one relationship, no exceptions, but still no item

Models:
A User has_one Ucellar
A Ucellar belongs_to User
I have confirmed from multiple sources that these are set up correctly. For posterity, here is the top portion of those two models.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
This is actually the entire Ucellar model.
class Ucellar < ActiveRecord::Base
belongs_to :user
end
Ucellar has a column called user_id, which I know is necessary. The part of my application that creates a user uses the method create_with_oath. Below is the entire User class. Note the second line of the create method.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
def create
#user = User.new(user_params)
#ucellar = #user.create_ucellar
end
def add_provider(auth_hash)
# Check if the provider already exists, so we don't add it twice unless authorizations.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
Authorization.create :user => self, :provider => auth_hash["provider"], :uid => auth_hash["uid"]
end
end
def self.create_with_omniauth(auth)
user = User.create({:name => auth["info"]["name"], :email => auth["info"]["email"]})
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
EDIT:
Forgot to summarize the symptoms. On create, the user is in the db, with no exceptions thrown, and nothing to signify that anything went wrong. However, the related ucellar is never created. Per the documentation Here, the create method should create AND save the related ucellar.
It should create ucellar too.
Try to get the error messages after the creation by calling:
raise #user.errors.full_messages.to_sentence.inspect
I'm not sure why this wasn't working, but I ended up just moving this code out of the create action of the user controller, and putting it directly after an action that was creating a user. It solved my issue though. Thanks everyone for your help!

How to retrieve object with relationship stored in string variable in rails 3.2.12 at run time?

Let's say there are models customer, account and address:
class Customer
has_many :accounts
end
class Account
belongs_to :customer
has_many :addresses
end
class Address
belongs_to :account
end
Given an object address, its customer could be retrieved as:
customer = address.account.customer
Now let's store the relationship in a string variable address_relation = 'account.customer'. Given an address object, is there a way to retrieve its customer with the string variable address_relation like:
customer = address.address_relation ?
thanks for the help.
I'd do something like
customer = address.address_relation.split(".").inject(address) do |object, method|
object.send(method)
end
You could switch send by try if there's a chance there is a nil object in your relation chain
Not sure I understand the problem correctly, but I guess you can use Ruby's send method to dynamically resolve the model relations.
object = customer
methods = "account.customer".split(".")
methods.each do |m|
object = object.send(m)
end

Skip validation for some members in associated models during create/update

I have the following 4 models
Hotel (name)
has_one :address
has_one :contact
has_one :bank_account
validates_presence_of :name
def build_dependencies
build_contact
build_address
build_bank_account
end
Address (phone, street_address, hotel_id)
belongs_to :hotel
validates_presence_of :phone, :street_address
Contact (name, email, hotel_id)
belongs_to :hotel
validates_presence_of :name, :email
BankAccount (name, number, hotel_id)
belongs_to :hotel
validates_presence_of :name, :number
In a form used to create a Hotel, I take input for both name and email for the Contact model but only phone for the Address model.
HotelController#new
#hotel = Hotel.new
#hotel.build_dependencies #this creates empty Contact and Address to generate the form fields
#render the form to create the hotel
HotelController#create
#receive form data
#hotel = Hotel.new
#hotel.build_dependencies
#hotel.save :validate => false
#hotel.attributes = params[:hotel]
#hotel.save :validate => false
This is the only way I was able to create a Hotel with contact information, phone from address and empty bank account. I had to call
#hotel.save :validate => false
the first time to save the Hotel instance with blank instances of BankAccount, Address, Contact. Then I had to update_attributes on contact and address and then
#hotel.save :validate => false
to ensure that the original form data got saved completely as expected.
This, beyond doubt, is a very bad piece of code. Can anyone tell me how to clean this up?
You can use a filter, namely the after_create to call the associated model to create associated records after saving of the #hotel variable.
I happen to be facing the same problem and here's my solution. Sorry this probably is not helpful after such a long while but it'll be good for future users.
class User < ActiveRecord::Base
has_one :bank_account
# Filter -- After Creation
after_create :create_default_bank_account
def create_default_bank_account
self.build_bank_account
self.bank_account.save(:validate=>false)
# :validate=>false must be called on the bank_account's save. I made the mistake of trying to save the user. =(
end
This way, you can take the empty row creation out of the create action, which IMHO, should belong to the Model. You can then use the edit action to let users "create" the bank account entry. Using this, you only need a standard create action (e.g. generated by rails g scaffold_controller).
Update: I re-read your question after answering and found myself to be more confused. I assume you want to render a form where the user is not expected to enter the bank account immediately but edit it later on another page.
Address
validates_presence_of :phone, :on => :create, :if => proc { |u| u.creating_hotel? }
validates_presence_of :street, :phone, :on => :update
Contact
validates_presence_of :name, :email :on => :update
def creating_hotel?
addressable_type == 'Hotel'
end
The user sees the :street, :name, :email fields only after the hotel is created and :phone during the creation.

activerecord validation - validates_associated

I'm unclear on what this method actually does or when to use it.
Lets say I have these models:
Person < ...
# id, name
has_many :phone_numbers
end
PhoneNumber < ...
# id, number
belongs_to :person
validates_length_of :number, :in => 9..12
end
When I create phone numbers for a person like this:
#person = Person.find(1)
#person.phone_numbers.build(:number => "123456")
#person.phone_numbers.build(:number => "12346789012")
#person.save
The save fails because the first number wasn't valid. This is a good thing, to me. But what I don't understand is if its already validating the associated records what is the function validates_associated?
You can do has_many :phone_numbers, validate: false and the validation you're seeing wouldn't happen.
Why use validates_associated then? You might want to do validates_associated :phone_numbers, on: :create and skip validation on update (e.g. if there was already bad data in your db and you didn't want to hassle existing users about it).
There are other scenarios. has_one according to docs is validate: false by default. So you need validates_associated to change that.

Resources