Index attribute based on other attributes - ruby-on-rails

We have several models that require children objects
Each children should have a unique sequential ID as primary key and a unique sequential ID based on the relation, like bellow:
Object id: 1
Children id: 1, internal_id: 1
Children id: 2, internal_id: 2
Object id: 2
Children id: 3, internal_id: 1
Children id: 6, internal_id: 2
Children id: 7, internal_id: 3
Object id: 3
Children id: 4, internal_id: 1
Children id: 5, internal_id: 2
Children id: 8, internal_id: 3
Children id: 9, internal_id: 4
Currently I'm using a before_save filter to pin the internal_id but I feel this is a bad practice and could be improved with some sql magic
def define_internal_id
self.internal_id = 1 + Children.unscoped.where(parent_id: self.parent_id).count
end
before_save :define_internal_id
Is there a better way to solve this?

This is exactly what the acts_as_list gem is for. First, add it to your Gemfile:
gem 'acts_as_list'
Then in your model, add:
acts_as_list column: :internal_id, scope: :parent_id
This will number internal_id starting at 1, scoped to the parent_id column, as in your examples. If you want to start at 0 set the option top_of_list: 0.
See the acts_as_list repo for other options.

Related

Rails group_by with empty results

I have following models:
class Task
belongs_to :task_category
end
class TaskCategory
has_many :tasks
end
I want to group tasks by task category and this works for me:
Task.all.group_by(&:task_category)
# =>
{
#<TaskCategory id: 1, name: "call", ... } =>[#<Task id: 1, ...>, #<Task id: 2, ...>],
#<TaskCategory id: 2, name: "event", ... } =>[#<Task id: 3, ...>, #<Task id: 4, ...>]
}
The problem is I want all task categories returned even if the task collection is empty. Therefore, something like this would work:
#<TaskCategory id: 3, name: "todo", ... } =>[]
In this case, the task category has no tasks, so the value is an empty array. Does the group_by support an option to allow this? If not, can this be done elegantly in a one-liner?
TaskCategory.all.includes(:task) would work wouldn't it? The data you get back would be in a slightly different format, but not significantly so.
If you just do TaskCategory.all, you can get the tasks grouped by the category that you need. The format isn't exactly the same but still grouped the way you want it:
TaskCategory.all
# Assuming the first TaskCategory has no tasks
TaskCategory.all.first.tasks
=> #<ActiveRecord::Relation []>
A TaskCategory with no tasks would yield #<ActiveRecord::Relation []> which is somewhat equivalent to [].

How to select unique records based on foreign key column in Rails?

I have the following model structure in my Rails 4.1 application:
delivery_service.rb
class DeliveryService < ActiveRecord::Base
attr_accessible :name, :description, :courier_name, :active, :country_ids
has_many :prices, class_name: 'DeliveryServicePrice', dependent: :delete_all
end
delivery_service_price.rb
class DeliveryServicePrice < ActiveRecord::Base
attr_accessible :code, :price, :description, :min_weight, :max_weight, :min_length, :max_length,
:min_thickness, :max_thickness, :active, :delivery_service_id
belongs_to :delivery_service
end
As you can see, a delivery service has many delivery service prices. I'm trying to retrieve records from the delivery service price table; selecting the record with the lowest price attribute within the unique scope of the foreign key, delivery_service_id (so essentially the cheapest delivery service price per delivery service).
How can I select unique records from a table, with the foreign key attribute as the scope?
I hope I've explained this enough, let me know if you need anymore information.
Thanks
UPDATE #1:
Example of what I'm trying to achieve:
delivery_service_prices table:
id: 1, price: 2.20, delivery_service_id: 1
id: 2, price: 10.58, delivery_service_id: 1
id: 3, price: 4.88, delivery_service_id: 2
id: 4, price: 1.20, delivery_service_id: 2
id: 5, price: 14.99, delivery_service_id: 3
expected results:
id: 1, price: 2.20, delivery_service_id: 1
id: 4, price: 1.20, delivery_service_id: 2
id: 5, price: 14.99, delivery_service_id: 3
Due to PostgreSQL being more strict with abiding the SQL standard (rightly so), it requires a bit of tweaking to get the correct results.
The following query returns the correct results for the lowest delivery service price, per delivery service:
DeliveryServicePrice.select('DISTINCT ON (delivery_service_id) *').order('delivery_service_id, price ASC')
I need to add the delivery_service_id attribute to the order condition, or PostgreSQL throws the following column error:
PG::InvalidColumnReference: ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions
Hope this helps anyone who stumbles upon it!
To get the minimum for a single record you can use
DeliveryServicePrice.where(delivery_service_id: x).order(:price).limit(1).first
or if you have a delivery_service object available
delivery_service.prices.order(:price).limit(1).first
UPDATE
If you want all minimums for all service_delivery_ids you can use a group query
DeliveryServicePrice.group(:delivery_service_id).minimum(:price)
which will get you almost where you want to go
{
1: 2.20,
2: 1.20,
3: 14.99
}
with a hash containing the delivery_service_id and the price. (you can't see the price_id )

Is there a guarantee that ActiveRecord returns objects ordered by ID?

My project is hosted on Heroku.
I was surprised when Room.all method returned objects with first object with ID 2 and only then a second object with ID 1. I thought that there were some sort of guarantee that objects are returned already ordered by ID. Should I always call Room.all.order(:id) instead of regular all method?
irb(main):002:0> Room.all
=> #<ActiveRecord::Relation [
#<Room id: 2, color: "rgb(83, 180, 83)", status: "Status #2", created_at: "2014-10-11 14:14:02", updated_at: "2014-10-11 14:18:19">,
#<Room id: 1, color: "rgb(0, 96, 255)", status: "Status #3", created_at: "2014-10-11 14:14:02", updated_at: "2014-10-11 14:18:30">
]>
Nope. Room.all just ends up with the SQL SELECT * FROM rooms; - no order is there. In this event, the order of the records is determined by the database (for instance, in PostgreSQL, I notice it returns me the most recently updated records last).
If you want to ensure there's an order when you call .all, add a default scope which adds it:
default_scope order('rooms.id ASC')

