I have a ActiveRecord backend model where I am inserting records. However when I am assigning value to certain attributes i would like to assign them based on a certain condition being satisified or not. How can I go about doing it? Have attached an example below for better sake of clarity.
#user = User.find_by_name("John")
Store.create(
name: "Some Store",
email: "store#example.com",
user_id: #user.id if #user.applicant?
)
Yes, you can't apply suffix if to keyword arguments / hash elements this way. I normally do something along these lines:
store_params = {
name: "Some Store",
email: "store#example.com",
}
store_params[:user_id] = #user.id if #user.applicant?
Store.create(store_params)
This also works well if there's an existing user_id value that needs to be preserved if #user is not an applicant. For example, when you're updating records. For creation, simple parenthesizing should work, as pointed out by others
user_id: (#user.id if #user.applicant?)
Caveat: this assumes that the default value for user_id is nil, so a nil produced from the expression when user is not an applicant is equal to the value set when user_id was not supplied at all.
You can use safe-operator here
Just use brackets to avoid interpreter error
Store.create(
name: "Some Store",
email: "store#example.com",
user_id: (#user.id if #user&.applicant?)
)
Related
I'm trying to map an AR relation in a specific data structure (to be rendered as JSON) and I can't make it work, for some reason the relations are always nil
Client.includes(:fixed_odds_account, person: [:phones, :emails]).map do |client|
{
id: client.id,
uri: client.uri,
updated_at: client.updated_at,
balance: client.fixed_odds_account.current_balance,
email: client.person.emails.pluck(:address),
first_name: client.person.first_name,
last_name: client.person.last_name,
number: client.person.phones.pluck(:number)
}
I'd expect this to return an array of hashes, but it always fails on the "person" relationship, which is apparently nil (and it is not).
What's weird is that if I remove the Hash and just puts client.person I can see my data.
Any idea?
Use #joins. With #includes you might be hitting a Client without Person as it uses left outer join. You might also want to add #uniq to remove duplicates.
Client.joins(:fixed_odds_account, person: [:phones, :emails]).uniq.map #code omitted
class Parent < ApplicationRecord
has_many :children
enum status: {
status1: 0,
status2: 1
}
end
class Child < ApplicationRecord
belongs_to :parent
end
# error
# "Relation passed to #or must be structurally compatible. Incompatible values: [:references]"
combination = Parent.status1.or(Parent.status2.includes(:children).where(children: {name: 'ABC'}))
I want to get the data "status1" or "status2 has children named 'ABC'", but error occurs.
The or method takes another relation that has a similar filter pattern, and combines it with the already-existing filters on the object being called.
For example, Parent.status1.or(Parent.status2) would give you a set of records that have either status: 1 or status: 2.
(In case someone is not familiar with it, the example in the question also uses enum, which allows filtering the enum's attribute value using the name of the value. #status1 and #status2 in this case correspond to { status: 0 } and {status: 1} respectively.)
In order to call more relation methods to modify the final result, you must call them on the result of calling #or, like this:
Parent.status1.or(Parent.status2).includes(:children).where(children: {name: 'ABC'})
Based on your comment I see now that you want records that either (have status1) or (have status2 and have a matching children record).
Note that in order to use a relation in a where (like where(children: { name: value }) you must join with the related table (joins(:children).where(children: { name: value }). It seems that ActiveRecord will infer the join if you use only includes, but that's not documented as far as I can tell. This is why or sees the two relations as incompatible: one has children in the references list, while the other does not.
If you write the where clause by hand as a string, it does not change the references list, so or does not see the relation as incompatible. When you write a where clause by hand, you must explicitly use joins:
Parent.status1.joins(:children).or(Parent.status2.joins(:children).where("children.name = 'ABC'"))
You are not calling "includes" on the final or result.
parent = Parent.status1.or(Parent.status2)
parent.includes(:chilren).where(children: {name: "ABC"})
I'm able to run both and it returns the same vale:
user = User.new(name:'John')
user.attributes['first_name']
=> 'John'
user.read_attribute('first_name')
=> 'John'
Is one more performant than the other? Are there cases where I would use one over the other?
Thanks!
attributes returns a hash of all attributes for the user and ['first_name'] just accesses the specified parameter of the hash, whereas read_attribute just returns the single parameter asked for. You don't really need either of those methods to access the name as this can be done which makes the code a lot cleaner:
user = User.new(name:'John')
user.name
=> 'John'
class State
include Mongoid::Document
embeds_many :cities
field :name
end
class City
include Mongoid::Document
embedded_in :state
field :name
field :population
field ...
end
I don't want to include the fields with nil value into mongodb,
nsw = State.new name: 'NSW'
if number_of_people
nsw.cities.create name: 'Syndey', population: number_of_people
else
nsw.cities.create name: 'Syndey'
end
so it is necessary to check whether or not that field is empty or null. But the problem is when there are many fields in City, the code looks ugly.
How to improve this and write smart code?
You need to define a custom class method in City model like the following:
def self.create_persistences(fields = {})
attributes = {}
fields.each do |key, value|
attributes[key] = value if value
end
create attributes
end
and in your controller, call this method without conditions hassle:
nsw.cities.create_persistences name: 'Syndey', population: number_of_people
note: you can also override create method on your model instead of defining new method but in my opinion, I don't prefer to override something you may use in other part of the code.
Now we know what you are doing your answer seems clear. But I think your question needs an edit to inform.
So what you have is data from some source that you are using to populate your new model. So at some stage here you are going to have a hash or at least some way of constructing a hash in some form from however your data is organized. Take the following [short form but the same thing]:
info = { name: "Sydney", population: 100 }
City.new( info );
info = { name: "Melbourne", population: 80, info: "fun" }
City.new( info )
info = { name: "Adelaide" }
City.new( info )
So (at least in my testing ), you are going to get each document, with only the specified fields created each time.
So dynamically using the hash (and hopefully you are even just reading in that way ) is going to be a lot smarter than testing each value in code.
If you have to do a lot of value testing to even "build up" a hash then you have problems that no-one here can fix. But building hashes should be easy.
I'm using the mongoid gem in Ruby. Each time I upsert, save or insert the same unique document in a collection, the Ruby instance shows a different id. For example, I have a script like so:
class User
include Mongoid::Document
field :email, type: String
field :name, type: String
index({ email: 1}, { unique: true })
create_indexes
end
u=User.new(email: 'test#testers.edu', name: "Mr. Testy")
u.upsert
puts u.to_json
The first time I run it against an empty or non-existent collection, I get this output
{"_id":"52097dee5feea8384a000001","email":"test#testers.edu","name":"Mr. Testy"}
If I run it again, I get this:
{"_id":"52097e805feea8575a000001","email":"test#testers.edu","name":"Mr. Testy"}
But the document in MongoDB still shows the first id (52097dee5feea8384a000001), so I know we are operating on the same record. If I always follow the upsert with a find_by operation, I get the right id consistently, but it feels inefficient to have to run an upsert followed by a query.
Am I doing something wrong? I'm concerned that I will be getting the wrong id back in an operation where someone is, say, updating his profile repeatedly.