Generate new record from fixture - ruby-on-rails-3

I have a fixture called "group":
one:
customer: one
name: MyString
In one test I need a couple more so I would like to do something like:
(1..3).each { |t| Group.create!(groups(:one), name: "Group #{t}")}
Is there a way to do something like that with fixtures? (The above of course doesn't work). I know that I could use factories but I want to keep using fixtures.

You can use your fixtures just like active record objects.
# get attr from fixture & delete id
attr_from_fixture = groups(:one).attributes
attr_from_fixture.delete('id')
# create new
(1..3).each do |t|
attr_from_fixture[:name] = "Group #{t}"
Group.create!(attr_from_fixture)
end
UPDATE: even easier
Just remembered the clone method, even easier
(1..3).each do |t|
new_group = groups(:one).clone
new_group.name = "Group #{t}"
new_group.save
end

#dup returns a new object. Clones attrs without id.
(1..3).each do |t|
new_group = groups(:one).dup
new_group.name = "Group #{t}"
new_group.save
end

your second example is a kind of Factory.
if you want to use (yaml) fixtures, you can simply produce them with a script similar to your second example, along the lines of:
y = {"two" => {"customer" => "two", "name" => "londiwe"}}.to_yaml
fi = open("groups.yml", "w")
fi.write(y)
fi.close
edit after comment:
if you just want to take attributes from an existing record and create new records based on that one record, use clone:
1. first find the record you want to clone:
orig = Group.find_by_customer("one")
2. create the clone, change its attributes and save it
(1..3).each do
tmp_clone = orig.clone
tmp_clone.name = "two"
tmp_clone.save
end

Related

Rails ActiveRecord where clause

I want to select Cars from database with where clause looking for best DRY approach for my issue.
for example I have this two parameters
params[:car_model_id] (int)
params[:transmission_id] (int)
params[:from_date]
params[:to_date]
but I dont know which one will be null
if params[:car_model_id].nil? && !params[:transmission_id].nil?
if params[:from_date].nil? && params[:from_date].nil?
return Car.where(:transmission_id => params[:transmission_id])
else
return Car.where(:transmission_id => params[:transmission_id], :date => params[:from_date]..params[:to_date])
end
elseif !params[:car_model_id].nil? && params[:transmission_id].nil?
if params[:from_date].nil? && params[:from_date].nil?
return Car.where(:car_model_id=> params[:car_model_id])
else
return Car.where(:car_model_id=> params[:car_model_id], :date => params[:from_date]..params[:to_date])
end
else
return Car.where(:car_model_id=> params[:car_model_id], :transmission_id => params[:transmission_id], :date => params[:from_date]..params[:to_date])
end
what is best approach to avoid such bad code and check if parameter is nil inline(in where)
You can do:
car_params = params.slice(:car_model_id, :transmission_id).reject{|k, v| v.nil? }
and then:
Car.where(car_params)
Explanation: Since, you're checking if the particular key i.e.: :car_model_id and transmission_id exists in params. The above code would be something like this when you have just :transimission_id in params:
Car.where(:transmission_id => '1')
or this when you have :car_model_id in params:
Car.where(:car_model_id => '3')
or this when you'll have both:
Car.where(:transmission_id => '1', :car_model_id => '3')
NOTE: This will work only when you have params keys as the column names for which you're trying to run queries for. If you intend to have a different key in params which doesn't match with the column name then I'd suggest you change it's key to the column name in controller itself before slice.
UPDATE: Since, OP has edited his question and introduced more if.. else conditions now. One way to go about solving that and to always keep one thing in mind is to have your user_params correct values for which you want to run your queries on the model class, here it's Car. So, in this case:
car_params = params.slice(:car_model_id, :transmission_id).reject{|k, v| v.nil? }
if params[:from_date].present? && params[:from_date].present?
car_params.merge!(date: params[:from_date]..params[:to_date])
end
and then:
Car.where(car_params)
what is best approach to avoid such bad code and check if parameter is
nil inline(in where)
Good Question !
I will make implementation with two extra boolean variables (transmission_id_is_valid and
car_model_id_is_valid)
transmission_id_is_valid = params[:car_model_id].nil? && !params[:transmission_id].nil?
car_model_id_is_valid = !params[:car_model_id].nil? && params[:transmission_id].nil?
if transmission_id_is_valid
return Car.where(:transmission_id => params[:transmission_id])
elseif car_model_id_is_valid
return Car.where(:car_model_id=> params[:car_model_id])
....
end
I think now is more human readable.
First, I would change this code to Car model, and I think there is no need to check if params doesn't exists.
# using Rails 4 methods
class Car < ActiveRecord::Base
def self.find_by_transmission_id_or_model_id(trasmission_id, model_id)
if transmission_id
find_by trasmission_id: trasmission_id
elsif model_id
find_by model_id: model_id
end
end
end
In controller:
def action
car = Car.find_by_transmission_id_or_model_id params[:trasmission_id], params[:car_model_id]
end
edit:
This code is fine while you have only two parameters. For many conditional parameters, look at ransack gem.

How to concatonate conditions with Active Record scope, Rails 4

I am trying to create a scope for an Active Record model using rails 4. The scope deals with 2 conditions which I am able to execute individually:
1) includes(:categories).for_category("proposal")
2) where(:is_published => true)
I need a scope that returns all records where either condition is met (not necessarily both). So something like:
scope :proposal, -> { includes(:categories).for_category("proposal") || where(:is_published => true)}
Thanks for any help.
**EDIT**
Per comment here is the definition of the for_category scope taken from https://github.com/comfy/comfortable-mexican-sofa/blob/b16bb14bec7bffee61949d72a5f2c9eca3c95bf8/lib/comfortable_mexican_sofa/extensions/is_categorized.rb
scope :for_category, lambda { |*categories|
if (categories = [categories].flatten.compact).present?
self.distinct.
joins(:categorizations => :category).
where('comfy_cms_categories.label' => categories)
end
}
You'll need to define a new scope which includes an sql "OR". I don't know what your "for_category" scope is doing but let's say it's something like
scope :for_category, ->(category_name) { where("categories.name" => category_name)}
Then you could make a new scope like this: note that the where arguments are a list of values rather than a hash (the hash form can only produce sql for = tests joined with AND)
scope :published_or_for_category ->(category_name){ includes(:categories).where("categories.name = ? OR proposals.is_published = ?", category_name, true) }
Based on Max's suggestions I got the following to work:
scope :published_or_for_category, ->(category_name){
includes(:categories).where(
"comfy_cms_categories.label = ? OR is_published = ?",
category_name, true
)
}
I might mention that the following seems to work as well:
scope :proposal, -> { includes(:categories).for_category("proposal") | where(:is_published => true) }
Is one better that the other?

