I have a few very basic questions in mind regarding active records. Please help me undestand:
What is the (data)type of the query result?
On doing '.class' to it, I get
the class name (Model name) to which it belongs. Makes complete
sense, but can we add more to the result?
Explaining my question with example:
u = User.find 1
=>#<User id: 1, email: "shivam#example.com", lang: 0, currency: 0, state: 0, category: 0, verified: true>
Checking the class:
u.class
=>User(id: integer, email: string, lang: integer, currency: integer, state: integer, category: integer, verified: boolean)
Is there someway to do this?
u.new_attr = "new_val"
Not entirely sure if this is what you want to do, but you can just select extra fields and they will be available on the object.
An example to demonstrate this:
results = Sample.find_by_sql(<<-SQL
select s.id, count(*) as results_count
from samples s, sample_results ss
where ss.sample_id = s.id
group by s.id
SQL)
So this query will return the sample-id and the count of linked sample-results. Now I can just type
results.first.results_count
and, obviously, results_count is not defined as attribute on the model Sample.
Not sure if that is what you ment. Maybe you just want to add an instance variable which is not saved to the database. In that case
you can simply do
class Sample
attr_accessor :my_special_field
end
and now I can just write
sample.my_special_field = 'something'
and it will only be available as long as the object "exists" and is never saved to the database.
Sorry if my question was not self explanatory. Here's a short explanation:
I do an active record query.
I get results.
I want to add something further to it.
As in the question the example is:
u = User.find 1
=>#<User id: 1, email: "shivam#example.com", lang: 0, currency: 0, state: 0, category: 0, verified: true>
AIM
To add some more information to the result set.
ISSUE
query result is an object of class User. As objects are instances of a class, we cannot simply add new attributes to the object without modifying the class. (Which is certainly not what we are looking for).
SOLUTION
It is plain stupidity to even think of directly modifying the object (which I did and got a -1 on my question. I acknowledge my mistake).
What we should rather do is, somehow convert the object into a data-structure that can be modified. In a case like this Hash suits the best.
Rails just provide an api to do so, its called attributes. Here:
u = User.find 1
=>#<User id: 1, email: "shivam#example.com", lang: 0, currency: 0, state: 0, category: 0, verified: true>
u.attributes
=> {"id" => 1, "email" => "shivam#example.com", "lang" => 0, "currency" => 0, "state" => 0, "category" => 0, "verified" => true}
Now that we have a hash. we can easily add/remove/modify/append any thing we want to:
u.attributes.merge({"something" => "added"})
=> {"id" => 1, "email" => "shivam#example.com", "lang" => 0, "currency" => 0, "state" => 0, "category" => 0, "verified" => true, "something" => "added"}
PS: Silly as it may, I have seen a lot of people asking such question over SO and other communities. I hope this may help someone. :)
Related
I used group_by to get a certain desired result. Based on the explanation in the answer, I have updated my question to reflect the answer, to see the steps it took to arrive at a solution, see the edit history.
#grouped_test_specific_reports = TestSpecificReport.all.group_by(&:equipment_type_name)
The code above produced this result:
2.5.1 :026 > pp #grouped_test_specific_reports
{"Ultrasonic Probes"=>
[#<TestSpecificReport:0x00007f832aa2d6e0
id: 10,
equipment_type_id: 2,
test_method_id: 1,
equipment_amount: "Multiple",
equipment_heading: "UT Probes">],
"Ultrasonic Instruments"=>
[#<TestSpecificReport:0x00007f832aa2d3c0
id: 8,
equipment_type_id: 1,
test_method_id: 1,
equipment_amount: "Single",
equipment_heading: "UT Instrument">],
"Visual Test Equipment"=>
[#<TestSpecificReport:0x00007f832aa2cfb0
id: 11,
equipment_type_id: 4,
test_method_id: 1,
equipment_amount: "Single",
equipment_heading: "VT Equipment">]}
=> {"Ultrasonic Probes"=>[#<TestSpecificReport id: 10, equipment_type_id: 2, test_method_id: 1, equipment_amount: "Multiple", equipment_heading: "UT Probes">], "Ultrasonic Instruments"=>[#<TestSpecificReport id: 8, equipment_type_id: 1, test_method_id: 1, equipment_amount: "Single", equipment_heading: "UT Instrument">], "Visual Test Equipment"=>[#<TestSpecificReport id: 11, equipment_type_id: 4, test_method_id: 1, equipment_amount: "Single", equipment_heading: "VT Equipment">]}
My next goal is to list out the grouped test specific report in the browser by their keys, I was able to do that by #grouped_test_specific_reports.each { |key, value| puts key }
"Visual Test Equipment"
"Ultrasonic Instruments" and
"Ultrasonic Probes"
Now we have to iterate over the values, which happens to be an array, in another loop to be able to compare equipment_amount.
The values with equipment_amount: "Multiple" will have the plus icon in front of them, and the ones with equipment_amount: "Single" will simply be a drop-down:
Here's the code for the UI:
- #grouped_test_specific_reports.each do |equipment_type_name, test_specific_reports|
.form-group.row
.col-sm-6
%label
= equipment_type_name
= select_tag '', options_from_collection_for_select(test_specific_reports, :id, :equipment_heading), { include_blank: "Select #{equipment_type_name} List", class: 'form-control select2', style: 'width: 100%;' }
.col-sm-1
- test_specific_reports.each do |test_specific_report|
- if test_specific_report.equipment_amount == 'Multiple'
.icon.text-center
%i.fa.fa-plus-circle.add-icon
I personally found the question you're asking a bit unclear. For this reason I discussed some things in the comments with you. From our discussion in the comments it seemed you simply wanted to loop through the grouped values for each group.
First I want to clear up what group_by exactly does, because this seemed to be the issue. A simple misunderstanding of what you're currently working on.
group_by { |obj| block } → a_hash
group_by → an_enumerator
Groups the collection by result of the block. Returns a hash where the keys are the evaluated result from the block and the values are arrays of elements in the collection that correspond to the key.
If no block is given an enumerator is returned.
(1..6).group_by { |i| i%3 } #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}
The documentation makes clear that the grouped hash has keys that evaluate from the block (the return value). The value that belongs to the key is actually an list of values that evaluate to the same result. This means you can simply loop through the values in the following way.
grouped_values = (1..6).group_by { |n| n % 3 }
grouped_values.each do |key, values|
puts "Key: #{key}"
values.each do |value|
puts "Value: #{value}"
end
end
The first each loops through the groups. The second each loops through the values of the group. Since you loop though two different things you can't change this into a single loop easily. The important thing to remember here that the value belonging to a group key is not a single value, but rather a group of values (array).
Does the money-rails gem require a specific order of price and currency when initializing an object? For example, take a look at the following:
Object.new(currency: 'JPY', price: 25)
=> #<Object id: nil, price_cents: 25, currency: "JPY">
If we specify the price first, we get an incorrect value (2500) for the price:
Object.new(price: 25, currency: 'JPY')
=> #<Object id: nil, price_cents: 2500, currency: "JPY">
Object contains the following: monetize :price_cents.
It looks like the order matters for certain currencies (including JPY because it doesn't have cents). This may not be the best solution but if anyone is stuck, here is what I did.
I added the following to the self.monetize method in money-rails to override the initialize methods for classes that use it:
define_method "initialize" do |opts = {}|
opts = opts.deep_symbolize_keys
opts = {currency: opts[:currency]}.merge(opts)
super(opts)
end
This way it will send the currency first.
I have this request to database
#show = Question.where(:question_status => params[:id])
Then in #show variable I have this: [#<Question id: 38, user_id: 1, question: "hi", question_status: 1, created_at: "2013-06-04 18:32:28", updated_at: "2013-06-04 18:32:28">, #<Question id: 40, user_id: 1, question: "urll", question_status: 1, created_at: "2013-06-04 18:34:57", updated_at: "2013-06-04 18:34:57">, #<Question id: 41, user_id: 1, question: "urll", question_status: 1, created_at: "2013-06-04 18:35:31", updated_at: "2013-06-04 18:35:31">]
How get , for example, question field ?
I trying #show.question but have error no defined method question.
#show = Question.find_by_question_status(params[:id])
and #show.question
If you us where statement then use:
#show.each do |show|
show.question
end
#show is an ActiveRecord::Relation object(Array). It does not belong to Question class.
#show = Question.where(:question_status => params([:id]).first
if you are expecting only one result. Otherwise you have to iterate the array for each element
You want to take an array of all question fields?
questions = Question.where(:question_status => params[:id]).map(&:question)
Then you can simply go like
questions.each{|question| puts question }
Try this code at your rails console
#show = Question.where(:question_status => params[:id])
#show.each_with_index do |ques, index|
p "#{index+1} :: #{ques.question}"
end
With the help of index, you will get the sequence number of rows.
Use #show.map(&:question) . This will give you all the questions in array.
#show.question gives you error no defined method question because you are trying to operate question on array #show.
If you do #show.first.question it will give you question of first object.
And
If you do #show.map(&:question) it will give you array of question.
To get all the information.
#show.each do |show|
puts show.question
puts show.question_status
puts show.user_id
end
You can optimize it as per you requirement.
(rdb:60) p resultsHash
{ 1 => [#<Participant id: 6, username: "player2", online_rank: 7, created_at: "2011-05-14 04:49:22", updated_at: "2011-05-14 04:57:56", win_count: 1, device_type: "iPad">],
0 => [#<Participant id: 5, username: "player1", online_rank: 3, created_at: "2011-05-12 02:47:50", updated_at: "2011-05-12 02:47:50", win_count: 0, device_type: "iPad">,
#<Participant id: 4, username: "iPhone4Simulator", online_rank: 4, created_at: "2011-05-12 02:45:37", updated_at: "2011-05-12 02:45:37", win_count: 0, device_type: "iPad">]}
I've tried...
resultsHash.sort {|a,b| -(a[0]<=>b[0])}
but the results aren't sorted by the keys when I iterate through the hash using each_pair.
Thanks!
What you really want to do is add an ORDER BY clause to your query. If this is Rails 3, Participant.order(:id).all is one way to do it.
To answer your immediate question, though, you would say resultsHash.sort_by(&:id). But don't do it this way.
Side note, use snake_case for Ruby code, not camelCase.
Edit: See comments.
resultsHash.sort.reverse
Note that it'll return an Array of [key, value] pairs. But you can still iterate like with a Hash:
resultsHash.sort.reverse.each do |key, value|
....
end
Or you can retrieve only the values: resultsHash.sort.reverse.map { |key, value| value }, or resultsHash.sort.reverse.map(&:last)
I realize this is an old question, but one way you can do exactly what you want while still retaining a return type of Hash is to use .slice on the sorted keys:
resultHash.slice(*resultHash.keys.sort)
I have the following:
#permission = #group.permissions.create(
:user_id => #user.id,
:role_id => 2,
:creator_id => current_user.id)
How can I update that to be find_or_create, so that if this record already exists, it's assigned to #permission, and if it doesn't exist, the record is created?
While the accepted answer is correct it's important to note that in Rails 4 this syntax will be changing (and the hash syntax). You should be writing the following:
#permission = Permission.where(
user_id: #user.id,
role_id: 2,
creator_id: current_user.id).first_or_create
Which actually looks much closer to your original method! See the sub-section Deprecated Finders for more details.
Related topic:
find_or_create_by in Rails 3 and updating for creating records
You can extend ActiveRecord with your own update_or_create method (see related topic) and then you can use this
#permission = Permission.update_or_create_by_user_id_and_role_id_and_creator_id(#user.id, 2, current_user.id) do |p|
p.group_id = #group.id
end
Or you can use find_or_create_by... method:
#permission = Permission.find_or_create_by_user_id_and_role_id_and_creator_id(#user.id, 2, current_user.id)
#permission.group = #group
#permission.save
I'm updating questions with versioned answers. Because its important.
Rails 4 (docs)
There are a few ways to "find or create" an object, one is find_or_create_by(args)
Client.find_or_create_by(email: "bambam#flinstones.com", phone: "4255551212")
But the community preferred way is using where
client = Client.where(email: "bambam#flinstones.com", phone: "4255551212").first_or_create
and then you can do something like:
client = Client.where(client_params.slice(:email, :phone)).first_or_create
client.update(client_params)
Rails 3 (docs)
Suppose you want to find a client named ‘Andy’, and if there’s none,
create one and additionally set his locked attribute to false. You can
do so by running:
client = Client.where(:first_name => 'Andy').first_or_create(:locked => false)
# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
Or you wanna try this if you have many fields to fill in:
conditions = { :user_id => #user.id,
:role_id => 2,
:creator_id => current_user.id }
#permission = group.permissions.find(:first, :conditions => conditions) || group.permissions.create(conditions)
see this post:
How can I pass multiple attributes to find_or_create_by in Rails 3?