I have a Ruby array which contains Post objects (a Mongoid model) with a created_at method returning a Time object. What I want is to sort that array using that method (which is present on every element on the array). I tried #posts.sort_by {|post| post.created_at} but that didn't work. How could I achieve this? Thanks!
EDIT: Example of the array:
[#<Post _id: 4ffd5184e47512e60b000003, _type: nil, created_at: 2012-07-11 10:12:20 UTC, title: "TestSecond", description: "TestSecond", slug: "assa", user_id: 4ffc4fd8e47512aa14000003>, #<Post _id: 4ffd518ee47512e60b000004, _type: nil, created_at: 2012-07-11 10:12:30 UTC, title: "TestFirst", description: "TestFirst", slug: "assadd", user_id: 4ffc4fd8e47512aa14000003>].
EDIT #2: I'm getting that #posts arra with:
#posts = Array.new
#user.following.each do |user|
user.posts.each do |p|
#posts << p
end
end
I'm not sure why you have an array here. Normally, with Mongoid querying, you should have a Mongoid::Criteria instance, which you can desc(:created_by) or asc(:created_by) on, in order to sort.
Still, can't think of any reason sort_by doesn't work for properly for you, works great on my app (just tried at console).
UPD:
#user.following.each do |user|
user.posts.each do |p|
#posts << p
end
end
Well, to still have a mongoid collection there, something like this might be done:
#posts = #user.following.map(&:posts).inject { |posts, post| posts.concat post }
Now, you can #posts.desc(:created_at).
UPD2:
Also, for purpose of having a cleaner code, you could define a posts_from_following on User:
class User
def posts_from_following
following.map(&:posts).inject { |posts, post| posts.concat post }
end
end
and then, just do
#posts = #user.posts_from_following.desc(:created_at)
in your controller.
#posts.sort_by &:created_at
#posts.sort {|a, b| a.created_at <=> b.created_at }
Related
Using Rails 4 with GraphQL API.
I'm getting some inputs via an object, based on which I'm finding or initializing new ActiveRecord objects that I want to save later.
Sample input is:
[
{:id=>"192", :internalId=>128, :title=>"Editing"},
{:internalId=>130, :title=>"New"}
]
As you can notice, some of the records already exist and have an ID, we need to update those. And the rest we need to save as new records.
Then I have a method that goes through those post values:
def posts=(value)
#posts = value.map do |post|
init_post(post)
end
end
def init_post(post)
Post.find_or_initialize_by(
id: post[:id],
title: post[:title],
internal_id: post[:internalId],
)
end
That will return two instances of the Post model:
[#<Post id: 192, title: "Editing", internal_id: 128, created_at: nil, updated_at: nil>, #<Post id: nil, title: "New", internal_id: 130, created_at: nil, updated_at: nil>]
Finally, I want to save both records:
def save_posts
posts.each(&:save)
end
Which will return:
"#<ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry '192' for key 'PRIMARY': INSERT INTO `posts` ..."
So how do I make sure the instances with ID just update the existing record, and the rest just save as new ones?
You can find, change/create and save it at once
Post.find_or_initialize_by(id: post[:id]).tap do |record|
record.title = post[:title]
record.internal_id = post[:internalId]
record.save
end
I have the following spec for the controller in simple ActiveRecord search feature:
Spec:
it "returns the records that match the given due date" do
create(:task, due_date: '2013-01-01')
create(:task, due_date: '2014-01-01')
get :search, 'filter' => { due_date: '2013-01-01' }
expect(assigns(:tasks)).to \
eq Task.search('filter' => { due_date: '2013-01-01' })
end
The model and controller are simple:
Model:
def self.search(params)
result = self.all #I know this is a bad idea, but starting simple.
params.each do |field, criteria|
if field.match(/due_date|completed_date/) && criteria != nil
result = result.where("DATE(#{field}) = ?", criteria)
end
end
result
end
Controller action:
def search
#tasks = Task.search(params['filter'])
#output from when the spec runs below
#puts params -> {"filter"=>{"due_date"=>"2013-01-01"}, \
# "controller"=>"tasks", \
# "action"=>"search"}
#puts params['filter] -> {"due_date"=>"2013-01-01"}
#puts #tasks.inspect -> just the one record
render 'index'
end
The spec fails, but it appears that it fails because the controller is returning both objects, while Task.search(...) is returning only the object with the specified value for due_date, as expected.
Here is the error message (edited for length):
2) TasksController GET #search returns the records that
match the given due date
Failure/Error: expect(assigns(:tasks)).to
eq Task.search('filter' => { due_date: '2013-01-01' })
expected: #<ActiveRecord::Relation
[#<Task id: 1,
due_date: "2013-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">,
#<Task id: 2, due_date: "2014-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">]>
got: #<ActiveRecord::Relation
[#<Task id: 1,
due_date: "2013-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">]>
You would assume that since the model apparently works (as evidenced by this result and a separate model spec that passes) that there is something wrong with the controller, but the controller is dead simple. I also have a feature spec incorporating the same controller that submits a form, triggers the search action and looks at the output, and the output only includes the one, correct record.
Am I missing something about how assigns works, making a dumb mistake or other?
It was option B, dumb mistake.
The model method takes the value of the filter element of the params hash as an argument, not the fake params hash I need to send to GET #searchin the line above the expectation. Or more clearly maybe, replace:
expect(assigns(:tasks)).to eq Task.search('filter' => { due_date: '2013-01-01' })
with
expect(assigns(:tasks)).to eq Task.search(due_date: '2013-01-01')'
I have the following where statement:
<% location = Location.where('locname' == client.locname) %>
How do I get the .id of the location record that it found?
This didn't work:
<% location = Location.where('locname' == client.locname).id %>
Thanks for the help!
<% location = Location.where("locname = ?", client.locname).first.id %>
The reason is that where will return an ActiveRecord::Relation, thus you can either loop through the elements or just grab the first one as I did above.
You may also use the find method provided by ActiveRecord like:
<% location = Location.find(:first, :conditions => ["locname = ?", client.locname]).id %>
be also aware that you need to paramterize your query properly to eliminate all possibilities of SQL injection.
The reason why your first code sample you provided doesn't allow you to obtain the id, is it isn't an instance of the Location class. Using some code from my own project:
1.9.2p290 :001 > ch = Character.where(name: 'Catharz')
Character Load (2.9ms) SELECT "characters".* FROM "characters" WHERE "characters"."name" = 'Catharz'
=> [#<Character id: 2, name: "Catharz", player_id: 2, archetype_id: 4, created_at: "2012-03-29 07:10:31", updated_at: "2012-11-26 05:36:11", char_type: "m", instances_count: 348, raids_count: 148, armour_rate: 5.1, jewellery_rate: 5.29, weapon_rate: 5.48>]
1.9.2p290 :002 > ch.class
=> ActiveRecord::Relation
This is because returns an instance of the ActiveRecord:Relation class which mimics your class. You can see this by calling #klass on the returned value.
1.9.2p290 :002 > ch.klass
=> Character(id: integer, name: string, player_id: integer, archetype_id: integer, created_at: datetime, updated_at: datetime, char_type: string, instances_count: integer, raids_count: integer, armour_rate: float, jewellery_rate: float, weapon_rate: float)
But if you try and get an id, you'll get the following exception:
1.9.2p290 :004 > ch.id
NoMethodError: undefined method `id' for #<ActiveRecord::Relation:0xce58344>
The ActiveRecord::Relation class allows you to chain together scopes, without executing the SQL until you need it to be executed. This is why Luis' answer above will work. Calling #first on the ActiveRecord::Relation will force the query to be executed.
As a pointer on design, you should probably be assigning your location as #location in your controller then using the instance variable in your view.
Let's say I have this simple method in my helper that helps me to retrieve a client:
def current_client
#current_client ||= Client.where(:name => 'my_client_name').first
end
Now calling current_client returns this:
#<Client _id: 5062f7b851dbb2394a00000a, _type: nil, name: "my_client_name">
Perfect. The client has a few associated users, let's look at the last one:
> current_client.user.last
#<User _id: 5062f7f251dbb2394a00000e, _type: nil, name: "user_name">
Later in a new method I call this:
#new_user = current_client.user.build
And now, to my surprise, calling current_client.user.last returns
#<User _id: 50635e8751dbb2127c000001, _type: nil, name: nil>
but users count doesn't change. In other words - it doesn't add the new user but one user is missing... Why is this? How can I repair it?
current_client.users.count makes a round trip to the database to figure out how many user records are associated. Since the new user hasn't been saved yet (it's only been built) the database doesn't know about it.
current_client.users.length will give you the count using Ruby.
current_client.users.count # => 2
current_client.users.length # => 2
current_client.users.build
current_client.users.count # => 2
current_client.users.length # => 3
I am defining #foo as a class instance attribute, and using the after_initialize callback to set the value of this when a record is created/loaded:
class Blog < ActiveRecord::Base
#foo = nil
after_initialize :assign_value
def assign_value
#foo = 'bar'
end
end
However, when I inspect a Blog object, I am not seeing the #foo attribute:
> Blog.first.inspect
=> "#<Blog id: 1, title: 'Test', created_at: nil, updated_at: nil>"
What do I need to do to get inspect to include this? Or conversely, how does inspect determine what to output?
Thanks.
Active record determines which attributes to show in inspect based on the columns in the database table:
def inspect
attributes_as_nice_string = self.class.column_names.collect { |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
end
}.compact.join(", ")
"#<#{self.class} #{attributes_as_nice_string}>"
end
Lifted from base.rb on github
To change the output of inspect you'll have to overwrite it with your own method e.g.
def inspect
"#{super}, #foo = #{#foo}"
end
Which should output:
> Blog.first.inspect
=> "#<Blog id: 1, title: 'Test', created_at: nil, updated_at: nil>, #foo = 'bar'"