I need write code for show users with organizations, but return next error in query, search, but with error
My code
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :email, String, null: false
field :role, String, null: true
field :organization_ids, [Types::OrganizationType], null: false
end
end
module Types
class OrganizationType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
end
end
But return next error
This is my query
It looks like you are trying to return an association: a User has many Organizations?
Make sure in user.rb you have the association defined with has_many :organizations ... and include any middle models required for Rails to understand the associations
Update your UserType to:
module Types
class UserType < Types::BaseObject
...
field :organizations, [Types::OrganizationType], null: false
end
end
Then you should be able to query like:
query getUsers{
users {
id
email
role
organizations {
id
}
}
}
Related
My data-model is this way: A vendor has many cars. A car belongs to one vendor.
So I got the ID of the respective vendor in each car-entity. What I like to have are the other fields of the vendor. Like vendor-name, country etc.
I finally have accomplished the described task:
Here is the code, which I have written.
The CarType:
module Types
class CarType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
field :vendor_id, ID, null: false
field :vendor, [String], null: true
def vendor
vendor = Vendor.find(object.vendor_id)
[vendor[:name], vendor[:country]]
end
end
end
And the root-type definition, plus resolver-function:
field :car, CarType, null: false,
description: "A single car-entity." do
argument :id, ID, required: true
end
def car(id:)
Car.find(id)
end
It works, but I like to ask:
Is my implementation correct, so that it can be used that way?
Or has it to be done differently?
In case so: How has it to be done?
I'd like my GraphQL mutation to return field that is not defined in the Type Object
I am using the Ruby GraphQL gem with Rails 6. I have described a UserType with the fields username, email and password. In my login mutation, I'd like to return a jwt token as the response. Here's what I have so far;
The user type:
module Types
class UserType < BaseObject
field :id, ID, null: false
field :email, String, null: false
field :username, String, null: false
end
end
The mutation:
module Mutations
UserMutation = GraphQL::ObjectType.define do
field :login, Types::UserType do
argument :username, !types.String
argument :password, !types.String
resolve ->(_obj, args, _ctx) do
# perform the authentication and return a jwt token.
end
end
end
end
I expect to get a response like:
{
"data" : {
"token": "my_generated_token"
}
}
or an error message of the same shape.
Update: Here's the solution that worked for me.
I created a new type LoginType that defines token and user fields and made the mutation respond to it instead of the UserType
module Types
class LoginType < BaseObject
field :token, String, null: false
field :user, Types::UserType, null: false
end
end
Thanks to #rmosolgo for the help
I have models for Users and Sites with a has_many through relaitonship between them. There is one extra piece of data on the sites_users join table is_default which is type boolean with the intention being to allow each user to have one default site from the list of related sites for that user.
user model
class User < ApplicationRecord
has_many :sites_users
has_many :sites, through: :sites_users
accepts_nested_attributes_for :sites_users, allow_destroy: true
...
end
user factory
factory :user do
sequence(:email) { |n| "user_#{n}#example.com" }
role { Role.find_by(title: 'Marketing') }
image { Rack::Test::UploadedFile.new(Rails.root.join('spec', 'support', 'fixtures', 'user.jpg'), 'image/jpeg') }
factory :super_admin do
role { Role.find_by(title: "Super Admin") }
admin true
end
before :create do |u|
u.sites_users.build(site: site, is_default: true)
end
end
alternate user factory Approach
On the User factory I have also tried this method included below, but cannot find a way to include the is_default: true using this syntax. So I ended up abandoning this method in favor of the above before_create call.
factory :user do
...
site { site }
...
end
I would really appreciate any help anyone could provide. Thank you!
schema info
table: users
t.string "email", default: "", null: false
t.boolean "admin", default: false
t.integer "role_id"
t.string "first_name"
t.string "last_name"
table: sites
t.string "domain", default: "", null: false
t.string "name", default: "", null: false
t.string "logo"
t.string "logo_mark"
table: sites_users
t.bigint "site_id", null: false
t.bigint "user_id", null: false
t.boolean "is_default", default: false
Create a factory for :site_user
factory :site_user, class: SiteUser do
site { site } # you could delete this line and add the site in factory :user
is_default { false } # as specified in your DB
end
Instead of creating the site within the :user factory, create its relation using the nice syntax:
factory :user do
...
sites_users { [FactoryBot.build(:site_user, is_default: true)] }
...
end
It should do the trick!
So when I deal with having extra fields on my joins tables I create custom methods to build those join relations rather than trying to rely on rails built in methods. I have tested this method and it works with factory_bot just fine.
User Model
class User < ApplicationRecord
...
def set_site(site, default = false)
SiteUser.find_or_create_by(
user_id: id,
site_id: site.id
).update_attribute(:is_default, default)
end
end
Note: This code is a block that I use so I would be able to both create new site relations and update the default value via the same method. You could simplify it to just create without checking for existence if you desired.
User Factory
factory :user do
...
# to relate to new site with default true
after(:create) do |u|
u.set_site(create(:site), true)
end
# to relate to existing site with default true
after(:create) do |u|
u.set_site(site_name, true)
end
end
Please let me know if this helps! (or if anyone has a more default railsish way that works just as well, I'd love to hear about it!)
You might want to consider addressing this with a schema change. You could add a default_site_id column to the users table and manage the default site as a separate association of the user model.
In a migration:
add_foreign_key :users, :sites, column: :default_site_id
In User class:
class User < ApplicationRecord
...
belongs_to :default_site, class_name: 'Site'
...
# validate that the default site has an association to this user
validate :default_site_id, inclusion: {in: sites.map(&:id)}, if: Proc.new {default_site_id.present?}
end
This will simplify the association and guarantee no user will ever have multiple site_users records where is_default is true. Setting the default site in a factory should be trivial.
I have roles for users implemented with an enum in the user model:
enum role: [:staff, :clinician]
I have a University model with the User belongs_to :university and a University model with has_many :users.
The way that my app will work is that "staff" will belong to a university, but "clinicians" are private practice and therefore do not need to belong to a university and need not select one during signup.
I have my signup form set up to hide the university field if the user selects Clinician, but I want to make sure that my validations are set up to require that any user who selects staff on signup must also select a university and that any user who selects clinician on signup fails validation if they select a university.
Here's the role section of the user signup form:
<%= f.label :role %>
<%= f.select(:role, User.roles.keys.map {|role| [role.titleize,role]}, :include_blank => "Please Select", id: 'user_role') %>
<%= content_tag :div, class: 'js-dependent-fields', data: { 'select-id': 'user_role', 'option-value': 'staff'} do %>
<%= f.label :university_id%>
<%= collection_select( :user, :university_id, University.all, :id, :name, prompt: true) %>
It requires a bit more extra setup but I think pays off in flexibility over time:
Try Single Table Inheritance combined with your enum roles. You'll be able to more easily define separate callbacks, validations, scopes, and associations for your different roles, while inheriting the ones you want them to share in common. For example, you could just make it so only Staff belongs_to :university, and Clinician does not.
# Stripped down schema
create_table "universities", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "university_id"
t.integer "role"
t.index ["university_id"], name: "index_users_on_university_id"
end
# Models
class University < ApplicationRecord
has_many :staffs
end
class User < ApplicationRecord
self.inheritance_column = :role
enum role: { Staff: 0, Clinician: 1 }
end
class Clinician < User
end
class Staff < User
belongs_to :university
end
Staff.first.university # => returns instance of University
Clinician.first.university # => raises NoMethodError
University.first.staffs # => returns collection of Staff objects
University.first.clinicians # => raises NoMethodError
Note that there is no type column. It's been overridden by the role:integer column used for the enum by setting self.inheritance_column = :role. You can interact with the enum roles with the string/symbol representation ("Staff", Staff.new, User.first.Staff?, User.first.Staff!, User.new(role: "Staff") and ActiveRecord takes care of converting that string to the right integer for the database queries.
For example, here's the query for User.where(role: "Staff")
SELECT "users".* FROM "users" WHERE "users"."role" = 0
Staff.all returns the same result but the wording of the query is slightly different
SELECT "users".* FROM "users" WHERE "users"."role" IN (0)
See this question for more detail: Same Model with different columns Rails
You can give a condition to the validates call in your User.rb model:
validates :university, presence: true, if: lambda { self.role.to_s == 'staff' }
# watch out for symbol vs. string in your self.role array
And I think (never done it but I guess that would work) you can do this for the :clinician role:
validates :university, presence: false, if: lambda { self.role.to_s == 'clinician' }
Given using the following example as a base... and please correct me if this isn't set up correctly.
Migration Files:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name, null: false
t.string :last_name, null: false
t.string :email, null: false
t.timestamps null: false
end
end
end
User.create(first_name:'John', last_name:'Smith', email:'john#smith.com') # id: 1
User.create(first_name:'Jane', last_name:'Smith', email:'jane#smith.com') # id: 2
class CreateCustomAttributes < ActiveRecord::Migration
def change
create_table :custom_attributes do |t|
t.string :name, null: false
t.text :description, null: true
t.timestamps null: false
end
end
end
CustomAttribute.create(name:'is_married', description:'') # id: 1
CustomAttribute.create(name:'has_children', description:'') # id: 2
CustomAttribute.create(name:'number_of_children', description:'') # id: 3
class CreateUserCustomAttributes < ActiveRecord::Migration
def change
create_table :user_custom_attributes do |t|
t.references :user, null: false
t.references :custom_attribute, null: false
t.text :value, null: true
t.timestamps null: false
end
end
end
# John
UserCustomAttribute.create(user_id: 1, custom_attribute_id: 1, value: 'no')
UserCustomAttribute.create(user_id: 1, custom_attribute_id: 2, value: 'yes')
UserCustomAttribute.create(user_id: 1, custom_attribute_id: 3, value: 4)
# Jane
UserCustomAttribute.create(user_id: 2, custom_attribute_id: 1, value: 'no')
UserCustomAttribute.create(user_id: 2, custom_attribute_id: 2, value: 'no')
UserCustomAttribute.create(user_id: 2, custom_attribute_id: 3, value: 0)
Models:
class CustomAttribute < ActiveRecord::Base
has_many :user_custom_attributes, :dependent => :destroy
has_many :users, through: :user_custom_attributes
end
class UserCustomAttribute < ActiveRecord::Base
belongs_to :user
belongs_to :custom_attribute
end
class User < ActiveRecord::Base
has_many :user_custom_attributes, :dependent => :destroy
has_many :custom_attributes, through: :user_custom_attributes
searchable do
text :first_name, :last_name, :email
end
end
I'm trying to allow "value" to be dynamic, whether it is a simple boolean (0,1), string ('speedy'), or serialized array of items ('---- Baseball- Football')
Is it possible to search for all users that have children (e.g. 'has_children' set to 'yes')?
For example: /users?fq[has_children]=yes
Also, would it possible to search for all users with children (e.g. 'number_of_children' is greater than 0)?
If so, how would you structure the 'searchable' block in the User model and the User.search block?
The easiest way to do what you want would be to start by separating out the different types of dynamic fields that you want (string, integer, boolean), maybe by adding an attribute_type: 'string', attribute_type: 'numerical', etc. Then you can use Sunspot's dynamic fields to search different types of dynamic data:
searchable do
dynamic_string :string_attributes do
user_custom_string_attributes.inject({}) do |hash, uca|
hash.merge(uca.custom_attribute.name.to_sym => uca.value)
end
end
dynamic_integer :numerical_attributes do
user_custom_numerical_attributes.inject({}) do |hash, nuca|
hash.merge(nuca.custom_attribute.name.to_sym => nuca.value.to_i) # just making sure it's an integer!
end
end
end
# Separate out the different types of attributes for different dynamic blocks
# via methods like the one below.
def user_custom_numerical_attributes
user_custom_attributes.where(attribute_type: 'numerical')
end
def user_custom_string_attributes
user_custom_attributes.where(attribute_type: 'string')
end
This will give you searchable 'string_attributes' objects that look like {:has_children => 'no'} or 'numerical_attributes' like {:number_of_children => 2}.
Then you can search with a block like so:
User.search do
dynamic :string_attributes do
with(:has_children, 'no')
end
dynamic :numerical_attributes do
with(:number_of_children).greater_than(0)
end
end
You can create dynamic fields for pretty much any data type except text; for that you'll have to use string instead. And once you've set up your dynamic fields, inside the dynamic :field_name do block you can use any of the non-text search methods demonstrated in the examples in the Sunspot readme.