How do I clear a Model's :has_many associations without writing to the database in ActiveRecord?

For the sake of this question, let's say I have a very simple model:
class DystopianFuture::Human < ActiveRecord::Base
has_many :hobbies
validates :hobbies, :presence => {message: 'Please pick at least 1 Hobby!!'}
end
The problem is that when a human is updating their hobbies on a form and they don't pick any hobbies, there's no way for me to reflect this in the code without actually deleting all the associations.
So, say the action looks like this:
def update
hobbies = params[:hobbies]
human = Human.find(params[:id])
#ideally here I'd like to go
human.hobbies.clear
#but this updates the db immediately
if hobbies && hobbies.any?
human.hobbies.build(hobbies)
end
if human.save
#great
else
#crap
end
end
Notice the human.hobbies.clear line. I'd like to call this to make sure I'm only saving the new hobbies. It means I can also check to see if the user hasn't checked any hobbies on the form.
But I can't do that as it clears the db. I don't want to write anything to the database unless I know the model is valid.
What am I doing wrong here?
Initialy I also did this same way. Then found out one solution for this issue.
You need to do something like this
params[:heman][:hobby_ids]=[] if params[:human][:hobby_ids].nil?
Then check
if human.update_attributes(params[:human])
Hope you will get some idea...
EDIT:
Make hobbies params like this
hobbies = { hobbies_attributes: [
{ title: 'h1' },
{ title: 'h2' },
{ title: 'h3', _destroy: '1' } # existing hobby
]}
if Human.update_atttributes(hobbies) # use this condition
For this you need to declare accepts_nested_attributes_for :hobbies, allow_destroy: true in your Human model.
See more about this here http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
You can try https://github.com/ryanb/nested_form for this purpose..

Ruby on Rails - search in database based on a query

I have a simple form, where I set up a query that I want to browse, for example panasonic viera.
This is on how I search the term in database:
Product.where("name ilike ?", "%#{params[:q]}%").order('price')
The query looks like %panasonic viera%, but I would need to search the query this way: %panasonic%viera% - I need to find all products, where is in the title the word panasonic or viera... but how to make this query?
One solution would be to break up your query into individual terms and build a set of database queries connected by OR.
terms = params[:q].split
query = terms.map { |term| "name like '%#{term}%'" }.join(" OR ")
Product.where(query).order('price')
If you're using PostgreSQL, you can use pg_search gem. It's support full text search, with option any_word:
Setting this attribute to true will perform a search which will return all models containing any word in the search terms.
Example from pg_search:
class Number < ActiveRecord::Base
include PgSearch
pg_search_scope :search_any_word,
:against => :text,
:using => {
:tsearch => {:any_word => true}
}
pg_search_scope :search_all_words,
:against => :text
end
one = Number.create! :text => 'one'
two = Number.create! :text => 'two'
three = Number.create! :text => 'three'
Number.search_any_word('one two three') # => [one, two, three]
Number.search_all_words('one two three') # => []
How about via ARel
def self.search(query)
words = query.split(/\s+/)
table = self.arel_table
predicates = []
words.each do |word|
predicates << table[:name].matches("%#{word}%")
end
if predicates.size > 1
first = predicates.shift
conditions = Arel::Nodes::Grouping.new(predicates.inject(first) {|memo, expr| Arel::Nodes::Or.new(memo, expr)})
else
conditions = predicates.first
end
where(conditions).to_a
end
This isn't working?
WHERE name LIKE "panasonic" OR name LIKE "viera"

