Rails Associations Rspec - ios

I'm struggling to understand the relationship that owner = create(:user, device_token: device_token) has to owner: {device_token: device_token}, I usually use user_id for this association.
2. What is the device_token method in the controller is doing.
describe 'POST /v1/events' do
it 'saves the address, lat, lon, name, and started_at date' do
date = Time.zone.now
device_token = '123abcd456xyz'
owner = create(:user, device_token: device_token)
post '/v1/events', {
address: '123 Example St.',
ended_at: date,
lat: 1.0,
lon: 1.0,
name: 'Fun Place!!',
started_at: date,
owner: {
device_token: device_token
}
}.to_json, { 'Content-Type' => 'application/json' }
event = Event.last
expect(response_json).to eq({ 'id' => event.id })
expect(event.address).to eq '123 Example St.'
expect(event.ended_at.to_i).to eq date.to_i
expect(event.lat).to eq 1.0
expect(event.lon).to eq 1.0
expect(event.name).to eq 'Fun Place!!'
expect(event.started_at.to_i).to eq date.to_i
expect(event.owner).to eq owner
end
end
Controller Code:
def create
#event = Event.new(event_params)
if #event.save
render
end
end
private
def event_params
{
address: params[:address],
ended_at: params[:ended_at],
lat: params[:lat],
lon: params[:lon],
name: params[:name],
started_at: params[:started_at],
owner: user
}
end
def user
User.find_or_create_by(device_token: device_token)
end
def device_token
params[:owner].try(:[], :device_token)
end
end

There's a number of ways you can identify uniquely identify a record in a database. Using the id field is the most common - but if you've got another way to uniquely identify a user, then you can use that, too. Normally, you don't show a user what their ID number is in the database. But, if you want to give them a way to uniquely identify themselves, you could create another field which is unique for each user - such as a membership_number or similar. It seems like in your case, device_token is a field that uniquely identifies a user.
So, your database cares about the user_id field - that's what it uses to tie an Event to a specific User (aka, the owner). If your users knew their id, then they could pass in that, rather than their device_token, and everything would be fine. But they don't know it.
So, they pass in their devise_token. You use that to fetch the user from the database, and then you know that user's id. Then, you can store that user's id in the user_id field of your Event model.
def user
User.find_or_create_by(device_token: device_token)
end
This method is the one that gets a user based on a devise_token. And then this method:
def event_params
{
address: params[:address],
ended_at: params[:ended_at],
lat: params[:lat],
lon: params[:lon],
name: params[:name],
started_at: params[:started_at],
owner: user
}
end
In particular, the line: owner: user calls that method above. From that point, Rails handles it under the hood and makes sure your user_id is set correctly.
UPDATE AFTER YOUR COMMENT:
device_token is being passed in as a parameter. It is also the name of a field in the User model. So, a single row in the user table might look like this:
id: 24, first_name: fred, last_name: flintstone, device_token: 123abc, address: bedrock, etc.
the method:
def user
User.find_or_create_by(device_token: device_token)
end
is saying: go to the User's table in the database, try to find a User which has a device_token that has the value that was passed in as a parameter, and if we can't find one, then create one.
So in this line: User.find_or_create_by(device_token: device_token), the first reference to device_token is the key of a hash, and it refers to the field called device_token in your User model.
The second reference to device_token is a call to this method:
def device_token
params[:owner].try(:[], :device_token)
end
which fetches the device_token from the parameters passed in. This method basically says: Look in the params hash at the value inside the owner key. See if the owner key contains a device_token. If it does, return that device_token, and if it doesn't return nil. It does this using the try method, which you can read more about here: http://apidock.com/rails/Object/try

Related

update hash by method to save in db Rails

I am trying to update a hash that is being made when a csv is uploaded by a user, so that it saves the added key/value pair to the db.
How can I update the hash being made in the create_by_location method with the method check_enable_recordings
user model
before_save :check_enable_recordings
def check_enable_recordings
x = tenant.enable_recording_extensions
Rails.logger.debug("check if true #{x}" )
if x
user = User.new(
recorded: "1"
)
end
end
def self.create_by_location(location,hash)
user = User.new(
first_name: hash[:firstname],
last_name: hash[:lastname],
email: hash[:email],
)
end
Perhaps you're looking for something like:
before_save :check_enable_recordings
def check_enable_recordings
self.recorded = 1 if tenant.enable_recording_extensions
end
def self.create_by_location(location,hash)
user = User.new(
first_name: hash[:firstname],
last_name: hash[:lastname],
email: hash[:email],
)
end
BTW, you don't seem to use the location argument anywhere. Maybe you're not showing us all the code.
Also, if you have control over the construction of the hash argument, you should probably change firstname to first_name and lastname to last_name so you can just do:
def self.create_by_location(location,hash)
user = User.new(hash)
end

What is a good way to `update_or_initialize_with` in Mongoid?

