I'm trying to grab the gift date of the most recent donation (in $USD) to my organization that has a fund ID not beginning in X (those are Canadian donations). Here's the code (in the controller) that I've used to pull out the gift date of the most recent overall donation, but it's a Canadian gift (fund_id = XP05). How do I get the most recent donation with a fund ID not beginning with an X?
#income_batch = Gift.between(Date.today - 2.weeks, Date.today, :field => :gift_date).order('gift_date DESC').first.gift_date
Assuming fund_id is a string field within your gifts table, you can use not like:
Gift.where("fund_id not like 'X%").between....
You should also avoid using an _id postfix on fields unless they're foreign keys, as Rails convention dictates that _id is specific for this purpose.
If fund_id is a foreign key, and you have a belongs_to :fund inside your Gift model, you can use joins and not like:
Gift.joins(:fund).where("funds.name not like 'X%").between....
This assumes that the field within the funds table is called name.
Related
Within my house table I have a postcode for each house.
I also have an index view for my housing table that contains a table which contains headings such as 'Name', 'Address', 'State'. I was looking to integrate a text_field_tag that would allow user's to input the 9 digits of a postcode in order to filter the table to only show the house with that postcode. However, I also want the user to be able to input the first 4 digits of their postcode e.g. '7644' and it would display all houses that begin with '7644' e.g. two records one with the postcode of the '76444-5645' and '76443-123'. Ideally I would apply logic through my '#search' variable within my houses controller. However I am up to any ideas or tips.
In order to instantiate the house model I would use #house = House.all
I'll be honest I don't know where to begin with this. I have arel_sql in my system so I assume that would be used to query for the search.
It depends on how your models/controllers are defined but you're probably looking for the SQL operator LIKE + '%', which allows you to search for a pattern in a given column. Example:
LIKE Operator
Description
WHERE CustomerName LIKE 'a%'
Finds any values that start with "a"
Assuming you're using ActiveRecord and your model is House, it wouldn't event need to instantiate all houses. Your code would look something like this:
postcode = '7644'
#houses = House.where('postcode LIKE ?', "#{postcode}%") # this returns where the postcode starts with '7644'
another similar SO answer for reference
In order to learn Ruby on Rails I am writing a web app that will be used to sort teams within a tournament given their performance to date.
The complication is that I want each tournament organiser (system user) to be able to use a variety of metrics in an arbitrary order.
Expressed as SQL (my background) I want User 1 to be able to choose:
ORDER BY
METRIC1
,METRIC2
,METRIC3
Whilst User 2 could choose:
ORDER BY
METRIC2
,METRIC3
,METRIC1
How would I accept this user input and use it to create a query on the Team table?
Edit 1 Neglected to mention (sorry) that the metrics themselves are calculated on the fly. Currently they are instance methods (e.g #team.metric1 etc). The abortive attempts I have made so far all involve trying to convert user strings to method names which just seems wrong (and I haven't been able to get it to work).
Edit 2 some example code in teams_controller.rb:
class Team < ApplicationRecord
belongs_to :tournament
has_many :matches
def score_for
matches.sum(:score_for)
end
def score_diff
matches.sum(:score_for) - matches.sum(:score_against)
end
end
ActiveRecord allows multiple arguments to be passed to the order method. So you could do something like:
Team.order(:metric2, :metric3, metric1: :desc)
Another options is you can also use ActiveRecord to dynamically construct a query. ActiveRecord queries are lazily evaluated, so the SQL won't be executed until you call an operation that requires loading the records.
For example you could construct a scope on Team like this:
class Team < ApplicationRecord
scope :custom_order, lambda { |sorting_order|
sorting_order.each do |metric|
order(metric)
end
}
end
You would then just need to input a collection of attributes in the order you wanted the order by clauses to be executed. For example:
Team.custom_order([:metric2, :metric3, :metric1])
A working but probably awful solution:
class Tournament < ApplicationRecord
has_many :teams
serialize :tiebreaker, Array
TIEBREAKER_WHITELIST = %w[score opponent_score possession].freeze
def sorted_teams
list = teams.shuffle
(TIEBREAKER_WHITELIST & tiebreaker).reverse.each do |metric|
list = list.sort_by { |team| [team.send(metric), list.find_index(team)] }
end
list.reverse
end
end
Each tournament has many teams. A tournament instance has a serialized field called tiebreaker. This contains an array of strings something like ["score", "possession"] where each string matches the name of a public instance method on team. Each of these methods returns a number.
The tiebreaker field is in descending order of precedence, so for the above example I would only expect possession to affect sorting for teams with an equal score.
list = teams.shuffle - this randomises the list to start with, in case teams are tied for all of the following tiebreakers.
(TIEBREAKER_WHITELIST & tiebreaker) - this returns only strings that appear in both the tiebreaker field and the whitelist constant to protect against end users running arbitrary methods.
.reverse.each do |metric| - this reverses the array of metrics so that the list is sorted by the lowest precedence metric first.
[team.send(metric), list.find_index(team)] - this is the sort for each metric. send turns the string into a method call. I found find_indexwas necessary to preserver sort order from previous sorts. i.e. if I had first sorted for possession this would preserve the order for teams with the same score.
list.reverse - reverse the list then return it. This was because I wanted higher scoring/possession teams first on my list and sort_by sorts ascending.
I wanted some metrics sorted ascending (opponent_score) and others descending (score) so I handled this in the respective methods, returning negative values for opponent_score for example.
I'm not entirely happy with the solution as is but it does seem to work!
On our product gallery page, I'd really like to query the database to find how many of each product has been purchased, and print that number on the page (for admin users only). I feel like I have all the pieces, I just don't know how to put it together to work.
There is a Products table that is referenced to print out all the products.
There is an LineItems table (Spree::LineItems >> Spree::Orders) where the product can be called/identified by:
#line_items.each do |line_item|
line_item.product.id
end
& this is what defines an order as complete in relation to that line_item:
#line_item.order.state == "complete"
So...I'd like to see if #product.id and #line_item.product.id match (where the #line_item.order.state == "complete") and count how many.
Basically, we iterate each product on the gallery page, so I want to see for each product how many times it appears as a line_item in an order where the order.state is complete
I'm no engineer (as you can obviously tell), but I feel like I'm making this more complicated than it needs to be. Help?
We're using a Postgres database. Thanks in advance!!
That should give you a count of line items associated at the same time with a product with the given id and an order with 'complete' state.
LineItem.joins(:product, :order).where(orders: { state: 'complete' }).where(products: { id: your_product_id }).count
No need to iterate! Just a database query (using ActiveRecord), which also is extremely more efficient than iteration.
I assumed your tables are called orders, products (used in the where clauses) and the associations on the LineItem ActiveRecord object are called order, product (used in the join). Otherwise simply replace those values with the appropriate ones.
I have three rails objects: User, DemoUser and Stats. Both the User and the DemoUser have many stats associated with them. The User and Stats tables are stored on Postgresql (using ActiveRecord). The DemoUser is stored in redis. The id for the DemoUser is a (random) string. The id for the User is a (standard-rails) incrementing integer.
The stats table has a user_id column that can contain either the User id or the DemoUser id. For that reason, the user_id column is a string, rather than an integer.
There isn't an easy way to translate from the random string to an integer, but there's a very easy way to translate the integer id to a string (42 -> "42"). The ids are guaranteed not to overlap (there won't be a User instance with the same id as a DemoUser, ever).
I have some code that manages those stats. I'd like to be able to pass over a some_user instance (which can either be a DemoUser or a User) and then be able to use the id to fetch Stats, update them etc. Also would be nice to be able to define a has_many for the User model, so I can do things like user.stats
However, operations like user.stats would create a query like
SELECT "stats".* FROM "stats" WHERE "stats"."user_id" = 42
which then breaks with PG::UndefinedFunction: ERROR: operator does not exist: character varying = integer
Is there a way to either let the database (Postgresql), or Rails do auto-translation of the ids on JOIN? (the translation from integer to string should be simple, e.g. 42 -> "42")
EDIT: updated the question to try to make things as clear as possible. Happy to accept edits or answer questions to clarify anything.
You can't define a foreign key between two types that don't have built-in equality operators.
The correct solution is to change the string column to be an integer.
In your case you could create a user-defined = operator for varchar = string, but that would have messy side effects elsewhere in the database; for example, it would allow bogus code like:
SELECT 2014-01-02 = '2014-01-02'
to run without an error. So I'm not going to give you the code to do that. If you truly feel it's the only solution (which I don't think is likely to be correct) then see CREATE OPERATOR and CREATE FUNCTION.
One option would be to have separate user_id and demo_user_id columns in your stats table. The user_id would be an integer that you could use as a foreign key to the users table in PostgreSQL and the demo_user_id would be a string that would link to your Redis database. If you wanted to treat the database properly, you'd use a real FK to link stats.user_id to users.id to ensure referential integrity and you'd include a CHECK constraint to ensure that exactly one of stats.user_id and stats.demo_user_id was NULL:
check (user_id is null <> demo_user_id is null)
You'll have to fight ActiveRecord a bit to properly constrain your database of course, AR doesn't believe in fancy things like FKs and CHECKs even though they are necessary for data integrity. You'd have to keep demo_user_id under control by hand though, some sort of periodic scan to make sure they link up with values in Redis would be a good idea.
Now your User can look up stats using a standard association to the stats.user_id column and your DemoUser can use stats.demo_user_id.
For the time being, my 'solution' is not to use a has_many in Rails, but I can define some helper functions in the models if necessary. e.g.
class User < ActiveRecord::Base
# ...
def stats
Stats.where(user_id: self.id.to_s)
end
# ...
end
also, I would define some helper scopes to help enforce the to_s translation
class Stats < ActiveRecord::Base
scope :for_user_id, -> (id) { where(user_id: id.to_s) }
# ...
end
This should allow calls like
user.stats and Stats.for_user_id(user.id)
I think I misunderstood a detail of your issue before because it was buried in the comments.
(I strongly suggest editing your question to clarify points when comments show that there's something confusing/incomplete in the question).
You seem to want a foreign key from an integer column to a string column because the string column might be an integer, or might be some unrelated string. That's why you can't make it an integer column - it's not necessarily a valid number value, it might be a textual key from a different system.
The typical solution in this case would be to have a synthetic primary key and two UNIQUE constraints instead, one for keys from each system, plus a CHECK constraint preventing both from being set. E.g.
CREATE TABLE my_referenced_table (
id serial,
system1_key integer,
system2_key varchar,
CONSTRAINT exactly_one_key_must_be_set
CHECK (system1_key IS NULL != system2_key IS NULL),
UNIQUE(system1_key),
UNIQUE(system2_key),
PRIMARY KEY (id),
... other values ...
);
You can then have a foreign key referencing system1_key from your integer-keyed table.
It's not perfect, as it doesn't prevent the same value appearing in two different rows, one for system1_key and one for system2_key.
So an alternative might be:
CREATE TABLE my_referenced_table (
the_key varchar primary key,
the_key_ifinteger integer,
CONSTRAINT integerkey_must_equal_key_if_set
CHECK (the_key_ifinteger IS NULL OR (the_key_ifinteger::varchar = the_key)),
UNIQUE(the_key_ifinteger),
... other values ...
);
CREATE OR REPLACE FUNCTION my_referenced_table_copy_int_key()
RETURNS trigger LANGUAGE plpgsql STRICT
AS $$
BEGIN
IF NEW.the_key ~ '^[\d]+$' THEN
NEW.the_key_ifinteger := CAST(NEW.the_key AS integer);
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER copy_int_key
BEFORE INSERT OR UPDATE ON my_referenced_table
FOR EACH ROW EXECUTE PROCEDURE my_referenced_table_copy_int_key();
which copies the integer value if it's an integer, so you can reference it.
All in all though I think the whole idea is a bit iffy.
I think I may have a solution for your problem, but maybe not a massively better one:
class User < ActiveRecord::Base
has_many :stats, primary_key: "id_s"
def id_s
read_attribute(:id).to_s
end
end
Still uses a second virtual column, but maybe more handy to use with Rails associations and is database agnostic.
I have three tables (that are relevant to this problem). One table is called organizations.
I also have a table called organization_details, which contains organization_id and multi-row information about the organization.
I work in the event industry, so the organization_details table contains a column called total_attendance, where a person can input an integer of the org's attendance for a certain year.
The third table is called divisions. This has five rows total, with columns division_smallest and division_largest (referring to the attendance range). Each row has a range to separate which division an organization should belong to according to their most recent attendance record.
For example, one row in the division table shows a division_smallest equal to 1 and a division_largest equal to 100000 (again, referring to attendance). Finally, the division table also has a name column (e.g. "Division 1").
I want the app to automatically figure out which division an organization belongs to according to their most recent total_attendance. Ideally, the division's name would display in the organization index and show pages.
I'd like to make a custom method for this, but am unsure how best to tackle it. I've read a little bit about .between? as in (possibly) .between?(division.division_smallest, division.division_largest) return "#{division.name}"
...But I am not sure how the entire method would work or if I need to steer away from that entirely. I would greatly appreciate any insight into this!
My suggestion is to add the following method to organization.rb
def division_name
last_details = organization_details.order('created_at DESC').first
if last_details.present?
Division.where(':attendance >= division_smallest AND :attendance <= division_largest', attendance: last_details.attendance).first.name
else
"None"
end
end
The code first grabs the organization details that have been created most recently. If the organization has organization details it uses the attendance value to select the appropriate division and it returns that division's name. If the organization doesn't have any organization_details it returns the string "None". You may also want to handle the case where the attendance isn't inside of the range on any of the divisions you have defined.
I hope this points you in the right direction.
A naive implementation might look something like this:
class Division
def self.for_attendance(total)
first('? BETWEEN divisions.division_smallest AND divisions.division_largest', total)
end
end
class Organization
def latest_division
Division.for_attendance(organization_details.last.try(:total_attendance))
end
end
Now calling some_organization.latest_division will pull the latest division for that organization. This is great for a 'show' page, but will run you into trouble when you have an 'index' with many Organizations - these 2 queries will need to run for each Organization (an N+1 problem). Instead use this:
class Division
def self.merge_latest!(organizations)
left_join = "LEFT JOIN organization_details od2 ON organization_details.organization_id = od2.organization_id AND organization_details.created_at < od2.created_at"
subquery = OrganizationDetails.where(organization_id: organizations.map(&:id)).
joins(left_join).
where(od2: {id: nil}).to_sql
divisions = joins("#{subquery} as t ON t.total_attendance divisions.division_smallest AND divisions.division_largest").
select('divisions.*, t.organization_id')
organizations.each {|org| org.latest_division = divisions.detect{|d| d.organization_id == org.id}
end
end
def Organization
attr_accessor :latest_division
end
Now you can call Division.merge_latest!(organizations) to collect the latest division for all the organizations in a single query, addressable via an organization's :latest_division attribute.