Rails scope filter/search from association - ruby-on-rails

I have a one-to-one association between PublicKey and PublicKeyRole. The PublicKey model has a column, role_id, and the PublicKeyRole model has role column which is a string, such as 'super'.
I want to be able to search by role string via a url query param, like; https://api.domain.com/public_keys?role=admin. I've tried this on the PublicKey model but I'm not sure where to pass the query in to:
scope :filter_by_role, lambda { |query|
joins(:public_key_role).merge(PublicKeyRole.role)
}
Here are my models:
class PublicKey < ActiveRecord::Base
belongs_to :user
belongs_to :public_key_role, foreign_key: :role_id
def self.search(params = {})
public_keys = params[:public_key_ids].present? ? PublicKey.where(id: params[:public_key_ids]) : PublicKey.all
public_keys = public_keys.filter_by_role(params[:role]) if params[:role]
public_keys
end
end
class PublicKeyRole < ActiveRecord::Base
has_one :public_key
end
Also, here's my test:
describe '.filter_by_role' do
before(:each) do
#public_key1 = FactoryGirl.create :public_key, { role_id: 1 }
#public_key2 = FactoryGirl.create :public_key, { role_id: 2 }
#public_key3 = FactoryGirl.create :public_key, { role_id: 1 }
end
context "when a 'super' role is sent" do
it 'returns the 2 matching public keys results' do
expect(PublicKey.filter_by_role('super').size).to eq(2)
end
it 'returns the matching public keys' do
expect(PublicKey.filter_by_role('super').sort).to match_array([#public_key1, #public_key3])
end
end
end
Update
I was missing the following in my spec, #lcguida's answer works.
FactoryGirl.create :public_key_role, { id: 1, role: 'super' }

From the docs you have some examples. You can search in the relation passing the query to the PublicKeyRole relation:
scope :filter_by_role, lambda { |query|
joins(:public_key_role).merge(PublicKeyRole.where(role: query))
}
If you want a string search (as LIKE):
scope :filter_by_role, lambda { |query|
joins(:public_key_role).merge(PublicKeyRole.where(PublicKeyRole.arel_table[:role].matches(query)))
}

Related

Create FactoryBot User with has_one relationship

Background:
I am trying to create a FactoryBot object which is related with has_one/belongs_to
User has_one Car
Car has_one Style
Style has an attribute {style_number:"1234"}
Question
My controller references user, user has_one Car, Car has_one Style, and I need to set these values within FactoryBot.
How do I create a User, who also has a Car object, that has a Style object?
I read the documentation https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
However, I am not understanding how they recommend doing this. Figured out, I need to nest the three objects, but confused on the syntax.
Controller
before_action :authenticate_user!
before_action :set_steps
before_action :setup_wizard
include Wicked::Wizard
def show
#user = current_user
#form_object = form_object_model_for_step(step).new(#user)
render_wizard
end
private
def set_steps
if style_is_1234
self.steps = car_steps.insert(1, :style_car)
else
self.steps = car_steps
end
end
def style_is_1234
if params.dig(:form_object, :style_number)
(params.dig(:form_object, :style_number) & ["1234"]).present?
else
(current_user.try(:car).try(:style).try(:style_number) & ["1234"]).present?
end
end
def car_steps
[:type,:wheel, :brand]
end
Rspec Test
Factory :User
FactoryBot.define do
factory :user, class: User do
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
password { "somepassword" }
password_confirmation { "some password"}
end
end
Before method
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryBot.create(:user)
sign_in user
Test
User needs to be signed in and User.car.style.style_number needs to be set to "1234"
context "Requesting with second step CarStyle" do
it "should return success" do
get :show, params: { :id => 'car_style' }
expect(response.status).to eq 200
end
end
Currently this test fails because User.Car.Style.style_number is not set to "1234".
Trial 1 (https://github.com/thoughtbot/factory_bot_rails/issues/232)
FactoryBot.define do
factory :user, class: User do
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
password { "somepassword" }
password_confirmation { "some password"}
car
end
end
FactoryBot.define do
factory :car, class: Car do
make { "Holden" }
model { "UTE" }
end
end
FactoryBot.define do
factory :style, class: Style do
color { "blue" }
for_car
trait :for_car do
association(:styable, factory: :car)
end
end
end
Error from trail 1
SystemStackError:
stack level too deep
Trail 2
I tried srng's recommendation
EDIT: For a polymorphic association try;
FactoryBot.define do
factory :car, class: Car do
make { "Holden" }
model { "UTE" }
association :stylable, factory: :style
end
end
and got error:
ActiveRecord::RecordInvalid: Validation failed: Stylable must exist
I think this is a rails 5 issue. https://github.com/rails/rails/issues/24518
However, I would like to keep my code with the adding the optional:true. Any way to do this?
Trail 3
FactoryBot.define do
factory :car, class: Car do
make { "Holden" }
model { "UTE" }
after(:create) do |car|
create(:style, stylable: car)
end
end
end
Tried Srng's second recommendation and although it worked for him, I got a slightly different error:
ActiveRecord::RecordInvalid:
Validation failed: User must exist
In order to create dependent Factories you have to create a factory for each model, and then just add the dependent Model name to your factory, ie.
FactoryBot.define do
factory :user, class: User do
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
password { "somepassword" }
password_confirmation { "some password"}
car
end
end
FactoryBot.define do
factory :car, class: Car do
make { "Holden" }
model { "UTE" }
style
end
end
FactoryBot.define do
factory :style, class: Style do
color { "blue" }
end
end
EDIT:
Relevant code;
# Factories
FactoryBot.define do
factory :user, class: User do
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
password { "somepassword" }
password_confirmation { "some password"}
after(:create) do |user|
user.car ||= create(:car, :user => user)
end
end
end
factory :style, class: Style do
style_number { "Blue" }
end
factory :car, class: Car do
name { "Holden" }
trait :style do
association :stylable, factory: :style
end
end
#models
class Car < ApplicationRecord
has_one :style, as: :styleable
end
class Style < ApplicationRecord
belongs_to :styleable, polymorphic: true
belongs_to :car
end
# Migrations - The belongs_to is the only important one
class CreateStyles < ActiveRecord::Migration[5.2]
def change
create_table :styles do |t|
t.string :style_number
t.belongs_to :stylable, polymorphic: true
t.timestamps
end
end
end
class CreateCars < ActiveRecord::Migration[5.2]
def change
create_table :cars do |t|
t.string :name
t.timestamps
end
end
end
There can be an another way of attaining this using a transient block in the Factory.
Hope the below snippet may help you to explore in new way.
Note: This is not tested.
## To Create a user in test case
# create(:user) # defaults to 1234 style number
# create(:user, car_style_number: 5678)
DEFAULT_STYLE_NUMBER = 1234
FactoryBot.define do
factory :user do
transient do
car_style_number { DEFAULT_STYLE_NUMBER }
end
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
after(:create) do |user, evaluator|
user.car = create(:car, car_style_number: evaluator.car_style_number, user: user)
end
end
end
FactoryBot.define do
factory :car do
transient do
car_style_number { DEFAULT_STYLE_NUMBER }
end
make { "Holden" }
model { "UTE" }
after(:create) do |car, evaluator|
car.style = create(:style, style_number: evaluator.car_style_number, car: car)
end
end
end
FactoryBot.define do
factory :style do
style_number { DEFAULT_STYLE_NUMBER }
end
end

How to convert a method into a scope.

Hello I am trying to convert the method self.liked_by(user) into a scope. I am not entirely sure what my instructor is asking for so any interpretations on the question are greatly appreciated.
this is the method in question that I am supposed to turn into a scope.
def self.liked_by(user)
joins(:likes).where(likes: { user_id: user.id })
end
this is where the method appears in the model
class Bookmark < ActiveRecord::Base
belongs_to :user
belongs_to :topic
has_many :likes, dependent: :destroy
before_validation :httpset
validates :url, format: { with: /\Ahttp:\/\/.*(com|org|net|gov)/i,
message: "only allows valid URLs." }
def self.liked_by(user)
joins(:likes).where(likes: { user_id: user.id })
end
def httpset
if self.url =~ /\Ahttp:\/\/|\Ahttps:\/\//i
else
if self.url.present?
self.url = "http://"+ self.url
else
self.url = nil
end
end
end
end
And this is where the method is called in the controller
class UsersController < ApplicationController
def show
user = User.find(params[:id])
#bookmarks = user.bookmarks
#liked_bookmarks = Bookmark.liked_by(user)
end
end
Thanks for looking at my problem and have a good day.
#liked_bookmarks = Bookmark.liked_by(user)
In this line, in the same way you send the user parameter to a method, the same way you can send it to a scope.
class Bookmark < ActiveRecord::Base
---------
---------
scope :liked_by, ->(user) { joins(:likes).where(likes: { user_id: user.id }) }
---------
---------
end
the parameter you sent from the scope call can be accessed using the (user{or any name) in the scope
reference of scopes
As Owen suggested, read the docs to understand what scopes are. It is just another syntax to define your model's class methods (just like the one you already have).
scope :liked_by, ->(user) { joins(:likes).where(likes: { user_id: user.id }) }

Using FactoryGirl for resource that belongs to 2 other resources and validates their id's in Rails 4 app

My associations aren't so complex but I've hit a wall making them work with FactoryGirl:
Text: blast_id:integer recipient_id:integer
class Text < ActiveRecord::Base
belongs_to :blast
belongs_to :recipient, class_name: "User"
validates :blast_id, presence: true
validates :recipient_id, presence: true
end
Blast: content:string author_id:integer
class Blast < ActiveRecord::Base
belongs_to :author, class_name: "User"
has_many :texts
validates :author_id, presence: true
end
User: name:string, etc. etc.
class User < ActiveRecord::Base
has_many :blasts, foreign_key: "author_id"
validates :name, presence: true
end
In FactoryGirl I've got:
FactoryGirl.define do
factory :user, aliases: [:author, :recipient] do |u|
sequence(:name) { Faker::Name.first_name }
end
factory :blast do
author
content "Lorem ipsum"
ignore do
texts_count 1
end
after :build do |blast, evaluator|
blast.texts << FactoryGirl.build_list(:text, evaluator.texts_count, blast: nil, recipient: FactoryGirl.create(:user) )
end
end
factory :text do
blast
association :recipient, factory: :user
end
end
Finally, some specs which all fail because Texts is not valid
require 'spec_helper'
describe Text do
User.destroy_all
Blast.destroy_all
Text.destroy_all
let!(:user) { FactoryGirl.create(:user) }
let!(:blast) { FactoryGirl.create(:blast, author: user) }
let(:text) { blast.texts.first }
subject { text }
it { should be_valid }
describe "attributes" do
it { should respond_to(:blast) }
it { should respond_to(:recipient) }
its(:blast) { should == blast }
its(:recipient) { should == recipient }
end
describe "when blast_id is not present" do
before { text.blast_id = nil }
it { should_not be_valid }
end
describe "when recipient_id is not present" do
before { text.recipient_id = nil }
it { should_not be_valid }
end
end
All the specs fail on FactoryGirl blast creation with:
1) Text
Failure/Error: let!(:blast) { FactoryGirl.create(:blast, author: user) }
ActiveRecord::RecordInvalid:
Validation failed: Texts is invalid
# ./spec/models/text_spec.rb:8:in `block (2 levels) in <top (required)>'
I've tried various iterations of the association code in the FactoryGirl docs and other question answers like this one but my situation is different enough that I can't get it to work.
If you've made it this far, thank you! Super grateful for any leads.
Your factory for "blast" should look like
factory :blast do
author
content "Lorem ipsum"
ignore do
texts_count 1
end
after :build do |blast, evaluator|
blast.texts << FactoryGirl.build_list(:text, evaluator.texts_count, blast: blast, recipient: FactoryGirl.create(:user) )
end
end
In other words, you immediately create the correct "parent" by connecting the newly created blast to the newly created tekst
To further dry your code, have a look at https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#configure-your-test-suite, describing how to get rid of using "FactoryGirl." over and over again by setting
config.include FactoryGirl::Syntax::Methods
once in your settings

rspec association creation error

I have a model item has_many ratings and a ratings belongs_to item ratings belongs_to user I want to force a user who is creating an item to rate it too. Other users can then rate it later on. item and user have no association in my model.
I am doing the following in my item_spec which is giving me an error no implicit conversion of Symbol into Integer on line #item = Item.new(name: "Item1", below.
class Item < ActiveRecord::Base
has_many :ratings, dependent: :destroy, inverse_of: :item
accepts_nested_attributes_for :ratings, :allow_destroy => true
validates :name , :length => { minimum: 3 }
validates :category , :length => { minimum: 3 }
validates_presence_of :ratings
end
require 'spec_helper'
describe Item do
before do
#item = Item.new(name: "Item1",
url: "www.item1.com",
full_address: "Item1Address",
city: "Item1City",
country: "Item1Country",
category: "Item1Type",
ratings_attributes: {"rating" => "3", "comment" => "Ahh Good"} )
end
Also using FactoryGirl I am doing something like this
factory :item do
before_create do |r|
r.ratings<< FactoryGirl.build(:ratings, item: r )
end
name "Item1"
url "www.Item1.com"
full_address "Item1Address"
city "Item1City"
country "Item1Country"
category "Item1Category"
end
factory :ratings do
rating 3
comment "Its not that bad"
user
end
end
which again is not yeilding the desired result.
can anyone help me solve this problem please.Thanks!
Working Code, now having problem testing some association order, but at least the desired functionality working.
factory :item do
name "Item1"
url "www.Item1.com"
full_address "Item1Address"
city "Item1City"
country "Item1Country"
category "Item1Category"
end
factory :ratings, :class => 'Ratings' do
association :item, factory: :item, strategy: :build
user
rating 3
comment "Its not that bad"
end
factory :item_with_rating, parent: :item do
ratings {[FactoryGirl.create(:ratings)]}
end
Here is the spec file
require 'spec_helper'
describe Item do
before do
#item = FactoryGirl.create(:item_with_rating)
end
subject { #item }
it { should respond_to(:name) }
it { should respond_to(:url) }
it { should respond_to(:full_address)}
it { should respond_to(:city) }
it { should respond_to(:country) }
it { should respond_to(:category) }
it { should respond_to(:ratings) }
it { should_not respond_to(:type) }
it { should_not respond_to(:user_id) }
it { should be_valid }
There is no change in the Model file for item

shoulda factory girl error Couldn't find model without an ID

Good day, i get this error from
ActiveRecord::RecordNotFound:
Couldn't find User without an ID
my model
has_many :objects, class_name: 'OrderObject', dependent: :destroy
belongs_to :user
belongs_to :tariff
validates :client, :phone, :tariff_id, :days, :user_id, presence: true
spec
before do
user = FactoryGirl.create(:user)
FactoryGirl.create(:order, user_id: user.id)
end
context "validations" do
it { should validate_presence_of :client }
it { should validate_presence_of :phone }
it { should validate_presence_of :tariff_id }
it { should validate_presence_of :days }
end
it { should have_many(:objects) }
it { should belong_to(:tariff) }
it { should belong_to(:user) }
factory
factory :order do
client "MyString"
phone "MyString"
tariff_id 1
days 1
# advt_payed_day 1
# firm_payed_day 1
user_id 1
end
UPDATE 1
changed to
before(:all) do
user = FactoryGirl.create(:user )
puts user.id
order = FactoryGirl.create(:order, user_id: user.id )
puts order.id
end
output
Order
45
32
should have many objects
should belong to tariff
should belong to user
validations
should require client to be set (FAILED - 1)
should require phone to be set (FAILED - 2)
should require tariff_id to be set (FAILED - 3)
should require days to be set (FAILED - 4)
should require user_id to be set (FAILED - 5)
so order & user are created...
Update 2
as Rubyman suggested, i've changed couple of things:
in spec
before(:all) do
user = FactoryGirl.create(:user )
#puts user.id
order = FactoryGirl.create(:order, user_id: user.id )
puts order
puts order.user_id
end
in the factory
factory :order do
client "MyString"
phone "MyString"
tariff_id 1
days 1
# user_id 1
association :user, factory: :user
end
and output is:
Order
#<Order:0x00000005a866a0>
46
should have many objects
should belong to tariff
should belong to user
validations
should require client to be set (FAILED - 1)
should require phone to be set (FAILED - 2)
should require tariff_id to be set (FAILED - 3)
should require days to be set (FAILED - 4)
should require user_id to be set (FAILED - 5)
1) Order validations
Failure/Error: it { should validate_presence_of :client }
ActiveRecord::RecordNotFound:
Couldn't find User without an ID
# ./app/models/order.rb:35:in `user_is_not_admin?'
# ./spec/models/order_spec.rb:14:in `block (3 levels) in <top (required)>'
update 3
after reading advice from tdgs here are the changes:
in model no changes :
validates :client, :phone, :tariff_id, :days, :user_id, presence: true
in spec
describe Order do
before(:each) do
user = FactoryGirl.create(:user )
#puts user.id
order = FactoryGirl.create(:order, user_id: user.id )
puts order
puts order.user_id
puts order.tariff_id
puts order.phone
puts order.days
puts order.client
puts '*****'
user = User.find(order.user_id)
puts user.login
end
context "validations" do
it { should validate_presence_of :client }
it { should validate_presence_of :phone }
it { should validate_presence_of :tariff_id }
it { should validate_presence_of :days }
it { should validate_presence_of :user_id }
end
it { should have_many(:objects) }
it { should belong_to(:tariff) }
it { should belong_to(:user) }
end
output:
#<Order:0x00000006c10ce0>
161
101
MyString
1
MyString
*****
user__7
should require days to be set (FAILED - 1)
output for every should is valid as far as i see...
UPDATE N
should have written it in the beginning. i've run (hoped that it'll solve this issue) in console
bundle exec rake db:migrate
bundle exec rake db:migrate:reset db:test:prepare
First, your factory definition is not defining associations correctly. You should have something like this:
FactoryGirl.define do
factory :user do
sequence(:username) {|n| "username_#{n}"}
# more attributes here
end
factory :tariff do
# attributes
end
factory :order do
client "MyString"
phone "MyString"
tariff
user
days 1
end
end
Then your tests should be written like this:
context "validations" do
it { should validate_presence_of :client }
it { should validate_presence_of :phone }
it { should validate_presence_of :tariff_id }
it { should validate_presence_of :days }
end
it { should have_many(:objects) }
it { should belong_to(:tariff) }
it { should belong_to(:user) }
All the code you currently have in the before filter is not relevant right now. Also notice that using before(:all) might have some strange effects when running your tests, because they do not run inside a transaction. before(:each) on the other hand does.
try this
before {
#user = FactoryGirl.create(:user, :email => "test.com", :password => "test123", ... )
#order = FactoryGirl.create(:order, :user_id => #user.id )
}
Factory
require 'factory_girl'
FactoryGirl.define do
factory :order do
client "MyString"
...
...
end
end
Check how to create associations with factory girl
https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md
i moved away from shoulda and rewrote checks for validation. This way it works:
before(:each) do
#user = FactoryGirl.create(:user )
#order = FactoryGirl.create(:order, user_id: #user.id )
end
it 'absence of client isn\'t acceptable' do
temp = #order.client
#order.client = ''
#order.should_not be_valid
#order.client = temp
#order.should be_valid
end

Resources