(Ruby on Rails) Help to add Hash to DB - ruby

Ruby 2.0, Rails 4.1.0, sQlite3. I have to create params in Hash to add to my db. I have yaml file and model:
class User < ActiveRecord::Base
has_many :tweets
accepts_nested_attributes_for :tweets
end
class Tweet < ActiveRecord::Base
belongs_to :user
end
I have 15 users. Try to run this code
UsTw_model_params = {user: []}
count_of_users = seeds_yml["users"].length - 1
for i in 0..count_of_users do
UsTw_model_params[:user][i] = {}
UsTw_model_params[:user][i][:name] = seeds_yml["users"][i]["name"]
UsTw_model_params[:user][i][:email] = seeds_yml["users"][i]["email"]
UsTw_model_params[:user][i][:password] = seeds_yml["users"][i]["password"]
UsTw_model_params[:user][i][:avatar] = seeds_yml["users"][i]["avatar"]
UsTw_model_params[:user][i][:tweets_attributes] = []
if seeds_yml["users"][i].has_key?(:tweets)
count_of_tweets = seeds_yml["users"][i]["tweets"].length - 1
for j in 0..count_of_tweets do
UsTw_model_params[:user][i][:tweets_attributes][j] = {}
UsTw_model_params[:user][i][:tweets_attributes][j][:post] = seeds_yml["users"][i]["tweets"][j]["post"]
UsTw_model_params[:user][i][:tweets_attributes][j][:created_at] = seeds_yml["users"][i]["tweets"][j]["created_at"]
end
end
end
User.create(UsTw_model_params[:user])
And get error ActiveRecord::UnknownAttributeError: unknown attribute: user
What's the matter?

First of all anything starting with a capital letter in Ruby is treated as a constant.
Reassigning a constant results in a warning.
Specifically:
UsTw_model_params = {user: []} # is a constant!
Examples:
class User
module Huggable
TAU = 2 * PI
Variables should by convention be in snakecase such as:
user_tweet_params
Second, you can leave for loops behind. Ruby has far better methods to loop within arrays and other enumables such as map, each, etc.
# Loop though seeds_yml["users"] and create a new array
user_tweet_params = seeds_yml["users"].map do |user|
# with_indifferent_access allows us to use symbols or strings as keys
user = user.with_indifferent_access
user.slice!(:name, :email, :password, :avatar, :tweets)
# Does the user have tweets?
# We use try(:any?) incase user["tweets"] is nil
if user[:tweets].try(:any?)
# Take the tweets and nest then under tweet_attributes
user[:tweet_attributes] = user.tweets.map do |tweet|
tweet.with_indifferent_access.slice!(:post, :created_at)
end
end
# remove the original tweets
user.delete(:tweets)
# return user
user
end

Related

Refactor with Strategy Pattern. In Ruby

