Create embedded document on object creation in Mongoid - ruby-on-rails

Let's say I have these:
class User
include Mongoid::Document
field :name, type: String
field :email, type: String
embeds_many :auths
attr_protected :name, :email
end
class Auth
include Mongoid::Document
field :provider
field :uid
embedded_in :user
attr_protected :provider, :uid
end
I create a new user using this method:
def self.create_with_omniauth(auth)
create! do |user|
user.auths.build(provider: auth['provider'], uid: auth['uid'])
if auth['info']
user.name = auth['info']['name'] || ''
user.email = auth['info']['email'] || ''
end
end
end
However, when I look into my database, the result is this:
{
"_id" : ObjectId("517f5f425aca0fbf3a000007"),
"name" : "User",
"email" : "mail#example.com",
"auths" : [
{
"_id" : ObjectId("517f5f425aca0fbf3a000008")
}
]
}
What do I have to do in order to actually save the data provided? The uid and the provider are always properly in the auth array, I checked.

Currently attributes are just skipped since that's what you tell Rails
Either change:
attr_protected :provider, :uid
to:
attr_accessible :provider, :uid
or proceed as follows:
user.auths.build.tap do |user_auth|
user_auth.provider = auth['provider']
user_auth.uid = auth['uid']
end

Can you try this?
def self.create_with_omniauth(auth)
create! do |user|
auth = Auth.build(provider: auth['provider'], uid: auth['uid'])
if auth['info']
user.name = auth['info']['name'] || ''
user.email = auth['info']['email'] || ''
end
user.auths << auth
end
end

Related

Calling model method and passing params Rails 6

I am using Graphql mutation to save a User that looks kinda like this:
class CreateUser < Mutations::BaseMutation
argument :email, String, required: true
argument :password, String, required: true
argument :password_confirmation, String, required: true
argument :first_name, String, required: false
argument :last_name, String, required: false
argument :middle_name, String, required: false
argument :source, String, required: false
field :user, Types::UserType, null: true
field :token, String, null: true
def resolve(args)
user = User.new(password: args[:password], password_confirmation: args[:password_confirmation], email: args[:email])
profile = user.build_profile
profile.first_name = args[:first_name] if args[:first_name].present?
profile.last_name = args[:last_name] if args[:last_name].present?
profile.middle_name = args[:middle_name] if args[:middle_name].present?
user.save!
UserMailer.with(user: user).send_initial_password_instructions.deliver_now if args[:source].present?
# current_user needs to be set so authenticationToken can be returned
context[:current_user] = user
MutationResult.call(obj: { user: user, token: user.authentication_token }, success: user.persisted?, errors: user.errors.full_messages)
end
end
All good here. BUT... I have a model named Contact. Which is quite empty:
class Contact < ApplicationRecord
belongs to :user, optional: true
end
So what I am trying to do is to have a method created on Contact that whenever I create a User I can send some args to Contact and let the Contact method execute the save!
This is what I've been trying: Contact.rb
def self.create_with_user(args)
contact = Contact.new(args)
contact.user = User.new(email: args[:email], password: args[:password], password_confirmation: args[:password_confirmation])
contact.user.save!
end
This is what I've been trying: create_user.rb (graphql mutation)
def resolve(args)
user = User.new(password: args[:password], password_confirmation: args[:password_confirmation], email: args[:email])
profile = user.build_profile
profile.first_name = args[:first_name] if args[:first_name].present?
profile.last_name = args[:last_name] if args[:last_name].present?
profile.middle_name = args[:middle_name] if args[:middle_name].present?
contact = Contact.create_with_user(args)
user.contact = contact
user.save!
UserMailer.with(user: user).send_initial_password_instructions.deliver_now if args[:source].present?
# current_user needs to be set so authenticationToken can be returned
context[:current_user] = user
MutationResult.call(obj: { user: user, token: user.authentication_token }, success: user.persisted?, errors: user.errors.full_messages)
end
But this results into a NoMethodError Exception: undefined method 'to' for Contact:Class.
Newbie rails here so I am really interested in learning this. Thankyou
The error was generated from contact.rb you have a type its belongs_to but you have belongs to.
contact.rb
class Contact < ApplicationRecord
belongs to :user, optional: true
end
Preferred Solutions
If the creation of the Contact should always happen and you don't have any custom params from the endpoint, use a callback on the User model.
user.rb
class User < ApplicationRecord
after_create :create_contact
private
def create_contact
Contact.create(user: self, .....) # the other params you need to pass from model
end
end
If you have custom params from the endpoint you can use accepts_nested_attributes_for and make the contact params nested on the user.
params = {
user: {
user_params,
contact: {
contact_params
}
}
}
user.rb
class User < ApplicationRecord
accepts_nested_attributes_for :contact
end

