How do I save a model with this dynamically generated field? - ruby-on-rails

I have a rails model that looks something like this:
class Recipe < ActiveRecord::Base
has_many :ingredients
attr_accessor :ingredients_string
attr_accessible :title, :directions, :ingredients, :ingredients_string
before_save :set_ingredients
def ingredients_string
ingredients.join("\n")
end
private
def set_ingredients
self.ingredients.each { |x| x.destroy }
self.ingredients_string ||= false
if self.ingredients_string
self.ingredients_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
end
The idea is that when I create the ingredient from the webpage, I pass in the ingredients_string and let the model sort it all out. Of course, if I am editing an ingredient I need to re-create that string. The bug is basically this: how do I inform the view of the ingredient_string (elegantly) and still check to see if the ingredient_string is defined in the set_ingredients method?

Using these two together are probably causing your issues. Both are trying to define an ingredients_string method that do different things
attr_accessor :ingredients_string
def ingredients_string
ingredients.join("\n")
end
Get rid of the attr_accessor, the before_save, set_ingredients method and define your own ingredients_string= method, something like this:
def ingredients_string=(ingredients)
ingredients.each { |x| x.destroy }
ingredients_string ||= false
if ingredients_string
ingredients_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
Note I just borrowed your implementation of set_ingredients. There's probably a more elegant way to break up that string and create/delete Ingredient model associations as needed, but it's late and I can't think of it right now. :)

The previous answer is very good but it could do with a few changes.
def ingredients_string=(text)
ingredients.each { |x| x.destroy }
unless text.blank?
text.split("\n").each do |x|
ingredient = Ingredient.find_or_create_by_ingredient_string(:ingredient_string => x)
self.ingredients

I basically just modified Otto's answer:
class Recipe < ActiveRecord::Base
has_many :ingredients
attr_accessible :title, :directions, :ingredients, :ingredients_string
def ingredients_string=(ingredient_string)
ingredient_string ||= false
if ingredient_string
self.ingredients.each { |x| x.destroy }
unless ingredient_string.blank?
ingredient_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
end
def ingredients_string
ingredients.join("\n")
end
end

Related

How to get many sum of child columns?

I have two tables, Member and MemberRecord.
This are their relationship:
# Member Model
class Member < ActiveRecord::Base
has_many :member_records, :dependent => :destroy
end
# MemberRecord Model
class MemberRecord < ActiveRecord::Base
belongs_to :member
end
In MemberRecord There are many columns: two_pointer_attempt, two_pointer_made, three_pointer_attempt, three_pointer_made, free_throws_attempt, free_throws_made, offensive_rebound, defensive_rebound, assist, block, steal, turnover, foul, score
Can I get those columns sum in more efficient way?
This is what I did so far:
class Member < ActiveRecord::Base
belongs_to :team
has_many :member_records, :dependent => :destroy
validates :name, :number, presence: true
validates_uniqueness_of :name, scope: :team_id
validates_inclusion_of :number, in: 0..99
def sum_two_pointer_made
self.member_records.sum(:two_pointer_made)
end
def sum_two_pointer_attempt
self.member_records.sum(:two_pointer_attempt)
end
def sum_two_pointer_total
sum_two_pointer_made + sum_two_pointer_attempt
end
def sum_three_pointer_made
self.member_records.sum(:three_pointer_made)
end
def sum_three_pointer_attempt
self.member_records.sum(:three_pointer_attempt)
end
def sum_three_pointer_total
sum_three_pointer_made + sum_three_pointer_attempt
end
def sum_free_throws_made
self.member_records.sum(:free_throws_made)
end
def sum_free_throws_attempt
self.member_records.sum(:free_throws_attempt)
end
def sum_free_throws_total
sum_free_throws_made + sum_free_throws_attempt
end
def sum_offensive_rebound
self.member_records.sum(:offensive_rebound)
end
def sum_defensive_rebound
self.member_records.sum(:defensive_rebound)
end
def sum_assist
self.member_records.sum(:assist)
end
def sum_block
self.member_records.sum(:block)
end
def sum_steal
self.member_records.sum(:steal)
end
def sum_turnover
self.member_records.sum(:turnover)
end
def sum_foul
self.member_records.sum(:foul)
end
def sum_score
self.member_records.sum(:score)
end
end
I will give you an example with two columns and you can extend it for your number of columns.
class Member < ActiveRecord::Base
# add associations here as already present
MR_SUM_COLUMNS = %w{
assist
block
} # add more member record columns here
MR_SUM_COLUMNS.each do |column|
define_method "member_record_#{column}_sum" do
member_record_sums.send(column)
end
end
private
def member_record_sums
#_member_record_sums ||=
begin
tn = MemberRecord.table_name
sums_str =
MR_SUM_COLUMNS.map do |c|
"SUM(#{tn}.#{c}) AS #{c}"
end.join(', ')
self.member_records.select(sums_str).first
end
end
end
m = Member.first
s1 = m.member_record_assist_sum
s2 = m.member_record_block_sum
Explanation:
In ActiveRecord's select method, you can store the sum of a column as a particular value. For example:
# you have only two members with ids 1 and 2
m = Member.select("SUM(id) AS id_sum").first
m.id_sum #=> 3
So we're storing all sums of member_records in one go: in the member_record_sums method. We are also using an instance variable to store the results so that subsequent calls to the method do not query the database.
From there, all we have to do is define our sum-lookup methods dynamically.

