I have the following service providing me with an array mapping month/week/today to a time.
module Admin
class TimeService
INTERVAL_TIME = [
{ :id => "month", :from => Time.zone.now.beginning_of_month },
{ :id => "week", :from => Time.zone.now.beginning_of_week },
{ :id => "today", :from => Time.zone.now.beginning_of_day }
]
end
end
In my controller params[:id] is passed, holding either "month"/"week"/"today", to index method.
class TimeController < ApplicationController
def index
#entry = TimeService::INTERVAL_TIME.find {
|item| item[:id] == params[:time_id] }
end
end
Then I use the entry[:from] in my view. Everything works fine in development, but in production the value shown for "today" is usually a couple days back. I guess there might be some caching going on, but I have only been able to find people having trouble with scopes being cached. Anyone know how this can be solved?
In your current implementation, the :from keys will have the values as calculated at them moment your class is loaded, once.
In development mode, the classes are reloaded that's why you don't see the "cached" values there.
The :from keys in your constant should have lambdas as values in order to calculate the values each time they are accessed (you'll have to change the way you access their values since they'll be lambdas).
Related
I know that if you have an array and reference it as array.uniq it will return without any of the duplicates.
However in this case it is an array of objects (is that proper ruby speak?). I want each call to go into the #calls array unless the call.from is the same as a call_formatted object already present in the array.
How can I conditionally place these objects in the array if no other objects in the array have the same call.from value?
calls_raw.each do |call|
call_formatted = {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
#calls << call_formatted
end
array.uniq { |item| item[:from] }
Use #map to build your array for you and call #uniq on it...
calls_raw.map do |call|
{
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.uniq{|call| call[:from]}
The above approach will first build an array of calls larger than it may ultimately need to be, and the final call to #uniq will make the list unique.
Or, to avoid adding all the duplicates in the array, you could build it with a Hash as such:
calls_raw.each_with_object do |call, h|
h[call.from] ||= {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.values
The Hash approach will use the first occurrence of call.from as it is being set with ||=. To use the last occurrence of call.from then use a straightforward assignment with =.
It's also been suggested to just use a Set instead of an Array.
To take that approach you're going to have to implement #eql? and #hash on the class we're populating the set with.
class CallRaw
attr_accessor :from
def initialize(from)
self.from = from
end
def eql?(o)
# Base equality on 'from'
o.from == self.from
end
def hash
# Use the hash of 'from' for our hash
self.from.hash
end
end
require 'set'
s = Set.new
=> <Set: {}>
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 #from="Chewbaca">}>
# We expect now, that adding another will not grow our set any larger
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 #from="Chewbaca">}>
# Great, it's not getting any bigger
s << CallRaw.new("Chewbaca")
s << CallRaw.new("Chewbaca")
=> <Set: {#<CallRaw:0x00000002211888 #from="Chewbaca">}>
Awesome - the Set works!!!
Now, it is interesting to note that having implemented #eql? and #hash, we can now use Array#uniq without having to pass in a block.
a = Array.new
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">]
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">, <CallRaw:0x000000021c2bc0 #from="Chewbaca">]
a.uniq
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">]
Now, I'm just wondering if there is a badge that StackOverflow awards for having too much coffee before setting out to answer a question?
Unless there's some reason it has to be an array, I'd store the data in a Hash, keyed by the from value.
Then it's easy and fast to look up an entry by the from value. You can choose to insert a new value only if there's no value already with the same key, or insert the new value and let it replace the old entry with that key.
Example:
calls = Hash.new
def add(call)
if not calls[call.from]
calls[call.from] = call
end
end
I got an events helper module that somebody coded in a rails application. I am working on a form that can allow someone to create a new event.
here is a part of the form
=form.input :sponsorship_type, collection: get_event_labels(:event_types), as: :select_other
=form.input :society_name
it used to be
=form.input :event_type, collection: get_event_labels(:sponsorship_types), as: :select_other
=form.input :society_name
per the client request I had to drop the event_type column from the events table and added this instead
t.string "sponsorship_type"
the old schema has this
t.string "event_type"
this is the module
module EventsHelper
LABEL_MAP = {
institutions: [::INSTITUTIONS, 'activerecord.values.institutions.name'],
event_types: [::EVENT_TYPES, 'activerecord.values.event_types'],
industries: [::INDUSTRIES, 'activerecord.values.industries'],
referrers: [::REFERRERS, 'activerecord.values.referrers'],
regions: [::REGIONS, 'activerecord.values.regions'],
cities: [::CITIES, 'activerecord.values.cities']
}.freeze
def get_event_labels(type)
if Geokit::Geocoders::IpGeocoder.geocode(remote_ip).country_code == 'TW' and type == :event_types
return {
'活動/班' => 'Activities/Classes',
'食品和飲料' => 'Food&Beverage',
'優惠券' => 'Coupons',
'現金' => 'Cash',
'器材' => 'Equipment',
'獎品' => 'Prizes'
}
end
Hash[
LABEL_MAP[type][0].map do |constant|
[I18n.t("#{LABEL_MAP[type][1]}.#{constant}"),
constant]
end
]
end
def remote_ip
request.remote_ip
end
end
what is this? [::EVENT_TYPES, 'activerecord.values.event_types']
i tried just changing all the event_types to sponsorship_type. and then I am getting a
': uninitialized constant SPONSORSHIP_TYPES (NameError)
Its probably because activerecord.values.sponsorship_types have no values. How do I access it and put in values?
what is this?
::EVENT_TYPES
my end goal is to return the hash
return {
'活動/班' => 'Activities/Classes',
'食品和飲料' => 'Food&Beverage',
'優惠券' => 'Coupons',
'現金' => 'Cash',
'器材' => 'Equipment',
'獎品' => 'Prizes'
}
as selection option for the user on the form.
EVENT_TYPES is a constant. It must be defined somewhere in that application, perhaps in the controller or somewhere in the config folder. Find it and define your SPONSORSHIP_TYPES in the same way.
activerecord.values.event_types looks like a localization key. Look into your localization files in config/locales/... for some yaml hash with this structure. Add a new node sponsorship_types in the same way.
I have only a vague idea on phrasing this, so question as needed:
I have a set of values I'm passing in my rails controller on a regular basis to a widget that differs slightly from page to page, from what I pass to it. This is is starting to get unwieldy for every controller, so I added a small class to help concatenate that process a bit (basic starting gist below).
#return dashcontroller hash from more succinct cues
module DashControl
class DashControl
attr_accessor :title, :instance, :actions
def initialize(title='default title', instance='default instance', actions={})
#title = title
#instance = instance
initialize_actions(actions)
end
def initialize_actions(actions)
actions.kind_of?(Hash) ? #actions = actions : initialize_tag(actions)
end
def initialize_tag(tag)
case tag
when :manage_default
#actions = {:statusactions => [],
:formactions => [ ['#tabaccount', 'addaccount'],
['#tabuser', 'addusers'],
['#tabadd','adddomain'] ],
:linkactions => [ [] ],
:filteractions => [ [] ] }
when :none
#actions = {}
#when
# #actions = {}
else
#actions = #actions
end
end
def dashcontroller
{:title => #title, :instance => #instance, :actions => #actions }
end
end
end
So basically I just need to pass an instance of this.dashcontroller and I get the hash I need with a lot less chaos in my controllers . The issue is with the #instance variable. I want to pass in the instance I'm using e.g. #book, #account, etc, and have it come out as #book, #account, etc. Instead, I get the contents of whatever I put into there as :instance => (contents of that instance). It doesn't seem right to me as before I was just using e.g. #account, and then using that, but looking at it might not make any sort of difference in the widget, as I juggle things and work on my code-fu.
Basically my question is how to push an instance variable through a class like this, and still have it accessibile as it went in without having to do any backflips and transformations on the other side. There is probably a better way, but this is what I'm working with at the moment.
edit: pseudo-code
DashControl::DashControl.new("Catchy Title", #book, :none).dashcontroller
#=> {:title => "Catchy Title", :instance => #book, :actions => {} }
I think I can work with it, like I said its more an issue of my understanding of how things flow than an actual bug or anything difficult. I'd like to not have to do more gymnastics on the other end with the instance stuff, though the contents are there and that is all I really need, I just need some input on thinking it through to be less of a mess. I really need to refine what I'm sending through this, or use this to further refine what I'm sending on is the bottom line lesson to take away right now.
edit:
I ended up tossing this, but it was a learning experience...I went back the widget and I know more than when I originally set up the widget, so I've been able to set that up to take only the instance variable and bootstrap what it needs without adding another class, cleaning up my controllers and handing a lot back to the widget where I suspect it should/could have been to start.
Based on your code and example, this fits:
# No need to put a class in a namespace of the same name, just make the module a class
# Also, if you inherit from a struct, it can save you a lot of typing. It defines the setters and getters for you.
class DashControl < Struct.new(:title, :instance, :actions)
# since it looks like you always access it the same way, create a class method
# which does this initialization and invocation
def self.for(*args)
new(*args).dashcontroller
end
def initialize(title='default title', instance='default instance', actions=:none)
# here, we can use our own defaults and normalization and pass the results up to the struct
super title, instance, normalize(actions)
end
# didn't make sense to call this initialize_tag, as it was initializing actions
# also there was already an initialize actions method which just checked for the case of a hash
# but then elsewhere you checked for other things. Better to just put it all in one method and return it
# (then you aren't setting it every time you want to ask it to calculate that value)
# also using guard clauses (the if statements that return early) instead of the case, as they are easier to understand
def normalize(actions)
return Hash.new if actions == :none
return actions unless actions == :manage_default
default_actions
end
# the value of default_actions is complicated and noisy, separate it out to its own method
# this prevents it from cluttering the code around it, and also allows us to access,
# and to do this without the side effects of setting values.
def default_actions
{ :statusactions => [],
:formactions => [ ['#tabaccount', 'addaccount'],
['#tabuser', 'addusers'],
['#tabadd','adddomain'] ],
:linkactions => [ [] ],
:filteractions => [ [] ] }
end
# use the getters instead of the ivars (I consider this a generally best practice -- and you could have
# done it before, since you declared the attr_accessor, even though I'm accessing it through the struct)
def dashcontroller
{:title => title, :instance => instance, :actions => actions }
end
end
DashControl.for # => {:title=>"default title", :instance=>"default instance", :actions=>{}}
DashControl.for('Catchy Title', '#book', :none) # => {:title=>"Catchy Title", :instance=>"#book", :actions=>{}}
DashControl.for('Catchy Title', '#book', :manage_default) # => {:title=>"Catchy Title", :instance=>"#book", :actions=>{:statusactions=>[], :formactions=>[["#tabaccount", "addaccount"], ["#tabuser", "addusers"], ["#tabadd", "adddomain"]], :linkactions=>[[]], :filteractions=>[[]]}}
DashControl.for('Catchy Title', '#book', a: 'b') # => {:title=>"Catchy Title", :instance=>"#book", :actions=>{:a=>"b"}}
DashControl.for('Catchy Title', '#book', 123) # => {:title=>"Catchy Title", :instance=>"#book", :actions=>123}
I wrote this retrieval statement to check if an appointment being saved or created dosent conflict with one thats already saved. but its not working, can someone please point me to where I'm going wrong?
#new_appointment = :appointment #which is the params of appointment being sent back from submit.
#appointments = Appointment.all(:conditions => { :date_of_appointment => #new_appointment.date_of_appointment, :trainer_id => #new_appointment.trainer_id}
)
the error is from the :date_of_appointment => #new_appointment.date_of_appointment this will always be false as:
thank you
At face value, there doesn't appear to be anything wrong with your syntax. My guess is that #new_appointment isn't containing the values you're expecting, and thus the database query is returning different values than you expect.
Try dumping out #new_appointment.inspect or check the logfiles to see what SQL the finder is producing, or use
Appointment.send(:construct_finder_sql, :conditions => {
:date_of_appointment => #new_appointment.date_of_appointment,
:trainer_id => #new_appointment.trainer_id
})
to see the SQL that will be generated (construct_finder_sql is a protected ActiveRecord::Base method).
Update based on your edit
#new_appointment = :appointment should be something like #new_appointment = Appointment.new(params[:appointment]). :appointment is just a symbol, it is not automatically related to your params unless you tell it to.
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