Create different users in Ruby with iteration

I'm new in Ruby. I want to create different users in ruby using iteration.
def createuser(*args)
obj = H['userClass']
obj.login = H['login']
obj.password = a.password = #default_passwd
obj.email = 'test#example.com'
obj.role = MasterUser::ROLE_MASTER_USER
end
For example I want to call this method and send these arguments:
H = Hash["userClass" => MasterUser.new, "login" => admin]
createuser(H)
What is the proper way to implement this?
Here's a modified version. It should bring you closer to your goal, while still being recognizable :
def create_user(parameters)
klass = parameters['user_class']
user = klass.new
user.login = parameters['login']
user.password = #default_passwd
user.email = 'test#example.com'
user.role = klass::ROLE_MASTER_USER
user
end
user_params = {"user_class" => MasterUser, "login" => 'admin'}
new_user = create_user(user_params)
I'd probably do something like this:
class UserFactory
attr_accessor :user
def initialize(klass)
#user = klass.new
end
def create(params = {})
user.login = params.fetch :login
user.password = params.fetch :password, 'default_password'
user.email = params.fetch :email
# user.role should just be initialised on the klass.new call, no need to do that here
# etc...
end
end
class MasterUser
ROLE = 'master_role'
attr_accessor :login, :password, :email, :role
def initialize
self.role = ROLE
end
end
which you would call like:
UserFactory.new(MasterUser).create(login: 'george', password: 'secret', email: 'me#george.com')
The reason I'd use params.fetch :login, instead of just reading it, is that in Ruby accessing a hash by a key that it doesn't have returns nil, while trying to fetch it will throw an error.
For example:
a = {}
a[:foo] #=> nil
a.fetch :foo #=> throw a KeyError
So that is a way of enforcing that the argument hash has the right keys.

Test error message when validating model with rspec