FactoryGirl: why does attributes_for omit some attributes?

I want to use FactoryGirl.attributes_for in controller testing, as in:
it "raise error creating a new PremiseGroup for this user" do
expect {
post :create, {:premise_group => FactoryGirl.attributes_for(:premise_group)}
}.to raise_error(CanCan::AccessDenied)
end
... but this doesn't work because #attributes_for omits the :user_id attribute. Here is the difference between #create and #attributes_for:
>> FactoryGirl.create(:premise_group)
=> #<PremiseGroup id: 3, name: "PremiseGroup_4", user_id: 6, is_visible: false, is_open: false)
>> FactoryGirl.attributes_for(:premise_group)
=> {:name=>"PremiseGroup_5", :is_visible=>false, :is_open=>false}
Note that the :user_id is absent from #attributes_for. Is this the expected behavior?
FWIW, my factories file includes definitions for :premise_group and for :user:
FactoryGirl.define do
...
factory :premise_group do
sequence(:name) {|n| "PremiseGroup_#{n}"}
user
is_visible false
is_open false
end
factory :user do
...
end
end
Short Answer:
By design, FactoryGirl's attribues_for intentionally omits things that would trigger a database transaction so tests will run fast. But you can can write a build_attributes method (below) to model all the attributes, if you're willing to take the time hit.
Original answer
Digging deep into the FactoryGirl documentation, e.g. this wiki page, you will find mentions that attributes_for ignores associations -- see update below. As a workaround, I've wrapped a helper method around FactoryGirl.build(...).attributes that strips id, created_at, and updated_at:
def build_attributes(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
end
So now:
>> build_attributes(:premise_group)
=> {"name"=>"PremiseGroup_21", "user_id"=>29, "is_visible"=>false, "is_open"=>false}
... which is exactly what's expected.
update
Having absorbed the comments from the creators of FactoryGirl, I understand why attributes_for ignores associations: referencing an association generates a call to the db which can greatly slow down tests in some cases. But if you need associations, the build_attributes approach shown above should work.
I think this is a slight improvement over fearless_fool's answer, although it depends on your desired result.
Easiest to explain with an example. Say you have lat and long attributes in your model. On your form, you don't have lat and long fields, but rather lat degree, lat minute, lat second, etc. These later can converted to the decimal lat long form.
Say your factory is like so:
factory :something
lat_d 12
lat_m 32
..
long_d 23
long_m 23.2
end
fearless's build_attributes would return { lat: nil, long: nil}. While the build_attributes below will return { lat_d: 12, lat_m: 32..., lat: nil...}
def build_attributes
ba = FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
af = FactoryGirl.attributes_for(*args)
ba.symbolize_keys.merge(af)
end
To further elaborate on the given build_attributes solution, I modified it to only add the accessible associations:
def build_attributes(*args)
obj = FactoryGirl.build(*args)
associations = obj.class.reflect_on_all_associations(:belongs_to).map { |a| "#{a.name}_id" }
accessible = obj.class.accessible_attributes
accessible_associations = obj.attributes.delete_if do |k, v|
!associations.member?(k) or !accessible.include?(k)
end
FactoryGirl.attributes_for(*args).merge(accessible_associations.symbolize_keys)
end
Here is another way:
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.
The accepted answer seems outdated as it did not work for me, after digging through the web & especially this Github issue, I present you:
A clean version for the most basic functionality for Rails 5+
This creates :belongs_to associations and adds their id (and type if :polymorphic) to the attributes. It also includes the code through FactoryBot::Syntax::Methods instead of an own module limited to controllers.
spec/support/factory_bot_macros.rb
module FactoryBot::Syntax::Methods
def nested_attributes_for(*args)
attributes = attributes_for(*args)
klass = args.first.to_s.camelize.constantize
klass.reflect_on_all_associations(:belongs_to).each do |r|
association = FactoryBot.create(r.class_name.underscore)
attributes["#{r.name}_id"] = association.id
attributes["#{r.name}_type"] = association.class.name if r.options[:polymorphic]
end
attributes
end
end
this is an adapted version of jamesst20 on the github issue - kudos to him 👏