Rails find_by_name on another table - ruby-on-rails

I have Genders and based on Gender name create category and subcategories.
m = Gender.create(:gender => 'masculine')
c = Category.find_by_name("T-shirt", gender: m )
c.subcategories.create(:name => "Necklace" )
and so on.

While the answer of Amit Sharma works I'd suggest have several improvements for it.
Use the new Hash syntax:
gender = Gender.create!(gender: 'masculine')
Use find_by instead of where/first
category = Category.find_by(gender: gender, name: 'T-Shirt')
Use the bang variants when not checking the return value
category.subcategories.create!(name: 'Necklace')
Use if/present? instead of unless/blank?
if category.present?
category.subcategories.create!(name: 'Necklace')
end
(this is just a matter of taste. But my brain seems to have troubles parsing unless expressions:-))
Use find_or_initialize_by/find_or_create_by!
If you want to find the category OR create it if it does not exist, use find_or_initialize/find_or_create_by!() so you can avoid the nil check:
category = Category.find_or_create_by!(gender: gender, name: 'T-Shirt')
So in total i'd write it like:
gender = Gender.create!(gender: 'masculine')
category = Category.find_or_create_by!(gender: gender, name: 'T-Shirt')
category.subcategories.create!(name: 'Necklace')

You can try this.
m = Gender.create(:gender => 'masculine')
c = Category.where(name: "T-shirt", gender: m.gender ).first
c.subcategories.create(name: "Necklace" )
Please note above code will raise an exception if no category found with given condition, so to avoid that you can use following.
m = Gender.create(:gender => 'masculine')
c = Category.where(name: "T-shirt", gender: m.gender).try(:first)
unless c.blank?
c.subcategories.create(name: "Necklace" )
end

Related

Input custom value from parameters to Rails SQL Query