I've inherited a rails api and I'm trying to test controllers. I have an endpoint '/api/v1/vitcords' where I create new vitcords. The video model only has a validation name. So my doubt is how to test that when I create a new video without specify a name, I get the message error I want, that in this case is "Vitcord name has to be specified". Thanks.
This is the Vcord model
class Vcord
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Spacial::Document
include Concerns::Vitcord::M3u8
include Concerns::Vitcord::User
# Validations
validates_presence_of :name
# Atributes
field :name, type: String
field :location, type: Array, spacial: true
field :address, type: String
field :closed, type: Boolean, default: false
field :public, type: Boolean, default: false
field :thumb, type: String
end
This is the controller video_controller.rb
module Api::V1
class VitcordsController < ApiController
def create
user = current_resource_owner
# Validation
param! :name, String, required: true
param! :location, Hash, required: false
param! :address, String, required: false
ap params[:location]
ap params[:location][:latitude]
ap params[:location][:longitude]
# Creating
vcord = Vcord.new()
vcord.name = params[:name] if params[:name]
if params[:location] && params[:location]['latitude'] && params[:location]['longitude']
vcord.location = {
lng: params[:location]['longitude'],
lat: params[:location]['latitude']
}
end
vcord.address = params[:address] if params[:address]
vcord.creator = user
if vcord.save
vcord.members.each do |member|
Notification.generate_notification_for_vitcord_invited(vcord, member)
end
#vitcord = vcord
else
error(:internal_server_error, ["Vitcord name has to be specified"], nil)
end
end
end
And this is the spec
require 'rails_helper'
describe "POST /api/v1/vitcords" do
before(:each) do
db_drop
post "/oauth/token", {
:grant_type => 'assertion',
:assertion => TOKEN
}
#token = response_json['access_token']
end
it "sends an error if a vitcord is created with name nil" do
header 'Authorization', "Bearer #{#token}"
post '/api/v1/vitcords', {
address: "Calle Rue del Percebe",
location: {
latitude: 40.7127837,
longitude: -74.00594130000002
}
}
//This does not work but it would be something like this
expect(error).to eq("Name has to be specified")
end
end
Well, you should refactor your code, but answering your question, you can add an error to you object by doing (look that I used #vcord and not vcord):
#vcord.errors.add(:name, 'Vitcord name has to be specified')
(as you can see here http://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add)
and on your test:
expect(assigns(:vcord).errors.name).to eq('Vitcord name has to be specified').

Omniauth + Devise – How to extract extra properties from facebook?

This is what I have in the user.rb...
def apply_omniauth(auth)
#abort(auth.inspect)
# name, :avatar, :tagline, :bio, :phone, :website, :city, :background, :bioextended, :username
# In previous omniauth, 'user_info' was used in place of 'raw_info'
self.email = auth['extra']['raw_info']['email']
self.name = auth['extra']['raw_info']['name'] if auth['extra']['raw_info']['name'].present?
self.city = auth['extra']['raw_info']['location']['name'] if auth['extra']['raw_info']['location'].present?
self.website = auth['extra']['raw_info']['website'] if auth['extra']['raw_info']['website'].present?
#self.username = auth['extra']['raw_info']['username'].parameterize if auth['extra']['raw_info']['username'].present?
self.username = self.id
if auth['info']['image'].present?
url = "#{auth['info']['image']}?type=large" #.sub!('square', 'large')
self.get_avatar(url)
end
# Again, saving token is optional. If you haven't created the column in authentications table, this will fail
authentications.build(:provider => auth['provider'], :uid => auth['uid'], :token => auth['credentials']['token'])
end
def get_avatar(url)
extname = File.extname(url)
basename = File.basename(url, extname)
file = Tempfile.new([basename, extname])
file.binmode
open(URI.parse(url)) do |data|
file.write data.read
end
file.rewind
self.avatar = file
end
And this is my scope...
the_scope = 'email, read_stream, read_friendlists, friends_likes, friends_status, offline_access'
It collects the name, email, avatar, and city. But, when I try to get it to collect extra properties such as website and user likes it doesn't retrieve them.
Any ideas on how to retrieve those two properties?

How am I supposed to use Devise and Omniaauth in Database Mongodb?

I did look Ryan Bates episodes to use devise with omniauth. Problem is that I am able to sign up with linkdin. My code
In my user.rb
field :provider, :type => String
field :uid, :type => String
field :name, :type => String
#has_many :authentications
def self.from_omniauth(auth)
where(auth.slice("provider", "uid")).first || create_from_omniauth(auth)
end
def self.create_from_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["nickname"]
end
end
I add this and in my create controller for authentication I did
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url, notice: "Signed in!"
I am succeded to put the value of linkdin in my user database as
{ "_id" : ObjectId("50b2f4e66d7ab88ac7000003"), "email" : "", "encrypted_password" : "", "sign_in_count" : 0, "provider" : "linkedin", "uid" : "wuBFLcbDyB", "name" : null, "updated_at" : ISODate("2012-11-26T04:49:42.549Z"), "created_at" : ISODate("2012-11-26T04:49:42.549Z") }
But as I login from linkdin it does not signup through linkdin else it redirects to
http://localhost:3000/users/sign_in
How can I login through that linkdin?
If you have something like this in your user model
validates :username, presence: true
Then you must know that linked in does not provide you any username. Since that, to complete your authentication / registration, your user has to add explicitly his username.
Make sure that your registrations_contreoller.rb looks like this
class RegistrationsController < Devise::RegistrationsController
def create
super
end
private
def build_resource(*args)
super
if session[:omniauth]
#user.apply_omniauth(session[:omniauth])
#user.valid?
end
end
end

Resources