I have an STI subclass, SoftCredits::EventBrite < SoftCredit, that has a function called on creation of SoftCredit
def create_activity
options = {
owner: person,
created_at: additional_data['created_at'],
updated_at: additional_data['updated_at'],
trail_item_type: "Activities::EventBriteRegistration",
trail_item_id: self.id
}
Activity.create(options)
end
Activity model is polymorphic
belongs_to :trail_item, :polymorphic => true
The Activities::EventBriteRegistration model declaration looks like so:
class Activities::EventBriteRegistration < SoftCredit
def self.default_scope
unscoped
end
end
My issue is when I have an Activity type of Activities::EventBriteRegistration and I query on it event_brite_registration_activity.trail_item, the query is construct like so:
SELECT "soft_credits".* FROM "soft_credits" WHERE "soft_credits"."type" IN ('Activities::EventBriteRegistration') AND "soft_credits"."id" =[id] LIMIT 1.
Of course, there is no type Activities::EventBriteRegistration for SoftCredits and I am confused how I should construct this so when I do event_brite_registration_activity.trail_item, it queries on SoftCredit to find the correct soft credit.
Related
I have models Category and Transactions.
Category has_many transactions, Transaction belongs_to category.
And i have scope for Category:
#relation = Category.all
#relation.joins(:transactions).where('transactions.created_at >= ?', 1.month.ago).
group('categories.id').order('SUM(transactions.debit_amount_cents) DESC')
It displays categories and sorts them by sum of transactions.debit_amount_cents
I want to display the amount for all its transactions along with each category.
Like:
id: 1,
name: "Category1",
all_amount: *some value* #like this
How can I improve this scope?
class Category < ApplicationRecord
# remember that scope is just a widely abused syntactic sugar
# for writing class methods
def self.with_recent_transactions
joins(:transactions)
.where('transactions.created_at >= ?', 1.month.ago)
.select(
'categories.*',
'SUM(transactions.debit_amount_cents) AS total_amount'
)
.order('total_amount DESC')
.group('categories.id')
end
end
If you select a column or an aggregate and give it an alias it will be available on the resulting model instances.
Category.with_recent_transactions.each do |category|
puts "#{category.name}: #{category.total_amount}"
end
For portability you can write this with Arel instead of SQL strings which avoids hardcoding stuff like table names:
class Category < ApplicationRecord
def self.with_recent_transactions
t = Transaction.arel_table
joins(:transactions)
.where(transactions: { created_at: Float::Infinity..1.month.ago })
.select(
arel_table[Arel.star]
t[:debit_amount_cents].sum.as('total_amount')
)
.order(total_amount: :desc) # use .order(t[:debit_amount_cents].sum) on Oracle
.group(:id) # categories.id on most adapters except TinyTDS
end
end
In Rails 6.1 (backported to 6.0x) you can use beginless ranges to create GTE conditions without Float::Infinity:
.where(transactions: { created_at: ..1.month.ago })
I have a model Camera in which
belongs_to :user, :foreign_key => 'owner_id', :class_name => 'EvercamUser'
i have asscociation like this. when i do Camera.first
#<Camera id: 6, created_at: "2013-12-12 17:30:32", updated_at: "2015-11-19 10:19:33", exid: "dublin-rememberance-floor2", owner_id: 4, is_public: true
i can get owner id, is there any way to create such function that , along side getting owner id, i can get the data which linked with this id for example at id = 4
#<EvercamUser id: 4, created_at: "2013-12-12 16:43:46", updated_at: "2015-04-16 15:23:19", firstname: "Garrett", lastname: "Heaver", username: "garrettheaver"
this user is present, what if when i do Camera.first then instead of OnwerID, how can i get the owners Name?
Any help will be appreciated!
how can i get the owners Name
You'd call the associative object on the Camera object:
#camera = Camera.find x
#user = #camera.user
#user.name #-> outputs name of associated user object
... this will allow you to call the attributes of the child object on it: #camera.user.name or #camera.user.email, etc
Off topic, but I always include a reference to delegate for this type of issue; it avoids the law of demeter (where you're using more than one point to access data).
This would allow you to use:
#app/models/camera.rb
class Camera < ActiveRecord::Base
belongs_to :user, foreign_key: :owner_id, class_name: 'EvercamUser'
delegate :name, to: :user, prefix: true #-> #camera.user_name
end
#camera = Camera.find x
#camera.user_name #-> outputs the user's name on the camera object (not user object)
To give you some context, Rails uses ActiveRecord to invoke/create objects for you.
In line with the object orientated nature of Rails, ActiveRecord is known as an ORM (Object Relationship Mapper). This basically allows you to create an object through ActiveRecord, and if it is associated to another (as Rails does with its associations), it will append the associated object onto the parent.
Thus, when you're asking about calling owner_id, you're referring to the foreign_key of the association (the database column which joins the two tables together):
What you need is to reference the associated object, which I've detailed above.
What about using join here?
Camera.all.joins(:evercamusers)
Camera.where(:id => 1).joins(:users).first
Note: I'm a bit unsure if the correct parameter should be ":users" or ":evercamusers"
http://apidock.com/rails/ActiveRecord/QueryMethods/joins
You could also add methods to your class to do this.
class Camera < ActiveRecord::Base
belongs_to :user, :foreign_key => 'owner_id', :class_name => 'EvercamUser'
def firstname
self.user.firstname
end
end
When you try to output data from Camera like this:
#<Camera id: 6, created_at: "2013-12-12 17:30:32", updated_at: "2015-11-19 10:19:33", exid: "dublin-rememberance-floor2", owner_id: 4, is_public: true
It won't show. But if you call the method like this, it should work:
Camera.first.firstname # "Garrett"
Also, if JSON is acceptable you could override the as_json method.
def as_json(options={})
{ :firstname => self.user.firstname }
end
Then call it with
Camera.first.as_json
If you need to do it with all, simply loop it
Camera.all.each { |c| puts c.firstname }
I would love to write this query using named scopes only. The reason is simple, I don't want to change code everywhere when I change the way a Client is considered active (same for User considered connectable)
Here is my code
client.rb
class Client < ActiveRecord::Base
has_one :user
scope :actives, -> { where(starting_date: a_date, finishing_date: another_date, enabled: true) }
end
user.rb
class User < ActiveRecord::Base
belongs_to :client
scope :connectable, -> { where.not(access_token: nil, instance_url: nil) }
end
And here you can find my attempts to write this query:
# What I would like to write
User.eager_load(:client).connectable.where("client is in :actives scope")
# Current options
# 1 - (but this violates dry)
User.eager_load(:client).connectable.where(['clients.starting_date = ? AND clients.finishing_date = ? AND clients.enabled = ?', a_date, another_date, true).references(:client)
# 2 - My best attempt so far
User.connectable.where(client_id: Client.actives.pluck(:id))
And a link to the GIST for reference
What you're looking for is ARel's merge method. (An article on this here.)
Try this:
User.connectable.includes(:client).merge(Client.actives).references(:clients)
The includes(:client) will introduce the :client relation from User which will make the .merge(Client...) scope work. And the .references is required by Rails 4+ to explicitly state which table the includes statement will be referencing.
I'm not sure how good of an idea it is, but as long as you ensure your column names are name spaced properly (ie. you can't have 'foo' in both Client and User without prefixing the table name) then this should work:
User.eager_load(:client).connectable.where(Client.active.where_values.map(&:to_sql).join(" AND "))
There must be a nicer way, but the above works for me:
Course model:
scope :valid, -> { enabled.has_country }
scope :has_country, -> { where.not(country_id:nil) }
Rails console:
> Course.where(Course.valid.where_values.map(&:to_sql).join(" AND ")).to_sql
=> "SELECT \"courses\".* FROM \"courses\"
WHERE (\"courses\".\"is_enabled\" = 't'
AND \"courses\".\"country_id\" IS NOT NULL)"
Is it possible to make a query like this? (Pseudo-code)
u=User.includes(all_delegated_attributes_from_relationships).all
How?
Further explanation:
class User<ActiveRecord::Base
has_one :car
delegate :wheel, :brake, :motor, to: :car, prefix: true
end
and then:
u=User.includes(delegated_car_parts).all
#<user id: 1, car_id: 1, name: "John", car_wheel: "foo", car_motor: "bar", car_brake: false>
I know that this can sound a little odd but I have to add a feature to an old app to export all delegated attributes from a model to CSV and this model has 14 relationships and 300 delegations... I just learnt Demeter's law when I made this app...
Assuming wheel, break and motor are relationships on car, you can do this:
User.includes(:car => [:wheel, :brake, :motor]).all
There is no build-in method to do this. You could try sth like:
class User < ActiveRecord::Base
has_one :car
DelegatedCarMethods = [:wheel, :brake, :motor]
delegate *DelegatedCarMethods, to: :car, prefix: true
scope :with_car_delagations, lambda do
select_array = ["#{table_name}.*"]
select_array += DelegateCarMethods.map {|method| "#{Car.table_name}.#{method} AS car_#{method}"}
select(select_array.join(', ')).joins(:car)
end
end
But it isn't extremely pretty. Why do you need this? Calling user.wheel or user.motor doesn't feel right.
Maybe the title is confusing, but I didn't know how to explain my doubt.
Say I have the following class methods that will be helpful in order to do chainings to query a model called Player. A Player belongs_to a User, but if I want to fetch Players from a particular village or city, I have to fetch the User model.
def self.by_village(village)
joins(:user).where(:village => "village")
end
def self.by_city(city)
joins(:user).where(:city => "city")
end
Let's say I want to fetch a Player by village but also by city, so I would do...
Player.by_city(city).by_village(village).
This would be doing a join of the User twice, and I don't think that is correct.. Right?
So my question is: What would be the correct way of doing so?
I haven't tried that, but I would judge the answer to your question by the actual sql query ActiveRecord generates. If it does only one join, I would use it as you did, if this results in two joins you could create a method by_village_and_city.
OK. Tried it now:
1.9.2p290 :022 > Player.by_city("Berlin").by_village("Kreuzberg")
Player Load (0.3ms) SELECT "players".* FROM "players" INNER JOIN "users" ON "users"."id" = "players"."user_id" WHERE "users"."city" = 'Berlin' AND "users"."village" = 'Kreuzberg'
=> [#<Player id: 1, user_id: 1, created_at: "2012-07-28 17:05:35", updated_at: "2012-07-28 17:05:35">, #<Player id: 2, user_id: 2, created_at: "2012-07-28 17:08:14", updated_at: "2012-07-28 17:08:14">]
So, ActiveRecors combines the two queries, does the right thing and I would use it, except:
I had to change your implementation though:
class Player < ActiveRecord::Base
belongs_to :user
def self.by_village(village)
joins(:user).where('users.village' => village)
end
def self.by_city(city)
joins(:user).where('users.city' => city)
end
end
and what you're doing is usually handled with parameterized scopes:
class Player < ActiveRecord::Base
belongs_to :user
scope :by_village, lambda { |village| joins(:user).where('users.village = ?', village) }
scope :by_city, lambda { |city| joins(:user).where('users.city = ?', city) }
end