allow duplicate taggings using acts-as-taggable-on

Rails newbie here!
I am using acts-as-taggable-on to implement basic tagging, but I want to modify the default behaviour so that each instance of a model (say a post) can be tagged multiple times using the same tag.
#post.tag_list.add("awesome, awesome", parse: true)
would only create one tag and one tagging in the default behavior. I would like it to use the same tag in the database but to create two unique taggings for that post.
Ultimately I would like to be able to count the number of times #post was tagged with "awesome" so I can make a tag frequency count for each post. What would be the best way to do this that wouldn't require rolling my own tag implementation?
I'm trying to add duplicate tags to a user. I want some user x to have multiple "awesome" tags. The default implementation wont let me.
Default implementation:
#instance.tag_list = "awesome, awesome, awesome"
#instance.save
#instance.reload
#instance.tags =>
[#<ActsAsTaggableOn::Tag id: 1, name: "awesome", taggings_count: 1>]
I want taggings_count to return 3 instead, because I want to make 3 separate taggings to "awesome" even though they all refer to the same tag.
This library already counts the tags. just look at attribute taggings_count of tag record.
As from docs:
#user.tag_list = "awesome, slick, hefty"
#user.save
#user.reload
#user.tags
=> [#<ActsAsTaggableOn::Tag id: 1, name: "awesome", taggings_count: 1>,
#<ActsAsTaggableOn::Tag id: 2, name: "slick", taggings_count: 1>,
#<ActsAsTaggableOn::Tag id: 3, name: "hefty", taggings_count: 1>]
Taggings_coungs is the number of taggings applied.

Rails CollectionProxy randomly inserts in wrong order

I'm seeing some weird behaviour in my models, and was hoping someone could shed some light on the issue.
# user model
class User < ActiveRecord::Base
has_many :events
has_and_belongs_to_many :attended_events
def attend(event)
self.attended_events << event
end
end
# helper method in /spec-dir
def attend_events(host, guest)
host.events.each do |event|
guest.attend(event)
end
end
This, for some reason inserts the event with id 2 before the event with id 1, like so:
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 2, name: "dummy-event", user_id: 1>, #<Event id: 1, name: "dummy-event", user_id: 1>
But, when I do something seemlingly random - like for instance change the attend_event method like so:
def attend_event(event)
self.attended_events << event
p self.attended_events # random puts statement
end
It gets inserted in the correct order.
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 1, name: "dummy-event", user_id: 1>, #<Event id: 2, name: "dummy-event", user_id: 1>
What am I not getting here?
Unless you specify an order on the association, associations are unordered when they are retrieved from the database (the generated sql won't have an order clause so the database is free to return things in whatever order it wants)
You can specify an order by doing (rails 4.x upwards)
has_and_belongs_to_many :attended_events, scope: -> {order("something")}
or, on earlier versions
has_and_belongs_to_many :attended_events, :order => "something"
When you've just inserted the object you may see a different object - here you are probably seeing the loaded version of the association, which is just an array (wrapped by the proxy)

Resources