How to implenent mongodb 'includes?' finder condition - ruby-on-rails

I am using Mongoid and have a project and a user model.
in the Project model, I have a field
class Project
include Mongoid::Document
field :name
field :user_ids, :type => Array
end
class User
include Mongoid::Document
field :email
end
I can find all the users belonging to one project, i.e., 'find this project's users'
#project = Project.first # => 'Housework'
User.criteria.id(#project.user_ids) # => ['Bart','Lisa','Maggie']
But I am having a bit trouble finding all the projects belonging to one user, i.e, 'find this user's projects'
#user = User.first # => 'Bart'
Project.where(:user_ids => #user.id) # doesn't work
Project.where(:user_ids.includes => #user.id) # not such method
Project.where(:user_ids => [#user.id]) # doesn't make sense to compare arrays, but tried anyway and doesn't work
I know that you can have another field in the User model to store project_ids, I would gladly do that, but I am just curious, is there a method to be used in finder conditions that works similarly to #includes? in ruby?

I found a solution to this. it is the all_in finder method
example:
Fruit.all[0].colors = ['red','green','blue'] #=> apple
Fruit.all[1].colors = ['yellow','green'] #=> banana
Fruit.all[2].colors = ['red', 'yellow'] #=> pineapple
To find all fruits that have the color red in their 'colors' array field, one can query:
Fruit.all_in(:colors => ['red'])
=>[apple, pineapple]

Related

In RoR, how do I initialize fields in my model based on a field in my model that has no underlying database column?

I’m using Rails 4.2.7. I have an attribute in my model that doesn’t have a database field underneath it
attr_accessor :division
This gets initialized when I create a new object.
my_object = MyObject.new(:name => name,
:age => get_age(data_hash),
:overall_rank => overall_rank,
:city => city,
:state => state,
:country => country,
:age_group_rank => age_group_rank,
:gender_rank => gender_rank,
:division => division)
What I would like is when this field gets set (if it is not nil), for two other fields that do have mappings in the database to get set. The other fields would be substrings of the “division” field. Where do I put that logic?
I'd probably drop the attr_accessor :division and do it by hand with:
def division=(d)
# Break up `d` as needed and assign the parts to the
# desired real attributes.
end
def division
# Combine the broken out attributes as needed and
# return the combined string.
end
With those two methods in place, the following will all call division=:
MyObject.new(:division => '...')
MyObject.create(:division => '...')
o = MyObject.find(...); o.update(:division => '...')
o = MyObject.find(...); o.division = '...'
so the division and the broken out attributes will always agree with each other.
If you try to use one of the lifecycle hooks (such as after_initialize) then things can get out of sync. Suppose division has the form 'a.b' and the broken out attributes are a and b and suppose that you're using one of the ActiveRecord hooks to break up division. Then saying:
o.division = 'x.y'
should give you o.a == 'x' but it won't because the hook won't have executed yet. Similarly, if you start with o.division == 'a.b' then
o.a = 'x'
won't give you o.division == 'x.b' so the attributes will have fallen out of sync again.
I see couple of options here
You can add it in your controller as follows
def create
if params[:example][:division]
# Set those params here
end
end
Or you can use before_save In your model
before_save :do_something
def do_something
if division
# Here!
end
end

Ransack + FlagShihTzu + Active Admin don't play well together

I'm using the brilliant gem flag_shih_tzu to create bitwise boolean flags on a single integer column without requiring a separate DB column for each flag. I have loved this gem for many years now, and it's quite excellent at interplaying with ActiveRecord attributes in all the ways you would normally expect.
However, it does not play well with Ransack and Active Admin out of the box. Active Admin requires me to add permitted params for each flag:
permit_params do
:identity_verified
end
for the :identity_verified "flag" attribute to even show up in filters or index columns, which is fine; I don't mind that. But the real problem I'm having is when I try to use the :identity_verified flag as a filter (it's boolean, of course), Active Admin shows the normal select option for it with Any/Yes/No, but when I first submitted the filter query, I got an exception: undefined method identity_verified_eq for Ransack::Search.
Ok, so I did some research and figured out that I need to add a ransacker for :identity_verified. Did that, and I don't get the exception anymore, but the ransacker doesn't appear to do anything at all. In fact, I intentionally put a syntax error in the block to cause an exception, but Active Admin just returns all the Users, regardless of whether they're :identity_verified or not. That code inside the ransacker block doesn't seem to even get executed. Can anyone help me figure out how to create a proper Ransack definition for :identity_verified?
Here's the code:
User Model :
# ===============
# = FlagShihTzu =
# ===============
has_flags 1 => :guest,
2 => :prospect,
3 => :phone_verified,
4 => :identity_verified,
# ==============
# = Ransackers =
# ==============
# this avoids the identity_verified_eq missing method exception, but
# doesn't appear to do anything else
ransacker :identity_verified, args: [:ransacker_args] do |args|
asdf # <-- should cause an exception
Arel.sql(identity_verified_condition)
end
Active Admin:
ActiveAdmin.register User do
permit_params do
:identity_verified
end
# ...
filter :identity_verified, as: :boolean
# ...
end
The Identity Verified filter shows up as a boolean select in Active Admin, like I expect, but when I submit the filter, (as I metioned above), I get all the Users back, and the ransacker block doesn't even seem to get executed. I've read the Ransack examples. I've dug into the code of all four gems (including Formtastic), and I still can't sort it out.
Here's the POST URL from Active Admin on query submit:
http://example.com/admin/users?q%5Bidentity_verified%5D=true&commit=Filter&order=id_desc
Here's the Rails log to confirm the :identity_verified param is getting passed:
Processing by Admin::UsersController#index as HTML
Parameters: {"utf8"=>"✓", "q"=>{"identity_verified"=>"true"}, "commit"=>"Filter", "order"=>"id_desc"}
Again, the inside of the ransacker block doesn't seem to get executed. I need to understand why, and if it ever does, how to write the proper Arel statement so I can filter on this flag.
Help?
Resurrecting this question as it is the first result on G* here searching for "flag shih tzu activeadmin". Plus it seems OP's solution is not ideal in that it loads & instantiates AR objects for all records fulfilling the flag condition in this part:
results = object_class.send("#{flag}")
results = results.map(&:id)
So here's my current solution for others:
# config/initializers/ransack.rb
Ransack.configure do |config|
config.add_predicate 'flag_equals',
arel_predicate: 'eq',
formatter: proc { |v| (v.downcase == 'true') ? 1 : 0 },
validator: proc { |v| v.present? },
type: :string
end
# app/concerns/has_flags.rb
module HasFlags
extend ActiveSupport::Concern
included { include FlagShihTzu }
class_methods do
def flag_ransacker(flag_name, flag_col: 'flags')
ransacker(flag_name) do |parent|
Arel::Nodes::InfixOperation.new('DIV',
Arel::Nodes::InfixOperation.new('&', parent.table[flag_col], flag_mapping[flag_col][flag_name]),
flag_mapping[flag_col][flag_name])
end
end
end
end
# app/models/foo.rb
class Foo < ActiveRecord::Base
include HasFlags
has_flags 1 => :bar, 2 => :baz
flag_ransacker :bar
end
# app/admin/foos.rb
ActiveAdmin.register Foo do
filter :bar_flag_equals, as: :boolean, label: 'Bar'
end
So, I finally figured out the answer after luckily stumbling on to this post ActiveAdmin Filters with Ransack. The gist of it is properly defining the Active Admin filter using the DSL and more importantly, the appropriate ransacker in the model for the FlagShihTzu flag you want to filter on.
Here's a working example:
models/user.rb:
class User
include FlagShihTzu
# define the flag
has_flags 1 => :identity_verified
# convenience method to define the necessary ransacker for a flag
def self.flag_ransacker(flag)
ransacker flag.to_sym,
formatter: proc { |true_false|
if true_false == "true"
results = object_class.send("#{flag}")
else
results = object_class.send("not_#{flag}")
end
results = results.map(&:id)
results = results.present? ? results : nil
}, splat_params: true do |parent|
parent.table[:id]
end
end
admin/user.rb:
ActiveAdmin.register User do
# A method used like a standard ActiveAdmin::Resource `filter` DSL call, but for FlagShizTzu flags
# A corresponding `flag_ransacker` call must be made on the model, which must include
# the FlagShizTzuRansack module defined in app/concerns/models/flag_shih_tzu_ransack.rb
def flag_filter(flag)
#resource.flag_ransacker flag.to_sym # call the ransacker builder on the model
flag = flag.to_s
filter_name = "#{flag}_in" # we use the 'in' predicate to allow multiple results
filter filter_name.to_sym,
:as => :select,
:label => flag.gsub(/[\s_]+/, ' ').titleize,
:collection => %w[true false]
end
flag_filter :identity_verified
end
And voila, a working sidebar filter for flag-shih-tzu flags. The key was adding the in predicate at the end of the flag name in the filter declarion, instead of excluding it, which defaults to the eq Ransack predicate. Defining the ransacker itself took trial and error using pry and the debugger, but was based largely on the aforementioned post.
Ultimately, I ended up pulling out the inline methods in the two files into modules that I include in the necessary models and AA resource definitions that need them.
app/concerns/models/flag_shih_tzu_ransack.rb:
# Used to define Ransackers for ActiveAdmin FlagShizTzu filters
# See app/admin/support/flag_shih_tzu.rb
module FlagShihTzuRansack
extend ActiveSupport::Concern
module ClassMethods
# +flags are one or more FlagShihTzu flags that need to have ransackers defined for
# ActiveAdmin filtering
def flag_ransacker(*flags)
object_class = self
flags.each do |flag|
flag = flag.to_s
ransacker flag.to_sym,
formatter: proc { |true_false|
if true_false == "true"
results = object_class.send("#{flag}")
else
results = object_class.send("not_#{flag}")
end
results = results.map(&:id)
results = results.present? ? results : nil
}, splat_params: true do |parent|
parent.table[:id]
end
end
end
end
end
app/admin/support/flag_shih_tzu.rb:
# Convenience extension to filter on FlagShizTzu flags in the AA side_bar
module Kandidly
module ActiveAdmin
module DSL
# used like a standard ActiveAdmin::Resource `filter` DSL call, but for FlagShizTzu flags
# A corresponding `flag_ransacker` call must be made on the model, which must include
# the FlagShizTzuRansack module defined in app/concerns/models/flag_shih_tzu_ransack.rb
def flag_filter(flag)
#resource.flag_ransacker flag.to_sym # call the ransacker builder on the model
flag = flag.to_s
filter_name = "#{flag}_in" # we use the 'in' predicate to allow multiple results
filter filter_name.to_sym,
:as => :select,
:label => flag.gsub(/[\s_]+/, ' ').titleize,
:collection => %w[true false]
end
end
end
end
ActiveAdmin::ResourceDSL.send :include, Kandidly::ActiveAdmin::DSL
Then more cleanly, in the model:
class User
include FlagShihTzu
include FlagShihTzuRansack
# define the flag
has_flags 1 => :identity_verified
end
and in the resource definition:
ActiveAdmin.register User do
flag_filter :identity_verified
end
There are probably more elegant implementations of the methods, but having a working solution, I'm moving on. Hope this helps whoever up-voted this question. Ransack documentation leaves a bit to be desired. Thanks to Russ for his post on Jaguar Design Studio, and to the commenters on https://github.com/activerecord-hackery/ransack/issues/36, who helped me better understand how Ransack works. In the end I had to dig into the gems to get my final solution, but I would not have known where to start without their contributions.

Getting rails3-autocomplete-jquery gem to work nicely with Simple_Form with multiple inputs

So I am trying to implement multiple autocomplete using this gem and simple_form and am getting an error.
I tried this:
<%= f.input_field :neighborhood_id, collection: Neighborhood.order(:name), :url => autocomplete_neighborhood_name_searches_path, :as => :autocomplete, 'data-delimiter' => ',', :multiple => true, :class => "span8" %>
This is the error I get:
undefined method `to_i' for ["Alley Park, Madison"]:Array
In my params, it is sending this in neighborhood_id:
"search"=>{"neighborhood_id"=>["Alley Park, Madison"],
So it isn't even using the IDs for those values.
Does anyone have any ideas?
Edit 1:
In response to #jvnill's question, I am not explicitly doing anything with params[:search] in the controller. A search creates a new record, and is searching listings.
In my Searches Controller, create action, I am simply doing this:
#search = Search.create!(params[:search])
Then my search.rb (i.e. search model) has this:
def listings
#listings ||= find_listings
end
private
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline)
listings = listings.includes(:neighborhood).where("listings.headline like ? or neighborhoods.name like ?", key, key) if keywords.present?
listings = listings.where(neighborhood_id: neighborhood_id) if neighborhood_id.present?
#truncated for brevity
listings
end
First of all, this would be easier if the form is returning the ids instead of the name of the neighborhood. I haven't used the gem yet so I'm not familiar how it works. Reading on the readme says that it will return ids but i don't know why you're only getting names. I'm sure once you figure out how to return the ids, you'll be able to change the code below to suit that.
You need to create a join table between a neighborhood and a search. Let's call that search_neighborhoods.
rails g model search_neighborhood neighborhood_id:integer search_id:integer
# dont forget to add indexes in the migration
After that, you'd want to setup your models.
# search.rb
has_many :search_neighborhoods
has_many :neighborhoods, through: :search_neighborhoods
# search_neighborhood.rb
belongs_to :search
belongs_to :neighborhood
# neighborhood.rb
has_many :search_neighborhoods
has_many :searches, through: :search_neighborhoods
Now that we've setup the associations, we need to setup the setters and the attributes
# search.rb
attr_accessible :neighborhood_names
# this will return a list of neighborhood names which is usefull with prepopulating
def neighborhood_names
neighborhoods.map(&:name).join(',')
end
# we will use this to find the ids of the neighborhoods given their names
# this will be called when you call create!
def neighborhood_names=(names)
names.split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
# view
# you need to change your autocomplete to use the getter method
<%= f.input :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, input_html: { data: { delimiter: ',', multiple: true, class: "span8" } %>
last but not the least is to update find_listings
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline).includes(:neighborhood)
if keywords.present?
listings = listings.where("listings.headline LIKE :key OR neighborhoods.name LIKE :key", { key: "#{keywords}")
end
if neighborhoods.exists?
listings = listings.where(neighborhood_id: neighborhood_ids)
end
listings
end
And that's it :)
UPDATE: using f.input_field
# view
<%= f.input_field :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, data: { delimiter: ',' }, multiple: true, class: "span8" %>
# model
# we need to put [0] because it returns an array with a single element containing
# the string of comma separated neighborhoods
def neighborhood_names=(names)
names[0].split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
Your problem is how you're collecting values from the neighborhood Model
Neighborhood.order(:name)
will return an array of names, you need to also collect the id, but just display the names
use collect and pass a block, I beleive this might owrk for you
Neighborhood.collect {|n| [n.name, n.id]}
Declare a scope on the Neighborhood class to order it by name if you like to get theat functionality back, as that behavior also belongs in the model anyhow.
edit>
To add a scope/class method to neighborhood model, you'd typically do soemthing like this
scope :desc, where("name DESC")
Than you can write something like:
Neighborhood.desc.all
which will return an array, thus allowing the .collect but there are other way to get those name and id attributes recognized by the select option.

Rails Model: Name -- First, Last

I'm fairly new to rails, working on a Rails 3 app with a Profile model for users.
In the profile Model I'd like to have a "name" entry, and I'd like to be able to access logical variations of it using simple syntax like:
user.profile.name = "John Doe"
user.profile.name.first = "John"
user.profile.name.last = "Doe"
Is this possible, or do I need to stick with "first_name" and "last_name" as my fields in this model?
It's possible, but I wouldn't recommend it.
I would just stick with first_name and last_name if I were you and add a method fullname:
def fullname
"#{first_name} #{last_name}"
end
Edit:
If you really do want user.profile.name, you could create a Name model like this:
class Name < ActiveRecord::Base
belongs_to :profile
def to_s
"#{first} #{last}"
end
end
This allows you to do:
user.profile.name.to_s # John Doe
user.profile.name.first # John
user.profile.name.last # Doe
The other answers are all correct, in so far as they ignore the #composed_of aggregator:
class Name
attr_reader :first, :last
def initialize(first_name, last_name)
#first, #last = first_name, last_name
end
def full_name
[#first, #last].reject(&:blank?).join(" ")
end
def to_s
full_name
end
end
class Profile < ActiveRecord::Base
composed_of :name, :mapping => %w(first_name last_name)
end
# Rails console prompt
> profile = Profile.new(:name => Name.new("Francois", "Beausoleil"))
> profile.save!
> profile = Profile.find_by_first_name("Francois")
> profile.name.first
"Francois"
As noted on the #composed_of page, you must assign a new instance of the aggregator: you cannot just replace values within the aggregator. The aggregator class acts as a Value, just like a simple string or number.
I also sent a response yesterday with a very similar answer: How best to associate an Address to multiple models in rails?
As Capt. Tokyo said that's a horrible idea but here's how you would do it:
rails g model User full_name:hash
Then you would store data in it like so:
user = User.new
user.full_name = {:first => "Forrest", :last => "Gump"}
Now your problems begin.
To search the field requires both names and you can't do a partial search like searching for all people with the same last name. Worst of all you can store anything in the field! So imagine another programmer mistypes one of the field names so for a week you have {:fist => "Name", :last => "Last"} being inserted into the database! Noooooooooooooooooo!
If you used proper field names you could do this:
user = User.new(:first_name => "First", :last_name => "Last")
Easy to read and no need for hashes. Now that you know how to do it the wrong way, do it the right way. :)
FYI (assume you have a field fullname. ie your profile.name = "John Doe")
class Profile
def name
#splited_name ||= fullname.split # #splited_name would cache the result so that no need to split the fullname every time
end
end
Now, you could do something like this:
user.profile.fullname # "John Doe"
user.profile.name.first # "John"
user.profile.name.last # "Doe"
Note the following case:
user.profile.fullname = "John Ronald Doe"
user.profile.name.first # "John"
user.profile.name.second # "Ronald"
user.profile.name.last # "Doe"
I agree with captaintokyo. You won't miss out the middle names.
Also this method assume no Chinese, Japanese names are input. It's because those names contain no spaces in between first name and last name normally.

Overriding id on create in ActiveRecord

Is there any way of overriding a model's id value on create? Something like:
Post.create(:id => 10, :title => 'Test')
would be ideal, but obviously won't work.
id is just attr_protected, which is why you can't use mass-assignment to set it. However, when setting it manually, it just works:
o = SomeObject.new
o.id = 8888
o.save!
o.reload.id # => 8888
I'm not sure what the original motivation was, but I do this when converting ActiveHash models to ActiveRecord. ActiveHash allows you to use the same belongs_to semantics in ActiveRecord, but instead of having a migration and creating a table, and incurring the overhead of the database on every call, you just store your data in yml files. The foreign keys in the database reference the in-memory ids in the yml.
ActiveHash is great for picklists and small tables that change infrequently and only change by developers. So when going from ActiveHash to ActiveRecord, it's easiest to just keep all of the foreign key references the same.
You could also use something like this:
Post.create({:id => 10, :title => 'Test'}, :without_protection => true)
Although as stated in the docs, this will bypass mass-assignment security.
Try
a_post = Post.new do |p|
p.id = 10
p.title = 'Test'
p.save
end
that should give you what you're looking for.
For Rails 4:
Post.create(:title => 'Test').update_column(:id, 10)
Other Rails 4 answers did not work for me. Many of them appeared to change when checking using the Rails Console, but when I checked the values in MySQL database, they remained unchanged. Other answers only worked sometimes.
For MySQL at least, assigning an id below the auto increment id number does not work unless you use update_column. For example,
p = Post.create(:title => 'Test')
p.id
=> 20 # 20 was the id the auto increment gave it
p2 = Post.create(:id => 40, :title => 'Test')
p2.id
=> 40 # 40 > the next auto increment id (21) so allow it
p3 = Post.create(:id => 10, :title => 'Test')
p3.id
=> 10 # Go check your database, it may say 41.
# Assigning an id to a number below the next auto generated id will not update the db
If you change create to use new + save you will still have this problem. Manually changing the id like p.id = 10 also produces this problem.
In general, I would use update_column to change the id even though it costs an extra database query because it will work all the time. This is an error that might not show up in your development environment, but can quietly corrupt your production database all the while saying it is working.
we can override attributes_protected_by_default
class Example < ActiveRecord::Base
def self.attributes_protected_by_default
# default is ["id", "type"]
["type"]
end
end
e = Example.new(:id => 10000)
Actually, it turns out that doing the following works:
p = Post.new(:id => 10, :title => 'Test')
p.save(false)
As Jeff points out, id behaves as if is attr_protected. To prevent that, you need to override the list of default protected attributes. Be careful doing this anywhere that attribute information can come from the outside. The id field is default protected for a reason.
class Post < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
(Tested with ActiveRecord 2.3.5)
Post.create!(:title => "Test") { |t| t.id = 10 }
This doesn't strike me as the sort of thing that you would normally want to do, but it works quite well if you need to populate a table with a fixed set of ids (for example when creating defaults using a rake task) and you want to override auto-incrementing (so that each time you run the task the table is populate with the same ids):
post_types.each_with_index do |post_type|
PostType.create!(:name => post_type) { |t| t.id = i + 1 }
end
Put this create_with_id function at the top of your seeds.rb and then use it to do your object creation where explicit ids are desired.
def create_with_id(clazz, params)
obj = clazz.send(:new, params)
obj.id = params[:id]
obj.save!
obj
end
and use it like this
create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})
instead of using
Foo.create({id:1,name:"My Foo",prop:"My other property"})
This case is a similar issue that was necessary overwrite the id with a kind of custom date :
# in app/models/calendar_block_group.rb
class CalendarBlockGroup < ActiveRecord::Base
...
before_validation :parse_id
def parse_id
self.id = self.date.strftime('%d%m%Y')
end
...
end
And then :
CalendarBlockGroup.create!(:date => Date.today)
# => #<CalendarBlockGroup id: 27072014, date: "2014-07-27", created_at: "2014-07-27 20:41:49", updated_at: "2014-07-27 20:41:49">
Callbacks works fine.
Good Luck!.
For Rails 3, the simplest way to do this is to use new with the without_protection refinement, and then save:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save
For seed data, it may make sense to bypass validation which you can do like this:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)
We've actually added a helper method to ActiveRecord::Base that is declared immediately prior to executing seed files:
class ActiveRecord::Base
def self.seed_create(attributes)
new(attributes, without_protection: true).save(validate: false)
end
end
And now:
Post.seed_create(:id => 10, :title => 'Test')
For Rails 4, you should be using StrongParams instead of protected attributes. If this is the case, you'll simply be able to assign and save without passing any flags to new:
Post.new(id: 10, title: 'Test').save # optionally pass `{validate: false}`
In Rails 4.2.1 with Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test') works as long as there isn't a row with id = 10 already.
you can insert id by sql:
arr = record_line.strip.split(",")
sql = "insert into records(id, created_at, updated_at, count, type_id, cycle, date) values(#{arr[0]},#{arr[1]},#{arr[2]},#{arr[3]},#{arr[4]},#{arr[5]},#{arr[6]})"
ActiveRecord::Base.connection.execute sql

Resources