Heads up! In the below example, using a pattern is probably overkill... however, if I were extending this to count genres, count the members in a given band, count the number of fans, count the number of venues played, count the number of records sold, count the number of downloads for a specific song etc... it seems like there could be a ton of stuff to count.
The Goal:
To create a new function that chooses the correct counting function based on the input.
The Example:
class Genre < ActiveRecord::Base
has_many :songs
has_many :artists, through: :songs
def song_count
self.songs.length
end
def artist_count
self.artists.length
end
end
P.S. If you are also a curious about this question, you may find this other question (unfortunately answered in C#) to be helpful as a supplemental context. Strategy or Command pattern? ...
In Ruby you can implement a strategy pattern quite easily using an (optional) block (assuming it's still unused).
class Genre < ActiveRecord::Base
has_many :songs
has_many :artists, through: :songs
def song_count(&strategy)
count_using_strategy(songs, &strategy)
end
def artist_count(&strategy)
count_using_strategy(artists, &strategy)
end
private
def count_using_strategy(collection, &strategy)
strategy ||= ->(collection) { collection.size }
strategy.call(collection)
end
end
The above code defaults to using the size strategy. If you ever want to use a specific strategy in a specific scenario you can simply provide the strategy alongside the call.
genre = Genre.last
genre.song_count # get the song_count using the default #size strategy
# or provide a custom stratigy
genre.song_count { |songs| songs.count } # get the song_count using #count
genre.song_count { |songs| songs.length } # get the song_count using #length
If you need to re-use some strategies more often you could save them in a constant or variable:
LENGTH_STRATEGY = ->(collection) { collection.length }
genre.artist_count(&LENGTH_STRATEGY)
Or create a specific class for them if they are more complicated (currently overkill):
class CollectionStrategy
def self.to_proc # called when providing the class as a block argument
->(collection) { new(collection).call }
end
attr_reader :collection
def initialize(collection)
#collection = collection
end
end
class LengthStrategy < CollectionStrategy
def call
collection.length
end
end
genre.artist_count(&LengthStrategy)

Rails 3 serialized model field form_for and field_for not generating correct name

I have this model:
class CompanyCrawler < ActiveRecord::Base
....
serialize :entry_pages, Array
def entry_page_objects
entry_pages.map { |url| EntryPage.new(url) }
end
def entry_page_objects_attributes=(attributes)
# ...
end
....
end
This form to render the model:
.....
%p
%p
= crawler_form.label 'Entry pages'
= crawler_form.text_area :entry_pages_text, size: '80x6'
%ul.entry-pages
= crawler_form.fields_for :entry_page_objects do |entry_page_field|
%li=entry_page_field.text_field :url, size: 80
%a{href: '#', class: 'add-button'} Add Entry Page
The problem I have is that the form renders the entry_page_object input names incorrectly(e.g. company_crawler[entry_page_objects_attributes][0][url] instead of company_crawler[entry_page_objects][0][url]). I am really not sure what to do, I have read the documentation and the example says that just by defining attr_attributes=(attributes) and persisted? I will be able to use fields_for for collections just if they were associations defined with accept_nested_fields.
I have seen different solutions like just giving String 'entry_page_objects[]' to fields_for but I want to be consistent with rails naming convention and I know I can use form_tag instead of form_for but I want to make fields_for work as expected.
Here is some information for all that have not understood properly how nested_attributes works, like me.
What I have reported as issue is actually how it is supposed to work. When we have, let say, this model:
class Foo < ActiveRecord::Base # it has name attribute
has_many :larodis
accepts_nested_attributes_for :larodi
end
class Larodi < ActiveRecord::Base # it has name attribute
belongs_to :foo
end
This definition gives me the possibility to create Foo with many Larodi's just by giving a hash of parameters. For example:
x = Foo.create(name: 'Josh', larodi_attributes: [ {name: 'Wayne'} ]
x.larodis.map(&:name) # ['Wayne']
Now comes the part where #field_for understands if we have nested attribute to work with. We check this by looking for name_attributes= method. If it is defined #fields_for generates form of the type <input ... name=object[name][INDEX][method]>... where index is just an integer.
Keep in mind that when implementing custom name_attibutes(attributes) you must check attributes type - it can be Array like the example, it can be Hash of this type:
{ 1 => { ... } , 2 => { ... } }
Just like a hash representing array, where the key is index and value is the value for this index.
The answear looks like this:
_form.html.haml
....
= crawler_form.fields_for :entry_pages do |entry_page_field|
%li
=entry_page_field.text_field :url, size: 80
...
company_crawler.rb
class CompanyCrawler < ActiveRecord::Base
....
serialize :entry_pages, Array
def entry_pages_attributes=(attributes)
self.entry_pages = attributes_collection(attributes).map do |attribute|
EntryPage.new(attribute[:url])
end
end
def entry_pages=(entry_pages)
entry_pages = entry_pages.map do |entry_page|
cast_entry_page_to_entry_page_object(entry_page)
end
write_attribute(:entry_pages, entry_pages)
end
...
private
def attributes_collection(attributes)
case attributes
when Array
attributes
when Hash
attributes.values
end
end
def cast_entry_page_to_entry_page_object(entry_page)
case entry_page
when String
EntryPage.new(entry_page)
when EntryPage
entry_page
end
end
end
For clarity I have removed entry_page_objects and use only entry_pages.

undefined method `save' for 1:Fixnum [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I made some variables that is related to models and I want to save the new variable relating to the control structure, but I can't. It said "NoMethodError - undefined method `save' for 1:Fixnum:".
What I want to make is that the function if This program get 4 people, It will show the member name for that members. 5th member will be in the next group.
Anyone can solve this?
or if you need more information, please let me know.
Thanks
This is Waitinglists_controller
class WaitinglistsController < ApplicationController
before_action :authenticate
def new
#waitinglist = current_user.created_waitinglists.build
end
def create
#waitinglist = current_user.created_waitinglists.build(waitinglist_params)
if #waitinglist.save
redirect_to waitinglist_waiting_path(#waitinglist, #owner)
else
render :new
end
end
def waiting
#group_number = Waitinglist.select(:count_number).last
#already_group_people = Waitinglist.where(count_number: #group_number).count
#current_person_group_number = current_user.created_waitinglists.select(:count_number)
#current_group_people = Waitinglist.where(count_number: #current_person_group_number).count
case #already_group_people
when 0
#current_person_group_number = 1
#current_person_group_number.save
when 1..2
#current_person_group_number = #group_number
#current_person_group_number.save
when 3
#current_person_group_number = #group_number
#current_person_group_number.save 
redirect_to show_waitinglist_path
when 4
group_number += 1
#current_person_group_number = #group_number
#current_person_group_number.save
end
end
def show
#current_person_group_number = current_user.created_waitinglist.select(:count_number)
#matched_people = Waitinglist.find(count_number: #current_person_group_number)
#matched_people == 0 if #matched_people = nil
end
private
def created_by?(user)
return false unless user
owner_id == user.id
end
def waitinglist_params
params.require(:waitinglist).permit(:look_like, :id)
end
end
This is Sessions controller for User loggin
class SessionsController < ApplicationController
def create
user = User.find_or_create_from_auth_hash(request.env['omniauth.auth'])
session[:user_id] = user.id
redirect_to root_path
end
def destroy
reset_session
redirect_to root_path
end
end
Application contrtoller is this
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :current_user, :logged_in?
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def logged_in?
!!session[:user_id]
end
def authenticate
return if logged_in?
redirect_to root_path
end
end
Under codes are for models
class Waitinglist < ActiveRecord::Base
belongs_to :waiting_person, class_name: 'User'
after_initialize :init
def init
self.count_number ||= 1 #will set the default value only if it's nil
end
end
class User < ActiveRecord::Base
has_many :created_waitinglists, class_name: 'Waitinglist', foreign_key: :owner_id
def self.find_or_create_from_auth_hash(auth_hash)
provider = auth_hash[:provider]
uid = auth_hash[:uid]
name = auth_hash[:info][:name]
image_url = auth_hash[:info][:image]
User.find_or_create_by(provider: provider, uid: uid) do |user|
user.nickname = name
user.image_url = image_url
end
end
end
It's quite clear to me. You call save on #current_person_group_number, which is instance of Fixnum, so it doesn't have save method defined.
Unfortunately the code you wrote makes very little sense and it is pretty hard to understand what you're trying to do here.
Firstly, you overuse instance variables. If you're not gona use them in another methods (it is hard to say as you haven't post rest of your class)
Secondly, you overuse select method.
#group_number = Waitinglist.select(:count_number).last
All it does is changing the SELECT statement when querying the database for models, but it still returns the model, not a number or field value. So #group_number is not a number - it is a WaitingList instance. If you want a number do:
group_number = WaitingList.last.count_number
(posting now as question may be closed in a second. Will update later if that won't happen)
You can only save the ActiveRecord objects, which means you need to have it somewhere. Apparantly you want to update #current_person_group_number, however you can't reassign this variable to do the trick. You have to get the whole model, change its attribute and then save the model. It would look sth like:
current_waiting_list = current_user.created_waitinglists.last # This seems to be a collection, you need to tell here which waiting list you want to get from this collection
current_waiting_list.count_number += 1
current_waitin_list.save
My last point is - please look into act_as_list gem. Since you're creating waiting list it is a must have gem for you.

FactoryGirl in Rails - Associations w/ Unique Constraints

This question is an extension to the one raised here:
Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors
The answer offered has worked perfectly for me. Here's what it looks like:
# Creates a class variable for factories that should be only created once.
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key)
begin
##singletons[factory_key] = FactoryGirl.create(factory_key)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
##singletons[factory_key]
end
end
end
The issue that has come up for me is when I need to manually build an association to support a polymorphic association with a uniqueness constraint in a hook. For example:
class Matchup < ActiveRecord::Base
belongs_to :event
belongs_to :matchupable, :polymorphic => true
validates :event_id, :uniqueness => { :scope => [:matchupable_id, :matchupable_type] }
end
class BaseballMatchup < ActiveRecord::Base
has_one :matchup, :as => :matchupable
end
FactoryGirl.define do
factory :matchup do
event { FactoryGirl::Singleton.execute(:event) }
matchupable { FactoryGirl::Singleton.execute(:baseball_matchup) }
home_team_record '10-5'
away_team_record '9-6'
end
factory :baseball_matchup do
home_pitcher 'Joe Bloe'
home_pitcher_record '21-0'
home_pitcher_era 1.92
home_pitcher_arm 'R'
away_pitcher 'Jack John'
away_pitcher_record '0-21'
away_pitcher_era 9.92
away_pitcher_arm 'R'
after_build do |bm|
bm.matchup = Factory.create(:matchup, :matchupable => bm)
end
end
end
My current singleton implementation doesn't support calling FactoryGirl::Singleton.execute(:matchup, :matchupable => bm), only FactoryGirl::Singleton.execute(:matchup).
How would you recommend modifying the singleton factory to support a call such as FactoryGirl::Singleton.execute(:matchup, :matchupable => bm) OR FactoryGirl::Singleton.execute(:matchup)?
Because right now, the above code will throw uniqueness validation error ("Event is already taken") everytime the hook is run on factory :baseball_matchup. Ultimately, this is what needs to be fixed so that there isn't more than one matchup or baseball_matchup in the DB.
As zetetic has mentioned, you can define a second parameter on your execute function to send the attributes to be used during the call to FactoryGirl.create, with a default value of an empty hash so it didn't override any of them in the case you don't use it (you don't need to check in this particular case if the attributes hash is empty).
Also notice that you don't need to define a begin..end block in this case, because there isn't anything to be done after your rescue, so you can simplify your method by defining the rescue as part of the method definition. The assignation on the case that the initialization was fine will also return the assigned value, so there is no need to explicitly access the hash again to return it. With all these changes, the code will end like:
# Creates a class variable for factories that should be only created once.
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key, attrs = {})
##singletons[factory_key] = FactoryGirl.create(factory_key, attrs)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
end
end
You need to do two things to make this work:
Accept attributes as an argument your execute method.
Key off of both the factory name and the attributes when creating the singleton factories.
Note that step 1 isn't sufficient to solve your problem. Even if you allow execute to accept attributes, the first call to execute(:matchup, attributes) will cache that result and return it any time you execute(:matchup), even if you attempt to pass different attributes to execute. That's why you also need to change what you're using as the hash key for your ##singletons hash.
Here's an implementation I tested out:
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key, attributes = {})
# form a unique key for this factory and set of attributes
key = [factory_key.to_s, '?', attributes.to_query].join
begin
##singletons[key] = FactoryGirl.create(factory_key, attributes)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
##singletons[key]
end
end
end
The key is a string consisting of the factory name and a query string representation of the attributes hash (something like "matchup?event=6&matchupable=2"). I was able to create multiple different matchups with different attributes, but it respected the uniqueness of the event/matchupable combination.
> e = FactoryGirl.create(:event)
> bm = FactoryGirl.create(:baseball_matchup)
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> f = FactoryGirl.create(:event)
> m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable => bm)
> m.id
3
Let me know if that doesn't work for you.
Ruby methods can have default values for arguments, so define your singleton method with an empty default options hash:
def self.execute(factory_key, options={})
Now you can call it both ways:
FactoryGirl::Singleton.execute(:matchup)
FactoryGirl::Singleton.execute(:matchup, :matchupable => bm)
within the method, test the options argument hash to see if anything hase been passed in:
if options.empty?
# no options specified
else
# options were specified
end

Testing before_create method in rspec and rails 3

I've looked into some tutes and all I saw were old posts on how to test before_create. Also it seems like they're all just testing that before_create was called i.e.:
#user = User.new
#user.should_receive(:method_name_called_by_before_create)
#user.send(:before_create) (sometimes they just do #user.save)
I want to actually test that my method worked and that it had assigned(and saved the variables) after creating the record.
Here are my models:
user.rb
class User < ActiveRecord::Base
has_one :character, :dependent => :destroy
after_create :generate_character
private
def generate_character
self.create_character(:name => "#{email}'s avatar")
end
end
and character.rb
class Character < ActiveRecord::Base
belongs_to :user
before_create :generate_character
private
def generate_character
response = api_call
#API CALL HERE
#set object attributes here
self.stat1 = calculate_stat1(response) + 5
self.stat2 = calculate_stat2(response) + 5
self.stat3 = calculate_stat3(response) + 5
end
def api_call
return api_call_response
end
end
I want to test that generate character indeed set the attributes without going online and calling the API call. Is this possible with rspec? I have a fixture of a json response so I was hoping I can stub out generate character and then use the fake response for testing.
Here's my character.spec:
describe Character do
before(:each) do
Character.any_instance.stub!(:api_call).and_return(fake_response.read)
#user = Factory(:user)
#character = #user.character
puts #character.inspect
end
def fake_response
File.open("spec/fixtures/api_response.json")
end
It prints out only 5 for each of the character's stats. Also I did a puts response in the generate_character method in character.rb and it still prints out the "real" api call.
I managed to do a puts in fake_response and it does goes through there but it also goes through the "real" api_call after, which makes the stub obsolete. How do I get through this?
A good approach here is extracting your api call into a self contained method. Something like this:
class Character < ActiveRecord::Base
belongs_to :user
before_create :generate_character
private
def generate_character
data = api_call
#set object attributes from data
end
def api_call
# returns a data structure
# resulting from the call
end
end
Then use RSpec's any_instance to stub the api_call method to return a fixed data structure
Character.any_instance.stub!(:api_call).and_return { {:id => 1, :attribute_one => "foo"} }
#user = User.create
#user.character.attribute_one.should == "foo"
for more info on any_instance check this commit

Resources