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
Related
I installed graphql_devise gem
when I tried to user context[:current_resource], I can't use context[:current_resource]
why?
when I want to use current_user, should i user devise_token_authentication?
I'm in trouble.
please help
thanks
configration
install into my schema
class MySchema < GraphQL::Schema
use GraphqlDevise::SchemaPlugin.new(
query: Types::QueryType,
mutation: Types::MutationType,
resource_loaders: [
GraphqlDevise::ResourceLoader.new('User'),
],
authenticate_default: false
)
mutation(Types::MutationType)
query(Types::QueryType)
# Setup GraphQL to use the built-in lazy_resolve method
use BatchLoader::GraphQL
end
route.rb:
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
# For graphql
if Rails.env.development?
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
end
post "/graphql", to: "graphql#execute"
get "/graphql", to: "graphql#execute"
graqhql_controller.rb:
def execute
variables = ensure_hash(params[:variables])
query = params[:query]
operation_name = params[:operationName]
result = BusinessCloudSchema.execute(query, variables: variables, context: graphql_context(:user), operation_name: operation_name)
render json: result unless performed?
rescue => e
raise e unless Rails.env.development?
handle_error_in_development e
end
user_update_input.rb
module Types
class UserUpdateInput < Types::BaseInputObject
argument :fist_name, String, required: true
argument :last_name, String, required: true
argument :position, String, required: false
argument :image, ApolloUploadServer::Upload, required: false
def update!
current_user.assign_attributes(first_name: first_name, last_name: last_name, image: valid_image)
current_user.update
{
errors: user_errors(current_user.errors),
user: current_user.reload
}
end
private
def valid_image
image if image&.is_a?(ApolloUploadServer::Wrappers::UploadedFile)
end
end
end
update_user.rb
module Mutations
class UpdateUser < BaseMutation
field :user, Types::UserType, null: true
argument :user, Types::UserUpdateInput, required: true
def resolve(user:)
{
user: user.update!
}
end
end
end
iI execute the following mutation in graphiql
mutation{
updateUser(input: { clientMutationId: "", user: {fistName: "aa", lastName: "bb", position: "manager"}}) {
clientMutationId
user{
email
firstName
image
lastName
position
}
}
}
[Field 'signoutUser' doesn't accept argument 'input']
Here is the error I am getting
I tried to code for logout mutation using Graphql on Ruby on Rails. However, it shows an error shown above. Try to click the link above to see the error. How can I solve the error of logout mutation?
Here is my code for
app/graphql/mutations/sign_out_user.rb:
module Mutations
class SignOutUser < BaseMutation
null true
field :user, Types::UserType, null: false
field :token, String, null: false
argument :email, String, required: false
def resolve(email:)
user = User.find_by email: email[:email]
return user.logout
token = token.destroy!
{ user: user, token: token }
end
end
end
this one works
module Mutations
class SignOutUser < BaseMutation
null true
argument :token, String, required: true
field :token, String, null: false
field :message, String, null: false
def resolve(token:)
user = User.find_by(session_token: token)
if user
user.session_token = ""
user.save!
{
token: user.session_token,
message: "signout success!"
}
else
context.add_error(GraphQL::ExecutionError.new("Invalid token", extensions: { "code" => "INVALID_TOKEN" }))
end
end
end
end
I have 2 rails models which look like this
class Physician < UserProfile
has_many :state_licenses, inverse_of: :physician, autosave: true, dependent: :destroy
validates :state_licenses, :length => { :minimum => 1, message: "Please select at-least one state license"}
class StateLicense < ApplicationRecord
include RailsAdminPhysicianDependencyConcern
belongs_to :physician, inverse_of: :state_licenses
belongs_to :state, optional: true
attr_accessor :client_id
validates :state, presence: { message: I18n.t("errors.choose_one", field: 'state') }
#validates :license_number, presence: { message: I18n.t("errors.blank") }
def name
return "" unless state
"#{state.try(:name)}"
end
end
In my controller, I am using the code below to create a new Physician record with a bunch of state licenses but for some reason, the state licenses I pass to the create function never make it to the Physician model
def create
physician = nil
ActiveRecord::Base.transaction do
state_licenses = params["state_licenses"]
state_licenses_For_Association = []
if (state_licenses != nil)
state_licenses.each do |state_license|
sl = {}
sl[:state_id] = state_license
state_licenses_For_Association.push(sl)
end
end
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id, :state_licenses => state_licenses_For_Association))
user_record = nil
super do |user|
user_record = user
user.errors.delete(:user_profile)
physician.errors.messages.each { |field, messages| messages.each {|message| user.errors.add(field, message)} }
end
raise ActiveRecord::Rollback unless user_record.persisted? && physician.persisted?
end
AdminNotificationsMailer.physician_signed_up(physician).deliver_now rescue nil
end
What am I doing wrong?
Try changing this:
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id, :state_licenses => state_licenses_For_Association))
to this:
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id).merge(state_licenses: state_licenses_For_Association)) # note the .merge call
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').
I'm trying to learn Ruby and RoR.
I'm confused by Ruby's magic:)
class User < ActiveRecord::Base
require 'digest/sha1'
validates :login, :password, :email, { presence: true }
validates :login, :email, { uniqueness: true }
before_save :hash_password
private def hash_password
if password_chanched?
puts #password.class.name
puts password
password = 'test_pass'
puts password
# #password = Digest::SHA1.hexdigest(password) it works
end
end
end
console:
>> t = User.new login: 'Test', email: 'abc#def.ghi', password: 'trololo'
#<User id: nil, login: "Test", password: "trololo", email: "abc#def.ghi", created_at: nil, updated_at: nil>
>> t.save
NilClass
trololo
test_pass
true
>> t.password
"trololo"
So my question is:
What does password returns? And what have I to modify in callbacks?
Actually, this before_validation callback works just fine:
def downcase_login_and_email
login.downcase!
email.downcase!
end
I think that when I write
password = 'test_pass'
I don't use the setter (like any_instance.password = 'test_pass'), but I create a local variable password which does not make any sense to class instance.
I have to use self:
if password_changed?
puts password # trololo
self.password = 'test_pass'
puts password # test_pass
end
So I have to read more about Ruby:)