Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
activerecord_filter_by
Advanced tools
This is a gem created with the intent of being a go-to sollution when it comes to filtering your records with active record.
The way we try to do it is creatin a filter
method, where you can execute complex queries over your models, or simple ones as well, always maintaining the logic that you pass down.
The gem also allows you to configure your own custom filters, with all the complex logic that you want, following a pattern developed by the rails guys, used on the scope
method.
Install the gem and add to the application's Gemfile by executing:
$ bundle add active_record-filterable
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install active_record-filterable
Or just add it to your Gemfile
gem 'active_record-filterable
For the models that you want to respond to the filter
method, you should add a act_as_filterable
to it, like this:
class User < ActiveRecord::Base
act_as_filterable
end
When you do it, the gem will redefine the ActiveRecord::DynamicMatchers
for your columns, because the rails sollution, unfortunally doesn't retur a Class::ActiveRecord_Relation
, so it can't be queried further.
# This model
User(id: integer, name: string, email: string, password_digest: string, created_at: datetime, updated_at: datetime)
# will have:
User#filter_by_id
User#filter_by_name
User#filter_by_email
User#filter_by_password_digest
User#filter_by_created_at
User#filter_by_updated_at
Note: The filter_by_
prepend it's very important for this gem, as we are going to see further
This gem introduces the filter method for all models that have the act_as_filterable
in it, and to use it, you could just:
User.filter(name: 'Jhon')
# will produce the following query
# => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"name\" = 'Jhon'"
And you can query like normal, continuing to give named params to it, and it will be continuing the query with the AND operator, like so:
User.filter(name: 'Jhon', email: 'jhon.smith@email.com')
# will produce the following query
# => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"name\" = 'Jhon'" AND \"users\".\"email\" = 'jhon.smith@email.com'"
So far, nothing spicy, right? Pretty much like the where
method, default on the models.
But now, you can query with OR conditions, just making sure that you are passing down an Array instead of a Hash.
User.filter([{name: "Jhon"}, {name: "doe"}])
# will produce
# => "SELECT \"users\".* FROM \"users\" WHERE ( \"users\".\"name\" = 'Jhon' AND \"users\".\"name\" = 'doe' )"
And going down even further, if you need to have a complex case with and OR condition nested within the AND condition, just use a :or
key, passing an array to it, like so:
User.filter([{name: "Jhon", email: "joaquin", or: [{name: "smith"}, {email: "smit"}]}, {name: "doe"}])
# will produce the following query
# => "SELECT \"users\".* FROM \"users\" WHERE ( \"users\".\"name\" = 'Jhon' AND \"users\".\"email\" = 'joaquin' AND ( \"users\".\"name\" = 'smith' OR \"users\".\"email\" = 'smit' ) AND \"users\".\"name\" = 'doe' )"
The rule of thumb is:
You can go as nested as you want, until you reach the call stack limit.
TLDR: Every class methods started with filter_by_
will generate a valid key (i.e.: filter_by_child_name
-> :child_name
will be a valid key)
So, be careful because the keys you choose to pass down NEED to map out to a class method with its name prepended with filter_by_
.
Every class method prepended with filter_by
will turn into a valid key for the filter method, but we put down some helper methods to help you define it, using the scope
pattern, already present in ActiveRecord
For instance if you have a custom, joined query to perform and you want for it to be in your possible keys for the filter method, you define it as the following:
class User < ActiveRecord::Base
act_as_filterable
has_many :posts, class_name: 'Post', foreign_key: 'author_id'
has_many :likes, class_name: 'Like', foreign_key: 'author_id'
has_many :comments, class_name: 'Comment', foreign_key: 'author_id'
define_filter :posts_that_have_more_than_n_comments, ->(number_of_comments) { joins(posts: :comments).group('posts.id').having('count(comments.id) > ?', number_of_comments) }
# This is the same that
# scope :filter_by_posts_that_have_more_than_n_comments, ->(number_of_comments) { joins(posts: :comments).group('posts.id').having('count(comments.id) > ?', number_of_comments) }
# or
# def self.filter_by_posts_that_have_more_than_n_comments(n)
# joins(posts: :comments).group('posts.id').having('count(comments.id) > ?', n)
# end
end
The define_filter
method takes up to 3 arguments, only requiring the first 2:
Note: You can define you own class methods, if you want, or use the already well tested scope
API, rather than that from the gem, it's really up to you.
If you want, and it is totally up to you, it will not have any impact on the filter methor whatsoever, you can pass down a block as a third argument and use it as an extension for your filters, like so:
class User < ActiveRecord::Base
act_as_filterable
has_many :posts, class_name: 'Post', foreign_key: 'author_id'
has_many :likes, class_name: 'Like', foreign_key: 'author_id'
has_many :comments, class_name: 'Comment', foreign_key: 'author_id'
define_filter :posts_that_have_more_than_n_comments, ->(number_of_comments) { joins(posts: :comments).group('posts.id').having('count(comments.id) > ?', number_of_comments) } do
def newer_than(date)
where('posts.created_at > ?', date)
end
def older_than(date)
where('posts.created_at < ?', date)
end
end
end
With this you will be able to call:
irb(main):001:0> User.filter_by_posts_that_have_more_than_n_comments(2).newer_than(2.days.ago)
# => "SELECT \"users\".* FROM \"users\" INNER JOIN \"posts\" ON \"posts\".\"author_id\" = \"users\".\"id\" INNER JOIN \"comments\" ON \"comments\".\"post_id\" = \"posts\".\"id\" WHERE (posts.created_at > '2022-12-15 11:54:47.858206') GROUP BY \"posts\".\"id\" HAVING (count(comments.id) > 30)"
This is only present, because the implementation is flexible enough for you to make your body as a proc as well as a block, and the block has this options for you, so use it as you please.
NOTE: methods that extend your filters functionality will not add new valid keys to your filter
method
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Bug reports and pull requests are really welcome on GitHub at https://github.com/[USERNAME]/activerecordfilterable.
The gem is available as open source under the terms of the MIT License.
FAQs
Unknown package
We found that activerecord_filter_by demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.