Rails 4 save unusual model

I want to get data from form, then process it in model, and after that save. I don't save data right from form.
I made a model like this:
class NewsParser < ActiveRecord::Base
def initialize a, h
#queries = a
#contents = h
end
Migration:
class CreateNewsParsers < ActiveRecord::Migration
def change
create_table :news_parsers do |t|
t.text :contents
t.text :queries
t.timestamps
end
end
end
And I want to save it to Database. So in controller I write:
class NewsParsersController < ApplicationController
def create
#news_parser = NewsParser.new([1,2,3], {1 => 3})
#news_parser.save
end
private
def news_parser_params
params.require(:news_parser).permit(:a, :h)
end
end
It makes next error on the save line:
undefined method `[]' for nil:NilClass
How can I save my model?
As your method name is news_parser_params,i guess giving this line
#my_model = MyModel.new ([1,2,3], {1 => 3})
as
#my_model = MyModel.new(news_parser_params) should work!
Update
Now change this line back to the original
params.require(:my_model).permit(:a, :h)
to
params.require(:my_model).permit(:queries, :contents)
In your NewsParser model add these two lines
serialize :queries, Array
serialize :contents, Hash
In your controller in news_parser_params method
params.require(:news_parser).permit(:contents => {}, :queries => [])
also here
#news_parser = NewsParser.new(:queries => [1,2,3], :contents => {1 => 3})
I got it.
The first thing, that I have done was to remove initialize method from model, as it was in first message.
class NewsParser < ActiveRecord::Base
def initialize a, h
#queries = a
#contents = h
end
end
Next. There is a quote in "The Rails 4 Way", that helped me to save attribute values.
...there is also a write_attribute method that lets you change attribute values.
class Project < ActiveRecord::Base
# The description for a project cannot be changed to a blank string
def description=(new_value)
write_attribute(:description, new_value) unless new_value.blank?
end
end
So now it looks that way:
class NewsParser < ActiveRecord::Base
serialize :queries, Array
serialize :contents, Hash
def add_query str
#queries ||= []
#queries << CGI.escape(str)
write_attribute(:queries, #queries)
end
end
And permits there don't need at all.

In Rails, what is the inverse of update_attributes?

In Rails, what is the inverse of update_attributes! ?
In other words, what maps a record to an attributes hash that would recreate that record and all of it children records?
The answer is not ActiveRecord.attributes as that will not recurse into child object.
To clarify if you have the following:
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
end
Then you can pass an hash like
{"name" => "a foo", "bars_attributes" => [{"name" => "a bar} ...]}
to update_attributes. But it's not clear how to easily generate such a hash programatically for this purpose.
EDIT:
As I have mentioned in a comment, I can do something like:
foo.as_json(:include => :bars)
but I wanted a solution that uses the accepts_nested_attributes_for :bars declaration to avoid having to explicitly include associations.
Not sure how that would be the "inverse", but while Rails might not "have the answer" per-see, there is nothing stopping you from traversing through an object and creating this VERY efficiently.
Something to get you started:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for
You'll notice in the accepts_nested_attributes_for method, rails sets a hash for all models nested, in nested_attributes_options. So we can use that to get these nested associations, to populate this new hash.
def to_nested_hash
nested_hash = self.attributes.delete_if {|key, value| [:id, :created_at, :deleted_at].include? key.to_sym } # And any other attributes you don't want
associations = self.nested_attributes_options.keys
associations.each do |association|
key = "#{association}_attributes"
nested_hash[key] = []
self.send(association).find_each do |child|
nested_hash[key] << child.attributes.delete_if {|key, value| [:id, :created_at, :deleted_at].include? key.to_sym }
end
end
return nested_hash
end
OR just thought of this:
Using your example above:
foo.as_json(:include => foo.nested_attributes_options.keys)
One thing to note, this won't give you the bars_attributes where my first suggestions will. (neither will serializable_hash)
You can use the following method to include nested options in hash
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
def to_nested_hash(options = nil)
options ||= {}
if options[:except]
incl = self.nested_attributes_options.keys.map(&:to_s) - Array(options[:except]).map(&:to_s)
else
incl = self.nested_attributes_options.keys
end
options = { :include => incl }.merge(options)
self.serializable_hash(options)
end
end
If for some situations you don't want bars, you can pass options
foo.to_nested_hash(:except => :bars)
Edit: Another option if you want same behaviour in as_json, to_json and to_xml
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
def serializable_hash(options = nil)
options ||= {}
if options[:except]
incl = self.nested_attributes_options.keys.map(&:to_s) - Array(options[:except]).map(&:to_s)
else
incl = self.nested_attributes_options.keys
end
options = { :include => incl }.merge(options)
super(options)
end
def to_nested_hash(options = nil)
self.serializable_hash(options)
end
end

Tableless model with ActiveRecord associations in Rails 3.2

My application configuration includes some values which need to be used in AR relationships. I'm aware this is an odd and potentially criminal thing to attempt, but I need to maintain the configuration as a textfile, and I honestly think I have a good case for a tableless model. Unfortunately I'm having trouble convincing AR (Rails 3.2) not to look for the table. My tableless model:
class Tableless < ActiveRecord::Base
def self.table_name
self.name.tableize
end
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
def self.columns_hash
#columns_hash ||= Hash[columns.map { |column| [column.name, column] }]
end
def self.column_names
#column_names ||= columns.map { |column| column.name }
end
def self.column_defaults
#column_defaults ||= columns.map { |column| [column.name, nil] }.inject({}) { |m, e| m[e[0]] = e[1]; m }
end
def self.descends_from_active_record?
return true
end
def persisted?
return false
end
def save( opts = {} )
options = { :validate => true }.merge(opts)
options[:validate] ? valid? : true
end
end
This is extended by the actual model:
class Stuff < Tableless
has_many :stuff_things
has_many :things, :through => :stuff_things
column :id, :integer
column :name, :string
column :value, :string
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
end
This is all based on code found here on SO and elsewhere, but alas, I get SQLException: no such table: stuffs: Any clues any one?
For Rails >= 3.2 there is the activerecord-tableless gem. Its a gem to create tableless ActiveRecord models, so it has support for validations, associations, types.
When you are using the recommended way (using ActiveModel opposed to ActiveRecord) to do it in Rails 3.x there is no support for association nor types.
For Rails >= 4 you can also get support for validations, associations, and some callbacks (like after_initialize) by defining your tableless classes like this:
class Tableless < ActiveRecord::Base
def self.columns() #columns ||= []; end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
attr_accessor :id, :name, :value
has_many :stuff_things
has_many :things, :through => :stuff_things
end

Metaprogramming question involving has_many & belongs_to association

I have two classes:
class Activity < ActiveRecord::Base
belongs_to :activity_type
def belongs_to_cat_a?
self.activity_category == ActivityCategory.category_a
end
def belongs_to_cat_b?
self.activity_category == ActivityCategory.category_b
end
end
class ActivityCategory < ActiveRecord::Base
has_many :activities
def self.cat_a
ActivityCategory.find_by_name("CatA")
end
def self.cat_b
ActivityCategory.find_by_name("CatB")
end
end
Using metaprogramming, I changed ActivityCategory to the following:
class ActivityCategory < ActiveRecord::Base
has_many :activities
CATEGORIES = ['CatA', 'CatB']
class << self
CATEGORIES.each do |c|
define_method "#{c.underscore.downcase}" do # for ex: cat_a
find_by_name(c)
end
end
end
end
Ok. Now imagine in the class Activity that I have about 12 methods to check which category it belongs to.
Seems like a perfect candidate to be DRY'ed up a bit using MP.
How can I do so?
I'm not sure this is a good candidate for MP. First, you are hard coding you categories, which right away is writing code, instead of generating it. If you want to return a true/false statement when asked if it belongs to a certain category, you could just do the following:
class Activity < ActiveRecord::Base
...
def belongs_to? activity
activity_type.name == activity
end
end
and execute as so...
a = Activity.save(:activity_category => ActivityCategory.new(:name => "CatA")
a.belongs_to? "CatA" #=> true
or am i missing the point?
This isn't a recommended way as this code tends to be a little difficult to maintain (Jed's solution is nicer), but you can just apply your same style of MP you already have:
class Activity < ActiveRecord::Base
belongs_to :activity_type
CATEGORIES = ['CatA', 'CatB']
class << self
CATEGORIES.each do |c|
define_method "belongs_to_#{c.underscore.downcase}?" do # for ex: cat_a
self.activity_category == ActivityCategory.send( "category_#{c[-1]}".to_sym )
end
end
end
end
Firstly, I'd alter what you already have.
If you intercept it with method_missing, you can avoid hardcoding the category list.
class ActivityCategory < ActiveRecord::Base
has_many :activities
alias_method :old_method_missing, :method_missing
def self.method_missing(method, *args, &block)
if cat = self.class.find_by_name(method.to_s)
return cat
else
old_method_missing(method, *args, &block)
end
end
end
This works because if the method called isn't detected, it will pass it to the old method missing. Just don't name any categories "find" or anything similar if you want to pull any tricks like this!
In the same manner, in activity, you can do
class Activity < ActiveRecord::Base
belongs_to :activity_type
alias_method :old_method_missing, :method_missing
def method_missing(method, *args, &block)
if matchdata = /\Abelongs_to_category_(\w)\?/.match(method.to_s)
return ActivityCategory.find_by_name(matchdata[1]) == ActivityCategory.send(matchdata[1].to_sym)
else
old_method_missing(method, *args, &block)
end
end
end
I'm not sure the syntax is entirely right, but you could investigate that general approach

Resources