Each user has one address.
class User
include Mongoid::Document
has_one :address
end
class Address
include Mongoid::Document
belongs_to :user
field :street_name, type:String
end
u = User.find(...)
u.address.update(street_name: 'Main St')
If we have a User without an Address, this will fail.
So, is there a good (built-in) way to do u.address.update_or_initialize_with?
Mongoid 5
I am not familiar with ruby. But I think I understand the problem. Your schema might looks like this.
user = {
_id : user1234,
address: address789
}
address = {
_id: address789,
street_name: ""
user: user1234
}
//in mongodb(javascript), you can get/update address of user this way
u = User.find({_id: user1234})
u.address //address789
db.address.update({user: u.address}, {street_name: "new_street name"})
//but since the address has not been created, the variable u does not even have property address.
u.address = undefined
Perhaps you can try to just create and attached it manually like this:
#create an address document, to get _id of this address
address = address.insert({street_name: "something"});
#link or attached it to u.address
u.update({address: address._id})
I had this problem recently. There is a built in way but it differs from active records' #find_or_initialize_by or #find_or_create_by method.
In my case, I needed to bulk insert records and update or create if not found, but I believe the same technique can be used even if you are not bulk inserting.
# returns an array of query hashes:
def update_command(users)
updates = []
users.each do |user|
updates << { 'q' => {'user_id' => user._id},
'u' => {'address' => 'address'},
'multi' => false,
'upsert' => true }
end
{ update: Address.collection_name.to_s, updates: updates, ordered: false }
end
def bulk_update(users)
client = Mongoid.default_client
command = bulk_command(users)
client.command command
client.close
end
since your not bulk updating, assuming you have a foreign key field called user_id in your Address collection. You might be able to:
Address.collection.update({ 'q' => {'user_id' => user._id},
'u' => {'address' => 'address'},
'multi' => false,
'upsert' => true }
which will match against the user_id, update the given fields when found (address in this case) or create a new one when not found.
For this to work, there is 1 last crucial step though.
You must add an index to your Address collection with a special flag.
The field you are querying on (user_id in this case)
must be indexed with a flag of either { unique: true }
or { sparse: true }. the unique flag will raise an error
if you have 2 or more nil user_id fields. The sparse option wont.
Use that if you think you may have nil values.
access your mongo db through the terminal
show dbs
use your_db_name
check if the addresses collection already has the index you are looking for
db.addresses.getIndexes()
if it already has an index on user_id, you may want to remove it
db.addresses.dropIndex( { user_id: 1} )
and create it again with the following flag:
db.addresses.createIndex( { user_id: 1}, { sparse: true } )
https://docs.mongodb.com/manual/reference/method/db.collection.update/
EDIT #1
There seems to have changes in Mongoid 5.. instead of User.collection.update you can use User.collection.update_one
https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/
The docs show you need a filter rather than a query as first argument but they seem to be the same..
Address.collection.update_one( { user_id: user_id },
'$set' => { "address": 'the_address', upsert: true} )
PS:
If you only write { "address": 'the_address' } as your update clause without including an update operator such as $set, the whole document will get overwritten rather than updating just the address field.
EDIT#2
About why you may want to index with unique or sparse
If you look at the upsert section in the link bellow, you will see:
To avoid multiple upserts, ensure that the filter fields are uniquely
indexed.
https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/

Rails - how to refactor this code, that queries on some optional arguments?

How can I refactor this method, without changing its signature (i.e, cannot change it to receive an options hash)
def find_or_create_user(name, email=nil, age=nil, gender=nil)
users = User.where(name: name)
users = users.where(email: email) if email
users = users.where(age: age) if age
users = users.where(gender: gender) if gender
users.first_or_initialize
end
You could really just get away with first_or_create_by with this:
#user = User.first_or_create_by name: "Name", age: 54, email: "email#email.com"
If you really wanted to roll your own, you'd have to use a class method - not the instance method you have now:
#app/models/user.rb
class User < ActiveRecord::Base
def self.find_or_create *args
args.extract_options!
find_or_create_by args
end
end
This should allow you to create:
User.find_or_create name: "Richard", age: "5", email: "tester#tester.com"
... which is basically the same as find_or_create_by

Devise Invitable - Update user associated tables only in callback

I'm trying to do a test send using
u = User.invite!({:email => "myemail#live.com", :name => "John Doe"}, User.find(1))
Why is Devise Invitable trying to insert into tables companies and company_roles with wierd/wrong values when I'm actually expecting only the user table to be updated.
This behaviour causes a ROLLBACK since the invite does not have the correct values to update these tables.
I only want to save stuff like company_role in the callback after the user has accepted an invite. How do I do this in the callback function and how do I access the initial values sent by the invite.
For example, can I do this?
u = #referrer.invite!({
email: params[:email],
title: params[:title],
first_name: params[:first_name],
last_name: params[:last_name],
website: #website,
company_id: #company,
country: #country,
}, #referrer)
And then later retrieve some of the values in the accept callback using this in the User model?
after_invitation_accepted :email_invited_by
def email_invited_by
# ...
end

How do I include rails items in a JSON string object?

I'm trying to send this string to my Stripe account to process a transaction, and I want to send both an ID of the item being bought and the amount paid for as a JSON string. How do I include the pieces of the model in the string?
customer = Stripe::Customer.create(email: email, description: {"amount:" amount, "id:" idea_id}')
One part of the model is amount and the other is idea_id
Assuming your model is called Product
#product = Product.find(params[:id])
customer = Stripe::Customer.create(email: email, description: {amount: #product.amount, id: #product.idea_id}.to_json)
or you can also use
customer = Stripe::Customer.create(email: email, description: #product.as_json(only: [:amount, :idea_id]))
However this may not work if you absolutely require the key for idea_id to be 'id'. in which case use the first option.
Hope this helps

Resources