I have written so code to query lists of users who go jogging at a certain time and in certain locations, but I am getting the following error: ActiveRecord::StatementInvalid: SQLite3::SQLException: near "'jogging'"
Does this mean I cannot write a string into that variable? Are there any solutions to this?
users = User.where('ids in (:user_ids)', user_ids:
Activity.where('title :title AND created_at >= :created_at AND location_id: location_id',
{title: 'jogging', created_at: Time.zone.now.beginning_of_day, location_id:
Location.where('id in id', id: user.activities.where.not(location_id: nil)
.order('created_at DESC').first)}))
You can simplified your query this way
location_id = Location.where(
id: user.activities
.where.not(location_id: nil)
.order('created_at DESC').first
)
user_ids = Activity.where("title = ? AND created_at > ? AND location_id = ?",
"jogging", Time.zone.now.beginning_of_day, location_id)
users = User.where(id: user_ids)
But If you want to keep query one liner. You can use this
User.where('id IN(:user_ids)',
user_ids: Activity.where('title = :title AND created_at >= :created_at AND location_id = :location_id', title: 'jogging', created_at: Time.zone.now.beginning_of_day,
location_id: Location.where('id IN(:id)', id: user.activities.where.not(location_id: nil)
.order('created_at DESC').first).ids)
)
Note: I am assuming that you have user object to use above one liner query
Hope this will help you.
I think you forgot to add an = in the query:
Your query:
Activity.where('title :title ...
What you want:
Activity.where('title = :title ...
And if you don't need an operator like > or <, you can also use:
Activity.where(title: title)
If you then need to chain it, it's pretty simple:
Activity.where(title: title).where('foo < ?', 100)

Assign each array element attribute with a value from another array respectively

I am trying to assign each person an age value from a list with same size.
class Person
attr_accessor :age
end
a = [person1, person2, person3, person4, person5]
b = [1,2,3,4,5]
How can I do the assignment below using a neat way(without using index i)?
i = 0
a.each do |p|
p.age = b[i]
i += 1
end
If they are guaranteed to be the same length, then you can use zip:
a.zip(b).each do |p, age|
p.age = age
end
As #ardavis pointed out, zip takes a block so you can remove the .each.
I know you asked for a solution without an index, but note that your code can be made neater even with an index. In Ruby, you don't need to define and increment your own index. Instead, you can use with_index like so:
a.each.with_index do |p, i|
p.age = b[i]
end
You can use index (as each Person instance is going to be unique):
a.each { |ai| ai.age = b[a.index(ai)] }
Demonstration
P.S. I would go with the approach introduced by #ardavis, using just zip:
a.zip(b) { |a, b| a.age = b }

Ruby - Extract value of a particular key from array of hashes

I have an array of hashes - #profiles which has data as:
[{:user_id=>5, :full_name=>"Emily Spot"},{:user_id=>7, :full_name=>"Kevin Walls"}]
I want to get full_name of say user_id = 7? I'm doing the following: but it's throwing an error that expression #profiles.find{|h| h[':user_id'] == current_user.id} is nil.
name = #profiles.find{ |h| h[':user_id'] == current_user.id }[':full_name']
if I use select instead of find then error is - no implicit conversion of String into Integer.
How do I search through the array of hashes?
UPDATE:
After #Eric's answer, I restructured my job model & view actions:
def full_names
profile_arr||= []
profile_arr = self.applications.pluck(:user_id)
#profiles = Profile.where(:user_id => profile_arr).select([:user_id, :first_name, :last_name]).map {|e| {user_id: e.user_id, full_name: e.full_name} }
#full_names = #profiles.each_with_object({}) do |profile, names|
names[profile[:user_id]] = profile[:full_name]
end
end
In the view....,
p #current_job.full_names[current_user.id]
#profiles is an array of hashes, with symbols as keys, whereas what you use is String objects.
So ':user_id' is a string, and you want symbol: :user_id:
#profiles.find{ |h| h[:user_id] == current_user.id }
I want to get full_name of say user_id == 7
#profiles.find { |hash| hash[:user_id] == 7 }.fetch(:full_name, nil)
Note, I used Hash#fetch for case, when there is no hash with value 7 at key :user_id.
As you've noticed, it's not very convenient to extract the name of user_id 7. You could modify your data structure a bit :
#profiles = [{:user_id=>5, :full_name=>"Emily Spot"},
{:user_id=>7, :full_name=>"Kevin Walls"}]
#full_names = #profiles.each_with_object({}) do |profile, names|
names[profile[:user_id]] = profile[:full_name]
end
p #full_names
# {5=>"Emily Spot", 7=>"Kevin Walls"}
p #full_names[7]
# "Kevin Walls"
p #full_names[6]
# nil
You didn't lose any information but name look-up is now much faster, easier and more robust.
Suggesting, to create a new hash that can make things simpler
Eg:
results = {}
profiles = [
{user_id: 5, full_name: "Emily Spot"},
{user_id: 7, full_name: "Kevin Walls"}
]
profiles.each do |details|
results[details[:user_id]] = details[:full_name]
end
Now, results will have:
{5: "Emily Spot", 7: "Kevin Walls"}
So, if you need to get full_name of say user_id = 7, simply do:
results[7] # will give "Kevin Walls"

grails hql left outer join

how is it possible to left outer join 2 tables?
class Person {
String firstName
String lastName
String gender
//static hasMany = [votes: Vote]
static mapping = {
version false
}
static constrains = {
}
}
class Vote {
Person voter;
Person subject;
static mapping = {
version false
}
static constraints = {
voter nullable: false
subject nullable: false
}
}
i need to get every person thats not subjected to a vote, for a specific person.
lets say person 1 votes for 3 out of 5 persons, i need the other 2 that he didnt vote for to show up for him.
How is the query supposed to be?
EDIT:
def personInstance1 = new Person(firstName: "Antonio", lastName: "Vivaldi", gender: "m")
def personInstance2 = new Person(firstName: "Dennis", lastName: "Rodman", gender: "m")
def personInstance3 = new Person(firstName: "Marc", lastName: "Oh", gender: "m")
def personInstance4 = new Person(firstName: "Gudrun", lastName: "Graublume", gender: "w")
def personInstance5 = new Person(firstName: "Hilde", lastName: "Feuerhorn", gender: "w")
def personInstance6 = new Person(firstName: "Mandy", lastName: "Muller", gender: "w")
personInstance1.save()
personInstance2.save()
personInstance3.save()
personInstance4.save()
personInstance5.save()
personInstance6.save()
def voteInstance1 = new Vote(voter: personInstance1, subject: personInstance2)
def voteInstance2 = new Vote(voter: personInstance1, subject: personInstance3)
def voteInstance3 = new Vote(voter: personInstance1, subject: personInstance4)
def voteInstance4 = new Vote(voter: personInstance1, subject: personInstance5)
def voteInstance5 = new Vote(voter: personInstance2, subject: personInstance1)
voteInstance1.save()
voteInstance2.save()
voteInstance3.save()
voteInstance4.save()
voteInstance5.save()
this is my grails bootstrap-file , Antonio and Dennis have voted, and each need to be presented a list of people they didnt vote for.
EDIT:
this way i seem to get a result for Dennis, since he only voted once,
but if i put v.voter_id = 1,
to get a result for Antonio, the result doubles according to how many votes he did.
SELECT first_name FROM vote as v
LEFT OUTER JOIN person as p
ON v.subject_id != p.id AND v.voter_id = 2
WHERE p.id IS NOT NULL
Try this:
SELECT * FROM Person P
WHERE NOT EXISTS(
SELECT 'Vote' FROM Vote V
WHERE V.subject = P
)
In this way you'll extract all Person without Vote
EDIT
In SQL you can retrieve a matrix in this way:
CREATE TABLE #person (nome varchar(30))
CREATE TABLE #vote (votante varchar(30), candidato varchar(30))
INSERT INTO #person values
('Antonio Vivaldi'),
('Dennis Rodman'),
('Marc Oh'),
('Gudrun Graublume'),
('Hilde Feuerhorn'),
('Mandy Muller')
INSERT INTO #vote values
('Antonio Vivaldi', 'Dennis Rodman'),
('Antonio Vivaldi', 'Marc Oh'),
('Antonio Vivaldi', 'Gudrun Graublume'),
('Antonio Vivaldi', 'Hilde Feuerhorn'),
('Dennis Rodman', 'Antonio Vivaldi')
SELECT *
FROM #person p
CROSS JOIN #person c
WHERE NOT EXISTS(
SELECT 'X'
FROM #vote v
WHERE v.votante = p.nome
AND v.candidato = c.nome
)
AND p.nome <> c.nome
ORDER BY p.nome

Adding a LIKE criteria to a Rails Conditions block

Consider the following code which is to be thrown at an AR find:
conditions = []
conditions[:age] = params[:age] if params[:age].present?
conditions[:gender] = params[:gender] if params[:gender].present?
I need to add another condition which is a LIKE criteria on a 'profile' attribute. How can I do this, as obviously a LIKE is usually done via an array, not a hash key.
You can scope your model with hash conditions, and then perform find on scope with array conditions:
YourModel.scoped(:conditions => conditions).all(:conditions => ["profile like ?", profile])
Follwing is ugly but it works
conditions = {} #This should be Hash
conditions[:age] = params[:age] if params[:age].present?
conditions[:gender] = params[:gender] if params[:gender].present?
conditions[:profile] = '%params[:profile]%' if params[:profile].present?
col_str ="" #this is our column names string for conditions array
col_str = "age=:age" if params[:age].present?
col_str+= (col_str.blank?)? "gender=:gender" :" AND gender=:gender" if params[:gender].present?
col_str += (col_str.blank?) 'profile like :profile' : ' AND profile like :profile' if params[:profile].present?
:conditions=>[col_str , conditions]
When you call your active record find, you send your conditions string first, then the hash with the values like :
:conditions => [ "age = :age AND gender = :gender AND profile LIKE :profile", conditions ]
that way you can keep doing what you are doing :)

Resources